Skip to main content

DAN AI — Webhooks (Event Notifications)

Webhooks are not an export format — they are event callbacks. When an extraction finishes, DAN AI fires an extraction.completed event to every registered endpoint.

There are two ways to receive completion events:

  • Account-wide webhooks — managed under Developer → Webhooks. Fire on every completed extraction in the account.
  • Per-request callback_url — passed as a form field on the REST API call. Fires only for that one extraction.

Setting up an account-wide webhook​

  1. Open Developer → Webhooks in the dashboard.
  2. Click Add Webhook.
  3. Enter the destination URL (must be HTTPS).
  4. Tick the events you care about (currently extraction.completed).
  5. Save. DAN AI generates a signing secret — copy it now, you'll need it to verify deliveries.

The endpoint must respond with a 2xx status within 15 seconds. Anything else is treated as a failure and retried (see Retries and ordering).

Payload shape​

{
"event": "extraction.completed",
"timestamp": "2026-04-15T14:32:18Z",
"data": {
"document_id": "42",
"reference": "DAN-00042",
"doc_type": "invoice",
"state": "done",
"fields": {
"invoice_number": "INV-0001",
"vendor_name": "Acme Corp",
"invoice_date": "2026-04-15",
"total_amount": 1250,
"currency": "USD"
},
"lines": [
{ "description": "Consulting", "quantity": 10, "price_unit": 125 }
],
"confidence": { "invoice_number": 95, "total_amount": 97 },
"source": "dashboard"
}
}
KeyNotes
eventAlways extraction.completed today; more event types may be added.
timestampISO-8601 UTC time DAN AI sent the event.
data.document_id / data.referenceStable identifiers. Use document_id for API calls and reference for human-readable references.
data.stateEither done or error. Webhooks fire on both — check state before acting.
data.sourceWhere the document came from: dashboard, inbox, or api.
data.fields / data.lines / data.confidenceSame shape as the REST API response.

Verifying signatures​

Every delivery includes two headers:

X-DAN-Signature: t=1716203400,v1=<hex hmac>
X-DAN-Delivery: <unique delivery id>

To verify:

  1. Take the raw request body (the JSON as bytes, not parsed).
  2. Compute HMAC-SHA256(secret, "<timestamp>." + body).
  3. Compare the hex digest to the v1 value in X-DAN-Signature using a constant-time compare.
  4. Reject if the timestamp is more than 5 minutes old (prevents replay).

Node.js example​

const crypto = require('crypto');

function verify(req, secret) {
const sig = req.headers['x-dan-signature'] || '';
const m = sig.match(/^t=(\d+),v1=([a-f0-9]+)$/);
if (!m) return false;
const [, t, v1] = m;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${req.rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

The raw body matters — most frameworks parse JSON before you see it, which mangles whitespace and breaks the HMAC. Capture req.rawBody (or the equivalent buffer) before parsing.

Retries and ordering​

DAN AI retries failed deliveries with exponential backoff:

AttemptDelay
1 (initial)immediate
230 seconds
32 minutes
410 minutes
530 minutes
6 (final)2 hours

A delivery is considered failed if your endpoint returns non-2xx, times out (>15 s), or the TLS handshake fails. After the 6th attempt the delivery is marked permanently failed and surfaced under Developer → Webhooks → Deliveries.

Webhook deliveries are not ordered — a fast extraction triggered after a slow one can land first. If you need ordering, use the timestamp field on the payload.

Inspecting deliveries​

The Developer → Webhooks → Deliveries page shows the last few hundred attempts per endpoint, with the response status, latency, and full request/response bodies. Use it to debug a webhook that's failing in production.

A common pattern: copy a failing delivery's body and replay it against your local server (curl -X POST localhost:3000/dan-webhook -H 'content-type: application/json' --data @body.json).

Per-request callback_url​

If you only want a callback for one specific extraction (rather than every extraction in the account), pass callback_url on the API call:

curl -X POST "https://dan.sdlccorp.com/api/external/v1/extract?async=true" \
-H "X-API-Key: dan_live_xxxxxxxxxxxxxxxxxxxx" \
-F "document=@invoice.pdf" \
-F "doc_type=invoice" \
-F "callback_url=https://your-app.example.com/dan-callback"

The callback uses the same payload shape and signature scheme as account-wide webhooks. The signing secret is the same as your account's primary webhook secret.