Webhooks
Receive report, payment and monitoring events instead of polling.
Reports run asynchronously. Webhooks are how you find out they finished.
Configure a destination
POST /api/v1/webhooks/config
replaces the configuration wholesale — send the complete enabledEvents list,
not a change.
{
"url": "https://example.com/hooks/verifisha",
"enabled": true,
"enabledEvents": ["report.ready", "report.failed", "payment.paid"],
"secret": "whsec_replace_with_a_long_random_value"
}The secret is yours to choose, and it is write-only: it is never returned by
the API, so keep it in your own secret store.
Events
| Event | Fires when |
|---|---|
report.created | A report is created. |
report.ready | Every check in a report has finished. |
report.failed | A report could not complete. |
check.completed | One check finished — useful for progressive UI. |
check.failed | One check could not complete. |
reference.submitted | A referee submitted their answers. |
monitoring.alert.created | A monitoring run raised an alert. |
payment.paid | A candidate or third-party payment completed. |
Payloads
Each delivery is a JSON object with the event name, when it was raised, and the
event's data:
| Field | Description |
|---|---|
event | The event type, e.g. check.completed. Branch on this. |
timestamp | When the event was raised, ISO 8601 UTC. |
data | The event payload, shaped by event. |
check.completed
{
"event": "check.completed",
"timestamp": "2026-09-19T18:42:11.218Z",
"data": {
"workspaceId": "7f000001-a0ae-1000-81a0-ae8e05830000",
"report": {
"id": "7f000001-a0ae-1d9d-81a0-ae8e05830014",
"workspaceId": "7f000001-a0ae-1000-81a0-ae8e05830000",
"status": "PROCESSING",
"createdAt": "2026-09-19T18:41:58.104Z",
"selfVerification": false,
"paymentMode": "SELF",
"consentStatus": "APPROVED",
"checksRequested": ["IDENTITY"],
"checkStack": ["IDENTITY"]
},
"subject": {
"id": "7f000001-a0ae-18be-81a0-ae8e00000000",
"type": "INDIVIDUAL"
},
"check": {
"id": "7f000001-a0ae-2222-81a0-ae8e05830014",
"resultId": "7f000001-a0ae-2222-81a0-ae8e05830014",
"definitionId": "7f000001-a077-1c27-81a0-774c84700000",
"type": "IDENTITY",
"name": "IDENTITY",
"displayName": "ID verification",
"description": "Verify a Kenyan national ID.",
"category": "Identity",
"status": "COMPLETED",
"riskLevel": "LOW",
"riskScore": 12,
"summary": "Identity verified successfully",
"flags": [],
"details": {
"matched": true,
"idNumber": "28647415",
"dateOfBirth": "1995-04-12"
},
"executedAt": "2026-09-19T18:42:10.944Z",
"durationMs": 1280,
"reused": false
},
"reportId": "7f000001-a0ae-1d9d-81a0-ae8e05830014",
"checkId": "7f000001-a0ae-2222-81a0-ae8e05830014",
"checkResultId": "7f000001-a0ae-2222-81a0-ae8e05830014",
"checkType": "IDENTITY",
"checkName": "IDENTITY",
"checkDefinitionId": "7f000001-a077-1c27-81a0-774c84700000",
"status": "COMPLETED",
"riskLevel": "LOW",
"riskScore": 12
}
}data carries the report the check belongs to, the subject, and the check
result:
| Field | Description |
|---|---|
data.workspaceId | Your workspace. |
data.report | The report the check belongs to — its id, status, paymentMode, consentStatus, and the checks it requested. Fetch the report for the full record. |
data.subject | The subject's id and type (INDIVIDUAL or COMPANY). |
data.check.resultId | This check result. Same value as data.check.id. |
data.check.name | The check name, as used in checksRequested. |
data.check.displayName | Human-readable name, safe to show in your own UI. |
data.check.status | Execution status of the check. |
data.check.riskLevel | LOW, MEDIUM, HIGH or UNKNOWN. |
data.check.riskScore | 0–100. |
data.check.summary | One-line outcome, written for people. |
data.check.flags | Adverse findings raised by the check. Empty when there are none. |
data.check.details | The provider result. Its fields depend on the check — see the check definition's outputFields. |
data.check.executedAt | When the check ran. |
data.check.durationMs | How long the check took. |
data.check.reused | true when an earlier, still-fresh result was reused instead of running the check again. |
The flat fields at the end of data — reportId, checkId, checkResultId,
checkName, checkType, checkDefinitionId, status, riskLevel,
riskScore — repeat values from data.report and data.check for convenience.
data.check.details can contain the subject's personal data — here an ID
number and date of birth. Accept deliveries over HTTPS only, and treat stored
payloads with the same care as the report itself.
Handling redelivery
A delivery that is not acknowledged is retried, so your endpoint can receive the
same event more than once. Make your handler idempotent — for example, skip an
event whose event and data.checkResultId you have already processed.
Verifying deliveries
Deliveries are signed with the secret you configured.
Confirm the signature header and algorithm with Verifisha before relying on verification in production.
Verify against the raw body bytes as received, before parsing. Re-serialising the JSON first will not match.
Testing and debugging
- Send a test event to exercise your handler before real traffic arrives.
- Delivery logs record each attempt
with the HTTP status your endpoint returned and whether it is
RETRYING. That is the first place to look when an event seems to have been missed.
Inbound payment webhooks are not yours to call
POST /api/v1/webhooks/paystack receives events from the payment provider,
authenticated by the provider's own signature. It is unrelated to your outbound
configuration and is not a customer API.