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).

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

Security & Privacy: Webhook payloads contain only event metadata and identifiers. Detailed card information and eligibility results are not included in webhook payloads for security reasons. You'll need to use the API to fetch full details using the provided IDs.

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 webhooks require the Eligibility Verification feature to be enabled on your account.

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

  1. Log into your CardScan Dashboard

  2. Navigate to the Webhooks section

  3. Click Add Endpoint

  4. Enter your webhook URL (e.g., https://your-domain.com/webhooks/cardscan)

  5. Select the event types you want to receive

  6. Click Create Endpoint

If you don't specify any event types, your endpoint will receive all events by default. We recommend selecting specific event types to avoid receiving unnecessary messages.

Testing Webhooks

Once you've added an endpoint, you should test it to ensure it's working correctly.

Dashboard Testing

  1. Go to your webhook endpoint in the dashboard

  2. Click on the Testing tab

  3. Select an event type to test

  4. Click Send Test Event

  5. 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);
}

For more examples and supported languages, check out the Svix webhook verification documentation.

Finding Your Webhook Secret

Each webhook endpoint has a unique secret key that you can find in the CardScan Dashboard:

  1. Go to your webhook endpoint settings

  2. Click Signing Secret

  3. 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:

  1. Find the failed message in your webhook endpoint logs

  2. Click the options menu next to the failed attempt

  3. Select Resend to retry the delivery

Bulk Recovery:

  1. Go to your endpoint's details page

  2. Click Options > Recover Failed Messages

  3. Choose a time window to recover from

  4. 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:

  1. Fix the underlying issue with your endpoint

  2. Go to the webhook dashboard

  3. Find your endpoint and click Enable Endpoint

Replay Failed Messages

To recover from downtime or misconfigurations:

  1. Single Event: Find the message and click Resend

  2. Time Range: Use Options > Recover Failed Messages to replay all failed events from a specific time period

  3. From Specific Message: Click the options menu on any message and select Replay all failed messages since this time

Need help with your webhook implementation? Contact us at [email protected] and we'll help you get set up correctly.

Last updated

Was this helpful?