Webhooks: events, delivery and retries
A webhook makes Dakoty call your server when something happens, so you do not have to poll. Dakoty sends an HTTPS POST with a JSON envelope for each event, signed so you can prove it came from Dakoty. This page covers the contract; to check signatures see Verifying signatures. People create webhooks by hand in the app (see the Slovak help article How to set up a webhook); tools such as Make subscribe through REST hooks.
The event envelope
Section titled “The event envelope”Every delivery body is the same envelope. id is stable per event, repeated on every retry and equal to the webhook-id header, so use it for deduplication. test is true for sample and test deliveries. data is the resource exactly as the matching GET endpoint returns it.
{ "id": "evt_3f2c0a9e5b1d4c7e8a6f2b1c9d0e3a4b", "type": "lead.created", "timestamp": "2026-10-11T08:30:12Z", "api_version": "2026-10-01", "organization": { "id": "0b5f7c1e-3c52-4d0e-9a53-6f0c1f6a2b11", "slug": "acme" }, "test": false, "data": { "id": "9a1d2c3e-4b5f-4a6b-8c7d-0e1f2a3b4c5d", "name": "Website enquiry", "status": "open", "value": 1200 }}Request headers of a delivery
Section titled “Request headers of a delivery”Each request is a POST with Content-Type: application/json and the user agent Dakoty-Webhooks/1.0. Besides the signature headers it carries webhook-id, webhook-timestamp (Unix seconds, refreshed on every attempt), X-Dakoty-Event (the event type) and X-Dakoty-Attempt (1 for the first attempt). The signature headers webhook-signature and the legacy X-Dakoty-Signature are described in Verifying signatures.
Event catalog and required scopes
Section titled “Event catalog and required scopes”Dakoty lists only events it really emits. A REST hook key needs the read scope of every event it subscribes to. The live list, with labels and descriptions, comes from GET /webhooks/events (scope webhooks:manage).
| Group | Events | Scope |
|---|---|---|
| Leads | lead.created, lead.updated, lead.archived, lead.stage_changed, lead.won, lead.lost | leads:read |
| Companies and contacts | company.created, company.updated, contact.created, contact.updated | companies:read |
| Clients | client.created, client.updated | clients:read |
| Activities | activity.created, activity.completed, activity.cancelled | activities:read |
| Tasks | task.created, task.moved, task.completed, task.overdue | tasks:read |
| Invoices and quotes | invoice.created, invoice.paid, invoice.overdue, quote.accepted, quote.rejected | invoices:read |
| Bookings | booking.created, booking.confirmed, booking.cancelled, booking.completed, booking.no_show | bookings:read |
| Products | product.created, product.updated | products:read |
| Orders | order.created, order.confirmed, order.paid, order.picking, order.packed, order.ready_for_pickup, order.shipped, order.delivered, order.cancelled, order.returned | orders:read |
| Order payments | order.payment_pending, order.payment_paid, order.payment_refunded, order.payment_failed | orders:read |
Get a sample payload
Section titled “Get a sample payload”To build a mapping before any real event exists, fetch a static sample envelope with test: true. The response is an array with one envelope. It needs a key with webhooks:manage.
curl -sS https://api.dakoty.com/api/v1/public/webhooks/events/lead.created/sample \ -H "Authorization: Bearer $DAKOTY_API_KEY"What counts as a successful delivery
Section titled “What counts as a successful delivery”Respond with any 2xx status within 10 seconds. Anything else is a failed attempt: a non-2xx status, a timeout, a connection or TLS error, or a redirect (Dakoty does not follow redirects, so register the final URL). The target must be an https:// address on a public server; private and internal addresses are refused. Acknowledge first and process afterwards, because slow handlers time out and trigger retries.
Retry schedule for failed deliveries
Section titled “Retry schedule for failed deliveries”A failed message is attempted up to nine times: the first attempt is immediate and the later ones follow at the delays below, which spreads the attempts over about two and a third days. If your server answers 429 or 503 with Retry-After, the next attempt waits at least that long, capped at 24 hours.
| Attempt | Delay after the previous attempt |
|---|---|
| 1 | immediately |
| 2 | 5 seconds |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 5 hours |
| 7 | 10 hours |
| 8 | 14 hours |
| 9 | 24 hours |
At-least-once delivery semantics
Section titled “At-least-once delivery semantics”Dakoty may deliver the same event more than once, for example when your server processed it but the response got lost. Retries can also make events arrive out of order. Make your handler idempotent: store the envelope id of every processed event and skip repeats, and use timestamp or a fresh GET of the resource when order matters. Each retry reuses the same webhook-id and body.
Automatic disabling after failures
Section titled “Automatic disabling after failures”If a webhook has no successful delivery for 24 hours, Dakoty e-mails the people marked to be notified. After 72 hours without a success the webhook is disabled automatically, and new events are held for up to 30 days (at most 1,000 per webhook) instead of being lost. Webhooks owned by an app such as Make are not disabled but deleted, see REST hooks. The Slovak article Why a webhook fails shows the same states in the app.
Answering 410 Gone
Section titled “Answering 410 Gone”Return 410 Gone from your endpoint to unsubscribe permanently. Dakoty stops delivering at once and does not retry: a manually created webhook is switched off with the reason “gone”, and a REST hook is deleted. Use it when the endpoint is retired for good, and use a 5xx status for a temporary outage you expect to recover from.