render.* (rendering lifecycle), document.* (engagement on a
delivered document), signature.* (e-signature lifecycle), and form.* (collect-only embed
fill submissions).
Looking to trigger a render from an external system instead? That’s the inbound webhook
(
POST /v1/hooks/:token) — see Inbound webhook.Configure a subscription
Webhooks are configured per project in the dashboard (Project → Webhooks): aurl, a signing
secret, the list of events to subscribe to, and an active flag. There is no API to register
subscriptions programmatically yet (roadmap) — use the dashboard. A subscription only receives the
events it is subscribed to; run several subscriptions (each with its own secret) to split traffic by
family.
Event triggers
Every event name isfamily.action.
render.*
document.*
Fired as recipients interact with a render you’ve shared or emailed (see
Sharing). Recipient-facing events (viewed/downloaded/printed/email_opened)
are de-duplicated within a 5-minute window per (share, event, source IP).
signature.*
Fired as a signature request progresses. Payloads are provider-neutral — Craftkit’s own status
vocabulary only, never a third-party event type or id.
form.*
Emitted by embed fill sessions minted with form.captureMode: "collect" (see
Embed sessions). In collect-only mode the form is pure
data-collection infrastructure: on submit Craftkit validates the field data and delivers it to you
without creating a render or storing the field data. You persist it and request the render as a
separate follow-up call.
At-least-once, no replay. The collect submit returns
202 only after the form.submitted
delivery is durably enqueued — but a 503/500 from the submit endpoint may still have enqueued
(and delivered) it, so treat form.submitted as at-least-once. A submit retry mints a new
delivery, so x-craftkit-delivery-id won’t match across a resubmit — dedupe on the payload’s
sessionId (stable per fill), not on the delivery id alone. And because an abandoned delivery
is purged it cannot be replayed: keep your endpoint reachable and rely on your own retry.Delivery contract
CraftkitPOSTs a JSON body to your url with these headers:
Every body is a JSON object whose first field is
event; the rest depends on the family.
- render.*
- document.*
- signature.*
- form.*
Verify the signature
Compute the HMAC over the raw request body and compare againstx-craftkit-signature (raw hex,
no sha256= prefix) in constant time. Reject on mismatch. Optionally reject deliveries whose
x-craftkit-timestamp is outside a tolerance window to blunt replays.
Retries
Failed deliveries (non-2xx, timeout, or error) retry up to 6 attempts; after that the delivery is markedabandoned. Respond 2xx quickly (the budget is ~15s per attempt) and do heavy work
asynchronously. De-duplicate on x-craftkit-delivery-id since a delivery may arrive more than once.
downloadUrl in render.* payloads follows the same durability rules as elsewhere — a permanent
public URL, or null if no public bucket is configured. See
Render lifecycle.