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

  1. Add your webhook domain in Dashboard > Settings > Webhooks.
  2. Send a parse request with background=true and webhook_url.
  3. When processing completes, Dokai sends a POST to your URL with the result.
cURL — parse with webhook
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.

Webhook POST body
{
  "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:

Failed parse webhook
{
  "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.

HeaderDescription
X-Dokai-SignatureHMAC-SHA256 hex digest of the request body, signed with your webhook secret
X-Dokai-TimestampUnix 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 formula
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.

AttemptDelayNotes
1st retry1 minuteAfter initial failure
2nd retry5 minutesExponential backoff
3rd retry30 minutesFinal 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 id field 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.

ngrok
# Start a tunnel to your local server
ngrok http 3000

# Use the generated URL as your webhook_url:
# https://abc123.ngrok-free.app/webhooks/dokai

Remember to add the tunnel domain (e.g. abc123.ngrok-free.app) to your Dashboard webhook domain whitelist.

Next steps