Webhooks

When a verification completes, Ad-Hoc Verify POSTs the results to a URL of your choosing in real time — no polling required.

When Webhooks Fire

A webhook is sent whenever a verification reaches a terminal state. There are two states that trigger delivery:

completed
The end user finished the full verification flow — license scan, liveness check, and face comparison. Results are final. This is the normal successful outcome.
manual_review
The face comparison was inconclusive and the end user submitted their images for human review. The webhook fires immediately, before any human reviews the case. The payload includes manual_review_requested: true in the verification object.

Verifications that expire without completion, or that are deleted, do not trigger a webhook.

i
If no webhook URL is configured on a verification or template, delivery is skipped entirely. Results are still available on the dashboard and via the GET /verification endpoint.

Payload Shape

The webhook is a POST request with Content-Type: application/json. The body always contains request_id and verification; the remaining fields are present only when they apply.

{ "request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "external_id": "PO12345", "verification": { "license_appears_real": true, "license_expired": false, "over_21": true, "over_18": true, "webcam_appears_live": true, "webcam_face_matches_license_photo": true, "face_match_score": "definite_match", "face_match_confidence": 0.84, "face_match_cosine_similarity": 0.91 }, "license_data": { "first_names": "JOHN", "last_name": "DOE", "dob": "1990-01-15" }, "phone_verification": { "phone_areacode": "415", "last_3_digits": "789", "phone_verified": true, "line_type": "mobile", "lookup": { "phone_number": "+14155550789", "caller_name": "John Doe", "carrier_name": "T-Mobile" } } }

Core Fields

request_id
UUID of the verification — matches the id returned when you created the request
external_id
Your reference identifier, if you provided one when creating the verification. Omitted when not set.
verification
Core verification results — always present (see table below)
license_data
PII fields extracted from the license. Only the fields you requested are included. Empty object {} when no fields were requested. An encrypted envelope or placeholder message when encryption is enabled.
phone_verification
Phone verification result. Present only when phone verification was enabled on the request.
address_verification
Address deliverability and name-match indicators. Present only when address verification was requested and could run. See Indicators.
location_consistency
Whether the end user's network location is consistent with their license address. Present only when the check was requested and could run. See Indicators.
license_photo
Cropped face image from the driver's license. Present only when include_license_photo was enabled. Base64-encoded JPEG, or an encrypted envelope when E2E encryption is active.
encryption
Encryption metadata (algorithm, wrapped key). Present only when custom-key E2E encryption is active — needed to decrypt license_data, phone_verification.lookup, and license_photo.
encryption_key
Which of your public keys the data was encrypted with, so you know which private key to use. Present only with custom-key E2E encryption. Contains name (the label you gave the key), fingerprint_ssh (SHA256:…, as printed by ssh-keygen -l) and fingerprint_sha256 (hex SHA-256 of the DER-encoded public key, as produced by openssl).

Verification Object

license_appears_real
Whether the scanned document appears to be a genuine ID (edge detection, proportions, face presence)
license_expired
Whether the license expiration date has passed
over_21
Whether the person is 21 or older based on the date of birth on the license
over_18
Whether the person is 18 or older based on the date of birth on the license
webcam_appears_live
Whether the selfie passed liveness detection (blink + head movement challenges)
webcam_face_matches_license_photo
true, false, or "undetermined". "undetermined" means the match was inconclusive and manual review was requested.
face_match_score
Categorical result: definite_match, likely_match, possible_match, or no_match. See Face Match Scoring.
face_match_confidence
Numeric confidence score (0–1). Supplementary — use face_match_score for business logic.
face_match_cosine_similarity
Cosine similarity between the license and selfie embeddings (−1 to 1). Supplementary.
ip_found / is_proxy
Whether the submitting connection was found in IP intelligence data, and whether it came through a known proxy or anonymizing service. proxy_type and proxy_description are added when is_proxy is true. A proxy is not proof of fraud; see Data Fields & Indicators.
manual_review_requested
Present and true when the end user requested manual review after a failed face match.

Custom Headers

You can attach arbitrary HTTP headers to webhook deliveries — useful for authenticating the request on your server so you know it's genuinely coming from Ad-Hoc Verify.

Configure headers when creating a template or an individual verification request. Headers are stored as a JSON object and sent with every delivery to that verification's webhook URL:

{ "webhook_url": "https://api.yourcompany.com/webhooks/verify", "webhook_headers": { "X-API-Key": "your-secret-key", "X-Source": "ad-hoc-verify" } }

On your server, validate the header value on every inbound webhook request before processing the payload. This is the primary mechanism for confirming the request originated from Ad-Hoc Verify.

!
Webhook headers are stored server-side and are never exposed to end users. Treat the values like passwords — rotate them if you believe they've been compromised.

Retry Logic

If your endpoint returns a non-2xx status code, or the request times out, Ad-Hoc Verify retries automatically:

Attempt
Delay
1 (initial)
Immediate
2
5 seconds after failure
3
15 seconds after failure

After 3 failed attempts the webhook_status is set to "failed". You can trigger a manual retry from the Requests page on the dashboard, or call the retry endpoint via API. Results are always retrievable via GET /verification regardless of webhook delivery status.

Security Recommendations

  • Use a secret header. Set a webhook_headers value like X-Webhook-Secret and validate it on every request.
  • Require HTTPS. All webhook URLs must use https://. Plaintext HTTP is not supported.
  • Respond quickly. Return a 2xx as soon as you receive the payload and process it asynchronously. Long-running responses risk timeouts and unnecessary retries.
  • Treat request_id as idempotent. Retries deliver the same payload. Check whether you've already processed a given request_id before acting on it.
  • Use encryption for sensitive data. When collecting PII, enable E2E encryption so the payload arriving at your webhook is already encrypted and cannot be read in transit or by intermediaries.

Ready to build? Head to the API reference for the full endpoint documentation.

API Reference →