Webhooks

Real-time notifications for asynchronous requests, batches, fraud cases, production reviews and billing.

Overview

Many RDCPASS operations finish after your HTTP call returns: asynchronous requests (Prefer: respond-async), batches of up to 10,000 items, fraud cases investigated on RDCPASS's own timeline, ongoing AML monitoring, production reviews and prepaid-wallet balances. Register a webhook endpoint and RDCPASS notifies you the moment something happens, instead of you polling for it. See Single, batch & async for how asynchronous and batch requests work.

Registering an endpoint

Register one HTTPS endpoint URL per application from the console (Owners, Administrators and Developers can do this). Every event for that application is delivered to it, in sandbox and production separately — use the livemode field to tell them apart. Subscribe to all events, or select only the types you handle.

Event catalogue

EventFires whendata.object
kyc_validation.completedAn asynchronous single KYC Validation finishes.job
face_recognition.completedAn asynchronous Face Recognition finishes (match or no_match).job
kyb_verification.completedAn asynchronous KYB Verification finishes.job
aml_screening.completedAn asynchronous AML & CTF Screening finishes.job
aml_screening.alertOngoing monitoring detects a new sanctions, PEP, adverse-media or watchlist hit for a monitored subject.aml_screening
credit_score.completedAn asynchronous Credit Scoring request finishes.job
age_verification.completedAn asynchronous Age Verification finishes.job
fraud_report.createdA fraud report is received and assigned a case number.fraud_report
fraud_report.status_changedA fraud case moves between received, under_review, confirmed, dismissed and escalated_to_authorities.fraud_report
job.completedAny asynchronous single request reaches completed — the job embeds the result.job
job.failedAn asynchronous single request fails; the job carries an error object.job
batch.completedEvery item of a batch has succeeded.batch
batch.completed_with_errorsA batch has finished and at least one item failed.batch
application.production_status_changedYour application’s production review changes status (submitted, in_review, changes_requested, approved, rejected).application
billing.balance_lowYour prepaid wallet falls below the alert threshold set by your Financial officer.wallet
billing.invoice_issuedA monthly postpaid invoice is issued.invoice
The legacy usurpation_report.status_changed event is still delivered for reports filed through the /v1/fraud/usurpation-reports alias. New integrations should use fraud_report.status_changed.

Asynchronous requests and batches

For an asynchronous single request, RDCPASS sends two events when it finishes: the service event (for example kyc_validation.completed, whose data.object is the completed job — its result is exactly the object the synchronous call returns) and the generic job.completed or job.failed. Handle whichever suits your code — most integrations listen to the service events and ignore the job events.

For a batch, RDCPASS sends one batch.completed or batch.completed_with_errors event when the last item is processed. Its data.object is the batch summary, not the results: page through GET /v1/batches/{batch_id}/results (up to 500 items per page, using next_cursor) to read every item, and match items to your records with the reference you supplied. Results stay available for 30 days.

  • Store the job or batch id returned in the 202 response alongside your own reference.
  • Treat the webhook as the trigger, then read the authoritative state with GET /v1/jobs/{job_id} or GET /v1/batches/{batch_id} if you need to be certain.
  • If a webhook never arrives (endpoint down beyond the retry schedule), the same data remains available by polling — webhooks are a notification, never the only copy.

Event envelope

Every event shares the same envelope. data.object holds the full resource the event is about, in exactly the shape the API returns for it:

{
  "id": "evt_7f1e2a9c4b3d8f60",
  "object": "event",
  "type": "fraud_report.status_changed",
  "livemode": true,
  "created_at": "2026-09-26T14:32:00Z",
  "data": {
    "object": { "id": "frd_2c9e4a71b0", "object": "fraud_report", "...": "..." },
    "previous_attributes": { "status": "under_review" }
  }
}
FieldTypeDescription
idstringUnique identifier for this event. Use it to deduplicate — see Idempotency below.
objectstringAlways "event".
typestring enumThe event type — one of the catalogue values above.
livemodebooleantrue for production, false for sandbox.
created_atstringRFC 3339 timestamp the event occurred at.
data.objectobjectThe resource the event is about. Its object field names its type.
data.previous_attributesobjectPresent on *.status_changed events only: the fields that changed, with their previous values.

Sample payloads

kyc_validation.completed

kyc_validation.completed
{
  "id": "evt_1a4c8e2f7b9d3605",
  "object": "event",
  "type": "kyc_validation.completed",
  "livemode": true,
  "created_at": "2026-09-26T10:15:04Z",
  "data": {
    "object": {
      "id": "job_4b1d9e7a2c",
      "object": "job",
      "service": "kyc_validation",
      "status": "completed",
      "created_at": "2026-09-26T10:15:00Z",
      "result_url": "/v1/jobs/job_4b1d9e7a2c",
      "result": {
        "id": "kyc_8d2f6a1c93",
        "object": "kyc_validation",
        "livemode": true,
        "reference": "cust-0001",
        "status": "completed",
        "result": "verified",
        "certified_account": true,
        "identifier": {
          "type": "rdcpass_id",
          "value": "COD-2103-0214-4937"
        },
        "match": {
          "full_name": "match",
          "date_of_birth": "match",
          "age": "match",
          "score": 0.98
        },
        "account": {
          "rdcpass_id": "COD-2103-0214-4937",
          "status": "active",
          "certified": true,
          "created_at": "2025-03-14T09:22:41Z",
          "level_of_assurance": "LOA3"
        },
        "kyc": {
          "basic": {
            "full_name": "Kabeya Mwamba Tshisekedi",
            "first_name": "Kabeya",
            "last_name": "Tshisekedi",
            "date_of_birth": "1988-04-12",
            "age": 38,
            "gender": "male",
            "nationality": "COD"
          },
          "phone_numbers": [
            {
              "number": "+243812345678",
              "operator": "Vodacom",
              "is_primary": true,
              "verified": true
            }
          ]
        },
        "scopes_applied": [
          "kyc.basic",
          "kyc.phone_numbers"
        ],
        "scopes_withheld": [],
        "purpose": "customer_onboarding",
        "created_at": "2026-09-26T10:15:02Z"
      }
    }
  }
}

batch.completed

batch.completed
{
  "id": "evt_5d0b3f8a1c6e2749",
  "object": "event",
  "type": "batch.completed",
  "livemode": true,
  "created_at": "2026-09-26T10:41:18Z",
  "data": {
    "object": {
      "id": "bat_7c2e91f04a",
      "object": "batch",
      "service": "kyc_validation",
      "status": "completed",
      "total_items": 2500,
      "processed_items": 2500,
      "succeeded_items": 2500,
      "failed_items": 0,
      "created_at": "2026-09-26T10:15:00Z",
      "completed_at": "2026-09-26T10:41:17Z",
      "results_url": "/v1/batches/bat_7c2e91f04a/results"
    }
  }
}

fraud_report.status_changed

fraud_report.status_changed
{
  "id": "evt_9e2d7c4a0b1f5836",
  "object": "event",
  "type": "fraud_report.status_changed",
  "livemode": true,
  "created_at": "2026-09-28T08:05:43Z",
  "data": {
    "object": {
      "id": "frd_2c9e4a71b0",
      "object": "fraud_report",
      "case_number": "RDC-FRD-2026-004812",
      "type": "identity_usurpation",
      "status": "confirmed",
      "reporter_reference": "INC-2026-0917",
      "created_at": "2026-09-26T14:02:11Z",
      "updated_at": "2026-09-28T08:05:42Z"
    },
    "previous_attributes": { "status": "under_review" }
  }
}

Delivery and headers

Every delivery is an HTTP POST with the event as the JSON body, plus three headers:

Example delivery
POST /your-webhook-endpoint HTTP/1.1
Host: yourapp.example.com
Content-Type: application/json
X-RDCPASS-Webhook-Id: evt_7f1e2a9c4b3d8f60
X-RDCPASS-Webhook-Timestamp: 1790432520
X-RDCPASS-Webhook-Signature: 9c3f7a1e2b6d4c85...

{ "id": "evt_7f1e2a9c4b3d8f60", "object": "event", "type": "fraud_report.status_changed", ... }
FieldDescription
X-RDCPASS-Webhook-IdSame value as the body’s id — read this without parsing the body, for logging or fast deduplication.
X-RDCPASS-Webhook-TimestampUnix seconds this delivery attempt was sent. Reject deliveries with a timestamp too far from your own clock, the same way you’d guard against a replayed request.
X-RDCPASS-Webhook-SignatureHex-encoded HMAC-SHA256 — see Verifying the signature below.

Verifying the signature

Compute HMAC-SHA256 over {timestamp}.{raw request body}, using the same Payload Encryption Key your application already has for request signing — hex-encode the result and compare it to X-RDCPASS-Webhook-Signature with a constant-time comparison, never ===.

verify-webhook-signature.js
const crypto = require('node:crypto')

function verifyWebhookSignature(payloadSecretKey, timestamp, rawBody, signature) {
  const expected = crypto
    .createHmac('sha256', payloadSecretKey)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}

The same standard HMAC primitives used for request signing in every language — see Authentication — apply here identically; only the canonical string differs.

Retry behavior

Respond with any 2xx status within 5 seconds to acknowledge an event. Anything else — a non-2xx status, a timeout, or an unreachable endpoint — is treated as a failed delivery attempt and retried on this schedule:

AttemptDelay since previous attempt
1Immediately
21 minute
310 minutes
41 hour
56 hours (final)
After the final attempt fails, RDCPASS stops retrying and marks the event as permanently failed. Failed events remain visible from the console, where they can be redelivered.

Idempotency and ordering

Acknowledge an event as soon as you’ve durably queued it for processing, before doing any slow work — a delivery that times out on your end is indistinguishable from one that never arrived, so RDCPASS retries it. Because of this, the same id can arrive more than once. Store processed event IDs and skip any you’ve already handled.

Events are not guaranteed to arrive in the order they occurred — a retried fraud_report.status_changed can arrive after a later one. Compare created_at (or the resource’s updated_at) with what you have stored before applying a change.

Rate limits and billing

Webhook deliveries are neither metered against your application’s quota nor billed — see Rate limits & quotas for the limits that apply to the calls that create the events in the first place.

Questions about your integration? Contact developer support