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.
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
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"
}
}
List endpoints
Returns all webhook endpoints for the tenant. The signing secret is never returned here.
Request
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 endpoint
Returns a single webhook endpoint by ID.
Request
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 endpoint
Remove a webhook endpoint. Returns 204 No Content on success. Already-queued deliveries to this endpoint stop firing immediately.
Request
curl -X DELETE "https://api.whocomply.com/api/v1/tenants/kuda-mfb/webhooks/{id}" \
-H "Authorization: Bearer wc_..."
Rotate secret
Generate a new signing secret. The old secret is invalidated immediately. Update your verification code on the receiver before rotating.
Request
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"
}
}
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
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.
| Event | Fired when |
|---|---|
alert.created | A new compliance alert is generated by the monitoring engine, the screening engine, or an activated provider plugin. |
alert.resolved | A compliance officer marks an alert resolved (true positive handled, escalated, or otherwise closed with a positive outcome). |
alert.dismissed | A 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:
| Header | Description |
|---|---|
X-Whocomply-Event | The event type (e.g. alert.created) |
X-Whocomply-Event-ID | UUID of the event |
X-Whocomply-Delivery-ID | UUID of this specific delivery attempt |
X-Whocomply-Signature | t={timestamp},v1={hmac-sha256-hex} |
The signature is computed as HMAC-SHA-256(secret, "{timestamp}.{raw_body}"). Always verify before processing.
Verification steps
- Read
X-Whocomply-Signature. Split on,to gett=<timestamp>andv1=<digest>. - Concatenate
<timestamp>+.+ raw request body bytes. - Compute HMAC-SHA-256 of the concatenation using your endpoint's
signing_secret. - Constant-time-compare your hex digest with
v1. Reject if they differ. - 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'),
)
}