Webhooks & signatures
Async delivery
Section titled “Async delivery”Add a webhook_url to any /v1/render or /v1/image request and DocJet delivers the result asynchronously:
curl -X POST https://api.docjet.dev/v1/render \ -H "Authorization: Bearer $DOCJET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"template_id":"invoice-en","data":{"client":"Acme"},"webhook_url":"https://example.com/webhooks/docjet"}'The API accepts the job immediately; when the render completes, DocJet POSTs a JSON payload to your webhook_url containing the job status and a signed download URL. Webhook URLs must be HTTPS on the default port.
Signed download URLs
Section titled “Signed download URLs”Render outputs are served via expiring, HMAC-signed URLs (.../v1/outputs/<id>?exp=...&sig=...). Anyone with the URL can download until it expires — treat URLs like the documents themselves and pass them point-to-point, not into public logs.
The X-DocJet-Signature scheme
Section titled “The X-DocJet-Signature scheme”Every webhook callback is signed so you can authenticate that it really came from DocJet before trusting it. The header:
X-DocJet-Signature: t=<unix-seconds>,v1=<hmac-sha256-hex>where:
v1 = HMAC-SHA256( "<t>" + "." + <raw request body>, webhook_secret )Your per-key webhook secret comes from:
curl https://api.docjet.dev/v1/keys/webhook-secret \ -H "Authorization: Bearer $DOCJET_API_KEY"Verification rules
Section titled “Verification rules”- Parse
tandv1from the header. - Reject if
|now - t| > 300seconds (replay protection). - Recompute the HMAC over
"<t>.<rawBody>"— the raw body string, before any JSON parsing or re-serialization. - Compare with a timing-safe comparison (
crypto.timingSafeEqual/hmac.compare_digest) — never==on hex strings.
One-call verification with the SDKs
Section titled “One-call verification with the SDKs”Both official SDKs implement the exact scheme above:
// JavaScript — npm install docjetimport { verifyWebhookSignature } from 'docjet';
if (!verifyWebhookSignature(rawBody, signatureHeader, secret)) { // reject with 401}# Python — pip install docjetfrom docjet import verify_webhook_signature
if not verify_webhook_signature(raw_body, header, secret): ... # reject with 400/401Both default to a 300-second tolerance window (configurable) and return false/False for tampered, expired, or malformed signatures.
Common mistakes
Section titled “Common mistakes”- Parsing JSON before verifying — frameworks that re-serialize the body produce different bytes; always verify the raw body string.
- Skipping the timestamp check — without it, a captured callback can be replayed forever.
- Hex comparison with
==— leaks timing information; use the SDK helpers or a constant-time compare.