Webhooks

Webhooks push real-time event notifications to your server when compliance-relevant activity occurs. Each delivery is HMAC-SHA-256 signed so you can verify it came from WhoComply.

GETs require read scope. Mutations (create, delete, rotate) require write scope.

POST/tenants/{slug}/webhooks

Create endpoint

Register a URL to receive webhook events. WhoComply generates a signing secret and returns it once on creation.

  • Name
    label
    Type
    string
    Description

    Human-readable label shown in the dashboard.

  • Name
    url
    Type
    string
    Description

    The HTTPS URL that receives POST requests. Must be publicly reachable.

  • Name
    event_types
    Type
    string[]
    Description

    Array of event types to subscribe to. See event types below.

The signing_secret is returned only on creation and after rotation. Store it securely; you cannot read it back later.

Request

POST
/tenants/{slug}/webhooks
curl -X POST "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks" \
  -H "Authorization: Bearer wc_..." \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Production alert handler",
    "url": "https://api.yourapp.com/webhooks/whocomply",
    "event_types": ["alert.created", "alert.resolved", "alert.dismissed"]
  }'

Response (201)

{
  "status": "ok",
  "data": {
    "endpoint": {
      "id": "wh-a1b2c3d4-...",
      "tenant_id": "...",
      "label": "Production alert handler",
      "url": "https://api.yourapp.com/webhooks/whocomply",
      "event_types": ["alert.created", "alert.resolved", "alert.dismissed"],
      "status": "active",
      "created_at": "2026-04-17T08:00:00Z",
      "updated_at": "2026-04-17T08:00:00Z"
    },
    "signing_secret": "whsec_k1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0"
  }
}

GET/tenants/{slug}/webhooks

List endpoints

Returns all webhook endpoints for the tenant. The signing secret is never returned here.

Request

GET
/tenants/{slug}/webhooks
curl "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks" \
  -H "Authorization: Bearer wc_..."

Response

{
  "status": "ok",
  "data": [
    {
      "id": "wh-a1b2c3d4-...",
      "tenant_id": "...",
      "label": "Production alert handler",
      "url": "https://api.yourapp.com/webhooks/whocomply",
      "event_types": ["alert.created", "alert.resolved", "alert.dismissed"],
      "status": "active",
      "created_at": "2026-04-17T08:00:00Z",
      "updated_at": "2026-04-17T08:00:00Z"
    }
  ]
}

GET/tenants/{slug}/webhooks/{id}

Get endpoint

Returns a single webhook endpoint by ID.

Request

GET
/tenants/{slug}/webhooks/{id}
curl "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks/{webhook_id}" \
  -H "Authorization: Bearer wc_..."

Response

{
  "status": "ok",
  "data": {
    "id": "wh-a1b2c3d4-...",
    "tenant_id": "...",
    "label": "Production alert handler",
    "url": "https://api.yourapp.com/webhooks/whocomply",
    "event_types": ["alert.created", "alert.resolved", "alert.dismissed"],
    "status": "active",
    "created_at": "2026-04-17T08:00:00Z",
    "updated_at": "2026-04-17T08:00:00Z"
  }
}

DELETE/tenants/{slug}/webhooks/{id}

Delete endpoint

Remove a webhook endpoint. Returns 204 No Content on success. Already-queued deliveries to this endpoint stop firing immediately.

Request

DELETE
/tenants/{slug}/webhooks/{id}
curl -X DELETE "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks/{id}" \
  -H "Authorization: Bearer wc_..."

POST/tenants/{slug}/webhooks/{id}/rotate-secret

Rotate secret

Generate a new signing secret. The old secret is invalidated immediately. Update your verification code on the receiver before rotating.

Request

POST
/tenants/{slug}/webhooks/{id}/rotate-secret
curl -X POST "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks/{id}/rotate-secret" \
  -H "Authorization: Bearer wc_..."

Response

{
  "status": "ok",
  "data": {
    "endpoint": {
      "id": "wh-a1b2c3d4-...",
      "label": "Production alert handler",
      "url": "https://api.yourapp.com/webhooks/whocomply",
      "event_types": ["alert.created", "alert.resolved", "alert.dismissed"],
      "status": "active"
    },
    "signing_secret": "whsec_n3w5ecr3tk3yv4lu3h3r3a1b2c3d4e5f6g7h8i9j0"
  }
}

GET/tenants/{slug}/webhooks/{id}/deliveries

List deliveries

Returns recent delivery attempts for a webhook endpoint. Useful for debugging failed deliveries.

  • Name
    limit
    Type
    integer
    Description

    Number of results (1-100, default 20).

  • Name
    offset
    Type
    integer
    Description

    Pagination offset (default 0).

Request

GET
/tenants/{slug}/webhooks/{id}/deliveries
curl "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks/{id}/deliveries?limit=10" \
  -H "Authorization: Bearer wc_..."

Response

{
  "status": "ok",
  "data": [
    {
      "id": "del-001",
      "endpoint_id": "wh-a1b2c3d4-...",
      "event_type": "alert.created",
      "event_id": "evt-...",
      "status": "succeeded",
      "response_status": 200,
      "attempt_count": 1,
      "delivered_at": "2026-04-17T08:14:34Z"
    },
    {
      "id": "del-002",
      "endpoint_id": "wh-a1b2c3d4-...",
      "event_type": "alert.created",
      "event_id": "evt-...",
      "status": "failed",
      "response_status": 500,
      "attempt_count": 3,
      "next_retry_at": "2026-04-17T09:00:00Z",
      "delivered_at": null
    }
  ]
}

Event types

Subscribe only to the events you need.

EventFired when
alert.createdA new compliance alert is generated by the monitoring engine, the screening engine, or an activated provider plugin.
alert.resolvedA compliance officer marks an alert resolved (true positive handled, escalated, or otherwise closed with a positive outcome).
alert.dismissedA compliance officer marks an alert dismissed (false positive).

Each delivery is a POST request with this JSON body shape:

{
  "event": "alert.created",
  "event_id": "evt-...",
  "tenant_id": "...",
  "occurred_at": "2026-04-17T08:14:33Z",
  "data": {
    "id": "a1b2c3d4-...",
    "customer_id": "f1e2d3c4-...",
    "alert_type": "transaction_flag",
    "severity": "high",
    "title": "High Value Transaction over 5M",
    "trigger_ref": "TXN-20260417-001"
  }
}

For alerts produced by activated provider plugins, data.metadata carries provenance (plugin name, original event source, raw-payload hash). See alert provenance.


Verifying signatures

Every webhook delivery includes these headers:

HeaderDescription
X-Whocomply-EventThe event type (e.g. alert.created)
X-Whocomply-Event-IDUUID of the event
X-Whocomply-Delivery-IDUUID of this specific delivery attempt
X-Whocomply-Signaturet={timestamp},v1={hmac-sha256-hex}

The signature is computed as HMAC-SHA-256(secret, "{timestamp}.{raw_body}"). Always verify before processing.

Verification steps

  1. Read X-Whocomply-Signature. Split on , to get t=<timestamp> and v1=<digest>.
  2. Concatenate <timestamp> + . + raw request body bytes.
  3. Compute HMAC-SHA-256 of the concatenation using your endpoint's signing_secret.
  4. Constant-time-compare your hex digest with v1. Reject if they differ.
  5. Optionally reject when <timestamp> is older than 5 minutes (prevents replay).

See the Verifying webhook signatures guide for a deeper walkthrough.

Verify signature

const crypto = require('crypto')

function verifyWebhook(rawBody, header, secret) {
  const [tPart, v1Part] = header.split(',')
  const timestamp = tPart.replace('t=', '')
  const received = v1Part.replace('v1=', '')

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

  return crypto.timingSafeEqual(
    Buffer.from(received, 'hex'),
    Buffer.from(expected, 'hex'),
  )
}

Was this page helpful?