Webhooks
Webhooks let CoreVerify notify your application in real time when events occur — such as when a background check completes or a candidate submits their information. Instead of polling the API, your server receives an HTTP POST request the moment something changes.
How Webhooks Work
When an event occurs, CoreVerify sends an HTTP POST request to your configured endpoint URL with a JSON payload describing the event. Your endpoint must respond with a 2xx status code within 15 seconds to acknowledge receipt. If your endpoint fails or times out, CoreVerify retries the delivery with exponential backoff — 5 attempts spread over roughly 15 minutes. After that you can replay any delivery by hand from the dashboard.
Configuring Endpoints
Webhook endpoints live in one of two places, depending on what they are for:
- Document verification — Account → Third party integrations → your integration → Webhooks. These events belong to a specific external application, so they are configured with it and delivered only to its endpoints.
- Account-level events — Account → Developer settings → Webhooks.
For each endpoint you can:
- Set the URL to receive events, and change it later without losing history
- Choose which event types to subscribe to
- Set the retry policy — attempts, backoff and request timeout
- Pause deliveries without deleting the endpoint
- View delivery history, inspect payloads, and replay failed deliveries
- Roll the signing secret for the endpoint
An integration’s events reach that integration’s endpoints and nowhere else, so one external application never sees another’s results — and an account-level endpoint does not receive a duplicate copy of every document event.
Event Types
| Event | Description |
|---|---|
| document_verification.completed | A submitted document was confirmed by its issuing source |
| document_verification.failed | A submitted document could not be verified, or every attempt was exhausted |
| document_verification.recorded | A document was captured and stored, but no verification source is configured for it — so there was nothing to check it against. Not a failure: the extracted and confirmed fields are on the payload, and verified is false. |
| report.completed | All checks in the report have finished processingcoming soon |
| candidate.completed | The candidate completed their information submissioncoming soon |
The event list in the dashboard is the authoritative one — an endpoint can only subscribe to events CoreVerify actually emits.
Webhook Payload
All webhook events share the same top-level structure. The data field contains the full object at the time the event occurred.
POST https://your-app.com/webhooks/coreverify
Content-Type: application/json
CoreVerify-Signature: t=1710590400,v1=abc123def456...
{
"id": "evt_9ZqRwMcLpKnY",
"type": "document_verification.completed",
"created": "2024-03-16T09:15:00Z",
"data": {
"verificationId": "dvf_3FpQwLvMcYnR",
"externalId": "nurse-10432",
"integrationId": "int_7KmPxTvNqWzB",
"sessionId": "V1v3kQ8pN2sL…",
"sessionReference": "nurse-10432",
"documentConfigSlug": "rn-license",
"checkTypeSlug": "rn-license-ca",
"state": "CA",
"status": "VERIFIED",
"verified": true,
"formData": {
"license_number": "RN-884120",
"expiration_date": "2026-06-12"
},
"verificationFileUploads": [
"https://…/screenshot.png"
],
"errorMessage": null,
"attemptCount": 1,
"metadata": {
"entity_id": 84213,
"certificate_id": 77120
},
"createdAt": "2024-03-16T09:14:12Z",
"completedAt": "2024-03-16T09:15:00Z"
}
}metadata is whatever you supplied when the verification was created — CoreVerify never interprets it, so put whatever identifiers you need to match the result to your own record.externalId is your own reference, and you can also poll GET /v1/document-verifications?externalId=… if you would rather not wait for the callback.
Verifying Signatures
Each webhook request includes a CoreVerify-Signature header that you should validate to confirm the request came from CoreVerify and was not tampered with. Signatures are computed using HMAC-SHA256.
Signature Header Format
CoreVerify-Signature: t=1710590400,v1=abc123def456...The header contains a Unix timestamp (t) and one or more signatures (v1). To verify:
- Extract the timestamp and signature(s) from the header
- Construct the signed payload string:
{timestamp}.{raw_body} - Compute HMAC-SHA256 of the signed payload using your endpoint's signing secret
- Compare the result to the
v1signature — they must match exactly - Check that the timestamp is within 5 minutes of the current time to prevent replay attacks
Verification Example (Node.js)
import crypto from 'crypto'
function verifyWebhookSignature(
payload: string,
header: string,
secret: string
): boolean {
const parts = Object.fromEntries(
header.split(',').map(p => p.split('='))
)
const timestamp = parts['t']
const signature = parts['v1']
if (!timestamp || !signature) return false
// Reject events older than 5 minutes
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)
if (age > 300) return false
const signed = `${timestamp}.${payload}`
const expected = crypto
.createHmac('sha256', secret)
.update(signed)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
)
}crypto.timingSafeEqual) when comparing signatures to prevent timing attacks.