Skip to main content
Webhooks let you register an HTTPS endpoint that Vouchmark associates with one of your apps (KYB widgets). A webhook is keyed by the combination of app, environment, and service type. Each registration points at a single endpoint URL, and only receives events for its subscribed serviceType running in its environment. When a matching event occurs, Vouchmark POSTs a signed JSON payload to your endpoint. Verify every delivery with the webhook’s signing secret before trusting it.

The registration model

A webhook registration has these fields:
FieldValuesNotes
appIdthe ID of one of your KYB widgetsMust be a widget owned by your account.
environmentsandbox or liveSeparate endpoints per environment.
serviceTypekyc_widget, verification, trust_monitor, disputesOne registration per service.
endpointan http/https URL (max 2048 chars)Where deliveries are sent.

The signing secret

Each webhook has its own signing secret in the form whsec_....
  • The full secret is returned only once, in the response to create and rotate. Store it securely the moment you receive it.
  • In the list response and the dashboard, the secret is masked (for example whsec_…ab12, last 4 characters visible) so you can identify it without exposing it.
  • If a secret leaks, rotate it. The old secret stops verifying deliveries immediately, so update your integration in lockstep.

The event envelope

Every delivery is a JSON body with this shape:
{
  "id": "whd_...",
  "event": "verification.cac.completed",
  "createdAt": "2026-05-12T09:14:00.000Z",
  "data": { }
}
FieldDescription
idUnique delivery ID (prefix whd_). Use it to dedupe — see Reliability.
eventThe event type. See Event types.
createdAtISO 8601 timestamp of when the event was created.
dataThe event-specific payload.

Headers

Each delivery carries these headers:
HeaderExampleDescription
Content-Typeapplication/jsonBody is always JSON.
X-Vouchmark-Eventverification.cac.completedThe event type, mirroring event.
X-Vouchmark-Deliverywhd_...The delivery ID, mirroring id.
X-Vouchmark-Signaturet=1715505240,v1=4f1d...The signature — see below.
X-Vouchmark-Timestamp1715505240Unix seconds the signature was computed at.

Verifying the signature

The X-Vouchmark-Signature header has the form t=<unixSeconds>,v1=<hmacHex>, where t matches X-Vouchmark-Timestamp. To verify a delivery:
  1. Read t and v1 from the X-Vouchmark-Signature header.
  2. Build the signed payload by concatenating the timestamp, a ., and the exact raw request body (the bytes you received, before any JSON re-serialization):
    signedPayload = `${t}.${rawRequestBody}`
    
  3. Compute HMAC-SHA256(signedPayload, signingSecret) and encode it as lowercase hex.
  4. Compare your computed value against v1 in constant time.
  5. Reject deliveries whose t is too far in the past to protect against replay attacks.
Verify against the raw request body. If your framework parses and re-serializes JSON before you read it, the bytes change and the signature will not match. Capture the raw body string first.

Node.js example

import crypto from "node:crypto";

const TOLERANCE_SECONDS = 5 * 60;

function verifyVouchmarkWebhook(rawBody, signatureHeader, signingSecret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((kv) => kv.split("=")),
  );
  const timestamp = Number(parts.t);
  const provided = parts.v1;
  if (!timestamp || !provided) return false;

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false;

  const signedPayload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(signedPayload)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(provided);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
In Express, capture the raw body with express.raw({ type: "application/json" }) (or express.json({ verify })) so rawBody is the unparsed string.

Event types

A webhook only receives events for its subscribed serviceType and environment. The event types currently emitted are:
EventService type
kyb.applicant.decisionkyc_widget
verification.cac.completedverification
verification.tin.completedverification

Service types

Service typeDescription
kyc_widgetApplicant decisions from your embedded KYB widget.
verificationStandalone verification results (CAC, TIN).
trust_monitorOngoing trust monitoring events.
disputesDispute lifecycle events.

Sandbox vs live

sandbox and live are fully separate. Register a webhook per environment, and remember that each environment has its own signing secret. Test against sandbox before pointing live traffic at your production endpoint.

Reliability and retries

  • Deliveries are at-least-once. The same event may arrive more than once, so dedupe on the delivery id (the whd_... value, also in X-Vouchmark-Delivery).
  • Vouchmark attempts each delivery up to 5 times with exponential backoff (base 5 seconds).
  • Each attempt has a 10 second request timeout.
  • Respond with a 2xx status quickly and process the event asynchronously. Any non-2xx response or a timeout triggers a retry.

Register an endpoint

curl -X POST https://api.vouchmark.com/v1/developers/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "appId": "wgt_aBcD...",
    "environment": "live",
    "serviceType": "verification",
    "endpoint": "https://your-app.com/hooks/vouchmark"
  }'
Response
{
  "success": true,
  "data": {
    "id": "whk_aBcD...",
    "appId": "wgt_aBcD...",
    "environment": "live",
    "serviceType": "verification",
    "endpoint": "https://your-app.com/hooks/vouchmark",
    "isActive": true,
    "signingSecret": "whsec_aBcD...",
    "createdAt": "2026-05-12T09:14:00Z"
  }
}
The signingSecret is returned in full here only — store it now.

List your endpoints

curl https://api.vouchmark.com/v1/developers/webhooks \
  -H "Authorization: Bearer $TOKEN"
List responses return the secret masked as signingSecretMasked, never in full.

Rotate a signing secret

curl -X POST https://api.vouchmark.com/v1/developers/webhooks/whk_aBcD.../rotate-secret \
  -H "Authorization: Bearer $TOKEN"
Returns a new full signingSecret. The previous secret stops verifying deliveries immediately. See Rotate webhook secret.

Delete an endpoint

curl -X DELETE https://api.vouchmark.com/v1/developers/webhooks/whk_aBcD... \
  -H "Authorization: Bearer $TOKEN"
Registrations are soft-deleted, so a deleted endpoint stops being listed immediately.

Rate limits

Webhook create/delete is limited to 100 changes per hour per IP. See Rate limits.