Webhooks
Receive real-time notifications when background document parsing completes. Instead of polling, your server gets an HTTPS POST with the full parse result.
How it works
- Add your webhook domain in Dashboard > Settings > Webhooks.
- Send a parse request with
background=trueandwebhook_url. - When processing completes, Dokai sends a
POSTto your URL with the result.
curl -X POST https://api.dokai.dev/v1/parse \
-H "Authorization: Bearer dk_live_your_api_key" \
-F "file=@document.jpg" \
-F "background=true" \
-F "webhook_url=https://hooks.example.com/dokai"Domain whitelisting
For security, webhook URLs must use HTTPS and the domain must be whitelisted in your Dashboard. Go to Settings > Webhooks and add your domain (e.g. hooks.example.com).
Requests to non-whitelisted domains will be rejected with a 422 error and code webhook_domain_not_allowed.
Webhook payload
The webhook delivers a POST request with Content-Type: application/json. The body contains the same result structure as a synchronous parse response.
{
"event": "parse.completed",
"id": "parse_abc123",
"status": "success",
"document_type": "identity_card",
"document_brand": "mykad",
"data": {
"full_name": "AHMAD BIN ABDULLAH",
"id_number": "880503-14-5234",
"date_of_birth": "1988-05-03",
"gender": "M",
"nationality": "WARGANEGARA",
"address": "42 JALAN MERDEKA, TAMAN SENTOSA, 50100 KUALA LUMPUR"
},
"extra": {
"religion": "ISLAM",
"race": "MELAYU"
},
"metadata": {
"processing_time_ms": 1420,
"ocr_confidence": 0.95,
"extraction_confidence": 0.97
},
"created_at": "2026-03-06T10:30:00Z"
}If the parse fails, the payload includes the error details:
{
"event": "parse.failed",
"id": "parse_xyz789",
"status": "failed",
"error": {
"code": "processing_failed",
"message": "Document could not be processed. Image quality too low."
},
"created_at": "2026-03-06T10:31:00Z"
}Signature verification
Every webhook request includes an X-Dokai-Signature header. Verify this signature to ensure the request came from Dokai and was not tampered with.
| Header | Description |
|---|---|
X-Dokai-Signature | HMAC-SHA256 hex digest of the request body, signed with your webhook secret |
X-Dokai-Timestamp | Unix timestamp of when the webhook was sent (use for replay protection) |
Your webhook secret is available in Dashboard > Settings > Webhooks. The signature is computed as:
signature = HMAC-SHA256(webhook_secret, request_body)Verification examples
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
# In your webhook handler:
# body = request.body (raw bytes)
# signature = request.headers["X-Dokai-Signature"]
# secret = "whsec_your_webhook_secret"
# if not verify_webhook(body, signature, secret):
# return Response(status_code=401)Retry policy
If your endpoint returns a non-2xx status code or times out (30 seconds), Dokai retries the delivery with exponential backoff.
| Attempt | Delay | Notes |
|---|---|---|
| 1st retry | 1 minute | After initial failure |
| 2nd retry | 5 minutes | Exponential backoff |
| 3rd retry | 30 minutes | Final attempt |
After 3 failed attempts, the delivery is marked as failed. You can view delivery history in the Dashboard under Parse History > result detail flyout.
Best practices
- ✓Always verify signatures before processing webhook payloads to prevent spoofing.
- ✓Return 200 quickly — process the payload asynchronously (e.g. queue a job) to avoid timeouts.
- ✓Handle duplicates — use the
idfield to deduplicate, since retries may deliver the same event twice. - ✓Check the timestamp — reject events older than 5 minutes to prevent replay attacks.
- ✓Use HTTPS only — plaintext HTTP webhook URLs are rejected.
Testing locally
Use a tunneling tool to expose your local server to the internet for webhook testing.
# Start a tunnel to your local server
ngrok http 3000
# Use the generated URL as your webhook_url:
# https://abc123.ngrok-free.app/webhooks/dokaiRemember to add the tunnel domain (e.g. abc123.ngrok-free.app) to your Dashboard webhook domain whitelist.
Next steps
- Parse Document — full parse endpoint reference
- Error Handling — webhook-related error codes
- Retrieve Result — poll for results as an alternative to webhooks