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.
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:
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 →