Webhooks 🔔
Webhooks provide real-time notifications when events occur in your CardScan.ai account, enabling you to build responsive applications that react immediately to card scanning and eligibility verification events.
Introduction
Webhooks are how services notify each other of events. At their core, they are just a POST request to a pre-determined endpoint that you control.
The endpoint can be whatever you want, and you can configure them from the CardScan Dashboard. You normally use one endpoint per service, and that endpoint listens to all of the event types you're interested in.
For example, if you receive webhooks from CardScan.ai, you can structure your URL like: https://www.example.com/cardscan/webhooks/
.
The way to indicate that a webhook has been processed successfully is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (15 seconds).
It's important to disable CSRF protection for your webhook endpoint if your framework enables it by default.
Another critical aspect of handling webhooks is to verify the signature and timestamp when processing them. You can learn more about this in the signature verification section.
Webhook Payload Structure
All webhook events follow a consistent structure:
{
"type": "event.type",
"data": {
"id": "resource_id",
"state": "current_state",
"created_at": "2024-02-05T15:50:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
Available Events
CardScan.ai sends webhooks for card scanning and eligibility verification events. Each webhook includes event metadata, but you'll need to call our API to get the full details.
Card Events
card.created
Triggered when a new insurance card is created at the start of a scanning attempt.
{
"type": "card.created",
"data": {
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"state": "pending",
"created_at": "2024-02-05T15:50:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
card.completed
Triggered after a successful insurance card scan. Use the card_id
to fetch the extracted data via the Get Card API.
{
"type": "card.completed",
"data": {
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"state": "completed",
"created_at": "2024-02-05T15:50:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
card.error
Triggered when an error occurs during an insurance card scan.
{
"type": "card.error",
"data": {
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"state": "error",
"created_at": "2024-02-05T15:50:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890",
"error_message": "Failure during OCR process - [E507]"
}
}
card.deleted
Triggered when a scanned insurance card is marked as deleted.
{
"type": "card.deleted",
"data": {
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"deleted_at": "2024-02-05T16:30:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
Eligibility Events
eligibility.created
Triggered when a new eligibility record is created for an insurance card.
{
"type": "eligibility.created",
"data": {
"eligibility_id": "elig_12345",
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"state": "pending",
"created_at": "2024-02-05T15:55:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
eligibility.completed
Triggered when an eligibility check for an insurance card is successfully completed. Use the eligibility_id
to fetch the full eligibility details via the API.
{
"type": "eligibility.completed",
"data": {
"eligibility_id": "elig_12345",
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"state": "completed",
"created_at": "2024-02-05T15:55:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
eligibility.error
Triggered when an error occurs during an eligibility check.
{
"type": "eligibility.error",
"data": {
"eligibility_id": "elig_12345",
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"state": "error",
"created_at": "2024-02-05T15:55:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890",
"error_message": "Unable to verify eligibility with payer"
}
}
eligibility.deleted
Triggered when an eligibility record is deleted.
{
"type": "eligibility.deleted",
"data": {
"eligibility_id": "elig_12345",
"card_id": "a1d743ee-3bb9-468d-a2c5-4e33fa0e1c6e",
"deleted_at": "2024-02-05T16:30:14.856792Z",
"session_id": "sess_12345",
"user_id": "user_67890"
}
}
Processing Webhook Events
Since webhook payloads only contain event metadata, you'll typically follow this pattern:
Example: Processing a Card Completion
import { CardScanApi } from '@cardscan.ai/cardscan-client';
app.post('/webhooks/cardscan', async (req, res) => {
try {
// Verify the webhook signature first
const payload = wh.verify(req.body, req.headers);
// Acknowledge the webhook immediately
res.status(200).send('OK');
// Process the event asynchronously
if (payload.type === 'card.completed') {
const { card_id, user_id } = payload.data;
// Fetch the full card details using the API
const client = new CardScanApi({
sessionToken: await getSessionTokenForUser(user_id),
live: true
});
const cardDetails = await client.getCard(card_id);
// Process the card data
await processCompletedCard(cardDetails);
}
} catch (err) {
console.error('Webhook processing failed:', err);
res.status(400).send('Invalid signature');
}
});
API Client Libraries
To make processing webhook events easier, use our official API client libraries that include typed models for parsing responses:
Available clients:
TypeScript/JavaScript -
@cardscan.ai/cardscan-client
Python -
cardscan-client
Go -
github.com/cardscan-ai/api-clients/go
Swift - CardScan Swift Package
Java - Maven package
These clients provide strongly-typed models for card details, eligibility results, and API responses, making it easier to work with the data you fetch after receiving webhook notifications.
Adding Webhook Endpoints
To start receiving webhook notifications, you need to configure your endpoints in the CardScan Dashboard.
Dashboard Configuration
Log into your CardScan Dashboard
Navigate to the Webhooks section
Click Add Endpoint
Enter your webhook URL (e.g.,
https://your-domain.com/webhooks/cardscan
)Select the event types you want to receive
Click Create Endpoint
API Configuration Coming Soon: While webhook endpoints are currently managed through the dashboard, API-based endpoint management is available upon request. Contact [email protected] if you need programmatic webhook management.
Testing Webhooks
Once you've added an endpoint, you should test it to ensure it's working correctly.
Dashboard Testing
Go to your webhook endpoint in the dashboard
Click on the Testing tab
Select an event type to test
Click Send Test Event
Review the response and check your endpoint logs
After sending a test event, you can click into the message to view:
The complete message payload
All delivery attempts
Success/failure status
Response details
Test Event Payloads
Test events use the same structure as real events but with clearly marked test data:
{
"type": "card.completed",
"data": {
"card_id": "test_card_12345",
"state": "completed",
"created_at": "2024-02-05T15:50:14.856792Z",
"session_id": "test_session",
"user_id": "test_user"
}
}
Signature Verification
Webhook signatures let you verify that webhook messages are actually sent by CardScan.ai and not a malicious actor. You should always verify webhook signatures in production.
Why Verify Signatures?
Without signature verification, any malicious actor could send fake webhook events to your endpoint, potentially compromising your application's security and data integrity.
How to Verify
CardScan.ai uses Svix for webhook delivery, which provides battle-tested signature verification. You can use Svix's libraries to easily verify webhook signatures:
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// These headers are sent with every webhook
const headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"type": "card.completed", "data": {...}}';
const wh = new Webhook(secret);
try {
// Throws on error, returns the verified content on success
const verifiedPayload = wh.verify(payload, headers);
console.log("Webhook verified successfully:", verifiedPayload);
} catch (err) {
console.error("Webhook verification failed:", err.message);
}
Finding Your Webhook Secret
Each webhook endpoint has a unique secret key that you can find in the CardScan Dashboard:
Go to your webhook endpoint settings
Click Signing Secret
Copy the secret (it starts with
whsec_
)
Retry Schedule
CardScan.ai automatically retries failed webhook deliveries using an exponential backoff strategy to ensure reliable delivery.
Retry Attempts
Each webhook message is attempted based on the following schedule:
Immediately
5 seconds
5 minutes
30 minutes
2 hours
5 hours
10 hours
10 hours (final attempt)
For example, a webhook that fails three times before succeeding will be delivered approximately 35 minutes and 5 seconds after the first attempt.
Automatic Disabling
If all delivery attempts to an endpoint fail for a period of 5 days, the endpoint will be automatically disabled to prevent unnecessary retry attempts.
Manual Retries
You can manually retry webhook deliveries from the dashboard:
Single Message Retry:
Find the failed message in your webhook endpoint logs
Click the options menu next to the failed attempt
Select Resend to retry the delivery
Bulk Recovery:
Go to your endpoint's details page
Click Options > Recover Failed Messages
Choose a time window to recover from
All failed messages in that period will be resent
Troubleshooting
Here are common issues and solutions when working with CardScan.ai webhooks:
Common Problems
Not Using Raw Payload Body
Most common issue. When verifying signatures, you must use the raw string body of the webhook payload exactly as received. Don't parse it as JSON first.
// ❌ Wrong - don't parse JSON first
const parsedBody = JSON.parse(requestBody);
const stringifiedBody = JSON.stringify(parsedBody);
wh.verify(stringifiedBody, headers); // This will fail
// ✅ Correct - use raw body
wh.verify(requestBody, headers);
Wrong Secret Key
Make sure you're using the correct secret for your specific endpoint. Each endpoint has its own unique secret key.
Incorrect Response Codes
Return 2xx status codes (200-299) for successful webhook processing, even if your business logic determines the event should be ignored.
// ✅ Correct
app.post('/webhooks/cardscan', (req, res) => {
try {
const payload = wh.verify(req.body, req.headers);
// Process the webhook...
res.status(200).send('OK');
} catch (err) {
res.status(400).send('Invalid signature');
}
});
Response Timeouts
Webhooks must respond within 15 seconds. For complex processing, acknowledge the webhook immediately and process asynchronously:
app.post('/webhooks/cardscan', async (req, res) => {
try {
const payload = wh.verify(req.body, req.headers);
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(payload);
} catch (err) {
res.status(400).send('Invalid signature');
}
});
Failure Recovery
Re-enable a Disabled Endpoint
If your endpoint was disabled due to consecutive failures:
Fix the underlying issue with your endpoint
Go to the webhook dashboard
Find your endpoint and click Enable Endpoint
Replay Failed Messages
To recover from downtime or misconfigurations:
Single Event: Find the message and click Resend
Time Range: Use Options > Recover Failed Messages to replay all failed events from a specific time period
From Specific Message: Click the options menu on any message and select Replay all failed messages since this time
Last updated
Was this helpful?