On this page
Webhooks
The async modes of the API (async=true emissions, batches) produce the PDF in a queue, a few seconds after the 201/202 response. Without webhooks you track progress by polling GET /documents/{public_id}/pdf or GET /batches/{batch_id}. With a webhook, DokKraft notifies you as soon as the state changes — polling remains the source of truth, the webhook is an optimization (a delivery can be lost: your handler must tolerate receiving nothing). Document events also cover the lifecycle of every document in the workspace, emitted through the API as well as the UI, a batch or an automation.
Declare an endpoint
Webhooks are included from the Pro plan upward (they complement the async API) — Free and Starter workspaces get read-only access. In DokKraft: Settings → Webhooks. Each workspace can declare up to 10 endpoints; an endpoint subscribes to a subset of the events below. At creation, a secret (whsec_…) is shown once — store it immediately. It can be rotated at any time (the old one stops signing immediately).
Events
One extra event, ping, is the test delivery: send one at any time with the endpoint's "Test" button (or right after creating it) to validate your URL and signature verification without emitting a real document. Its payload contains a message and the endpoint id.
| Event | Emitted when | Data |
|---|---|---|
ping |
Test delivery ("Test" button in the UI) | message, endpoint |
document.emitted |
A document was emitted (number reserved + frozen snapshot) — API, UI, batch or automation | document: publicId, name, number, status, pdfUrl |
document.pdf_ready |
A document's PDF rendering finished and was stored | same, status is ready |
document.pdf_failed |
Rendering definitively failed (attempts exhausted) | same + errorCode, errorMessage |
document.cancelled |
A document was cancelled (terminal status, neutralised document) | same, status is annule |
batch.completed |
A batch reached its final state (completed, partial or failed) |
batch: publicId, status, counts (emitted, failed), completedAt |
automation.run.completed |
An automation processed an occurrence (emission/delivery finished) | automationId, runId, documentId, status (emitted|sent), mode (notify|email|none) |
Document, batch and automation identifiers are the same as in the REST API: fetch details with GET /documents/{publicId} or GET /batches/{publicId}. The PDF itself is never in the payload — download it through pdfUrl.
The received body is a common envelope:
{
"id": "0b6e0f2e-…",
"event": "document.pdf_ready",
"createdAt": "2026-09-22T14:03:11+00:00",
"data": {
"document": {
"publicId": "doc_…",
"name": "Invoice ABC",
"number": "INV-2026-0042",
"status": "ready",
"pdfUrl": "https://dokkraft.blocsland.com/api/v1/documents/doc_…/pdf"
}
}
}
Example envelope for automation.run.completed:
{
"id": "7c1d94a0-…",
"event": "automation.run.completed",
"createdAt": "2026-09-30T09:00:04+00:00",
"data": {
"automationId": "e3a1c07e-…",
"runId": 42,
"documentId": "9f2b6d41-…",
"status": "sent",
"mode": "email"
}
}
status reflects the run's outcome (emitted for "notification" delivery, sent after an email send); mode echoes the channel declared on the rule (notify, email or none).
Verify the signature
Each delivery carries the X-DokKraft-Event, X-DokKraft-Delivery and X-DokKraft-Signature headers, formatted t=<timestamp>,v1=<hmac>. The HMAC is an HMAC-SHA256 of "{t}.{raw_body}" keyed with the endpoint secret — sign the exact body received (not a re-encoded JSON), then compare both v1 values with a constant-time check.
Reference verification (PHP):
$parts = [];
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_DOKKRAFT_SIGNATURE']), $parts);
$expected = hash_hmac('sha256', $parts['t'] . '.' . file_get_contents('php://input'), $secret);
if (! hash_equals($expected, $parts['v1'] ?? '')) {
http_response_code(401); // invalid signature — not a DokKraft delivery
exit;
}
if (abs(time() - (int) $parts['t']) > 300) {
http_response_code(401); // stale delivery — replay or ignore
exit;
}
Delivery and retries
- A delivery answering 2xx is considered complete; any other response (or more than 5 s) triggers a retry.
- Retries: 1 min, 10 min, 1 h, 6 h (5 attempts total), then the delivery is abandoned. It stays visible and replayable in Settings → Webhooks.
- Idempotence: the same event is never delivered twice to the same endpoint. Conversely, your handler can receive two distinct events in any order — filter on
document.statusif needed. - Daily cap: deliveries are limited to 1,000 per day per workspace (all endpoints combined). Beyond that, no more deliveries are created for the day — events emitted afterwards do not reach you; the counter resets the next day.
- Disabling an endpoint (toggle) cuts its deliveries immediately, without losing its history.
- Automatic shutdown: after 20 consecutive abandoned deliveries (an URL that never answers 2xx), the endpoint is switched off automatically to preserve the delivery quota — the UI marks it "Off (failures)" and re-enabling it is one toggle click.