REST hooks for Make, Zapier and n8n
A REST hook is a webhook that an application creates and removes itself through the API, so nobody has to paste URLs into the Dakoty app. Make, Zapier and n8n use this pattern: when you switch a scenario on they subscribe, and when you switch it off they unsubscribe. You can use the same endpoints from your own integration. Deliveries, signatures and retries work exactly as in Webhooks.
What a key needs to subscribe
Section titled “What a key needs to subscribe”The key needs the scope webhooks:manage and, in addition, the read scope of every event it subscribes to, for example leads:read for lead.created. The event catalog with scopes is on the Webhooks page. A key that lacks one of the event scopes gets 403 FORBIDDEN and no hook is created.
Subscribe with POST /webhooks
Section titled “Subscribe with POST /webhooks”Send the HTTPS target_url (up to 2048 characters, a public host) and the events either as an events array or, in the shape Zapier uses, as a single event string. name is optional; without it Dakoty names the hook after the key and the first event. The reply is 201 with the hook and its signing secret.
curl -sS -X POST https://api.dakoty.com/api/v1/public/webhooks \ -H "Authorization: Bearer $DAKOTY_API_KEY" \ -H "Content-Type: application/json" \ -d '{"target_url":"https://example.com/dakoty/webhook","events":["lead.created","lead.won"],"name":"CRM sync"}'The create response and the secret
Section titled “The create response and the secret”The secret (starting with whsec_) is returned only in this response and is never shown again, so save it next to the hook id. The response is sent with Cache-Control: no-store. Use the secret to verify deliveries as described in Verifying signatures.
{ "data": { "id": "2c7f0f4e-8d1a-4b53-9d1e-5a6b7c8d9e0f", "target_url": "https://example.com/dakoty/webhook", "events": ["lead.created", "lead.won"], "name": "CRM sync", "created_at": "2026-10-11T08:30:12Z", "secret": "whsec_..." }}Errors when subscribing
Section titled “Errors when subscribing”An unknown event returns 422 UNKNOWN_EVENT. An address that is not https://, points to a private or internal host, or does not resolve returns 422 WEBHOOK_URL_INVALID or WEBHOOK_URL_BLOCKED. When the organization has reached its maximum number of webhooks you get 409 LIMIT_EXCEEDED. Send an Idempotency-Key so a retried subscribe request does not register the hook twice; see Idempotency.
List, read and unsubscribe
Section titled “List, read and unsubscribe”GET /webhooks lists the REST hooks of the calling key and GET /webhooks/{id} returns one; neither ever returns the secret. DELETE /webhooks/{id} unsubscribes and answers 204. A key sees only the hooks it created, so a hook of another key, a hand-made webhook from the app and an unknown id all answer 404. GET /webhooks/events lists the available event types and GET /webhooks/events/{type}/sample returns a sample envelope.
Subscribe and unsubscribe in Node.js
Section titled “Subscribe and unsubscribe in Node.js”A small helper pair for an app that follows a user’s scenario on and off. It needs Node.js 18 or newer.
const BASE = "https://api.dakoty.com/api/v1/public";const headers = { Authorization: `Bearer ${process.env.DAKOTY_API_KEY}`, "Content-Type": "application/json",};
export async function subscribe(targetUrl, events) { const res = await fetch(`${BASE}/webhooks`, { method: "POST", headers, body: JSON.stringify({ target_url: targetUrl, events }), }); const body = await res.json(); if (res.status !== 201) throw new Error(`${res.status} ${body.error.code}`); return body.data; // keep data.id and data.secret}
export async function unsubscribe(id) { const res = await fetch(`${BASE}/webhooks/${id}`, { method: "DELETE", headers }); if (res.status !== 204 && res.status !== 404) throw new Error(`unsubscribe failed: ${res.status}`);}Subscribe and unsubscribe in Python
Section titled “Subscribe and unsubscribe in Python”The same pair with requests. Treat 404 on delete as success, because the hook may already be gone.
import osimport requests
BASE = "https://api.dakoty.com/api/v1/public"HEADERS = {"Authorization": f"Bearer {os.environ['DAKOTY_API_KEY']}"}
def subscribe(target_url, events): r = requests.post(f"{BASE}/webhooks", headers=HEADERS, timeout=30, json={"target_url": target_url, "events": events}) if r.status_code != 201: raise RuntimeError(f"{r.status_code} {r.json()['error']['code']}") return r.json()["data"] # keep data["id"] and data["secret"]
def unsubscribe(hook_id): r = requests.delete(f"{BASE}/webhooks/{hook_id}", headers=HEADERS, timeout=30) if r.status_code not in (204, 404): raise RuntimeError(f"unsubscribe failed: {r.status_code}")When Dakoty removes a REST hook
Section titled “When Dakoty removes a REST hook”REST hooks are owned by the application, so Dakoty does not switch them off like hand-made webhooks. A REST hook is deleted when your endpoint answers 410 Gone, when it has had no successful delivery for 72 hours, and when the API key that created it is revoked. Your application should treat a missing hook as normal: when a scenario is switched on, call GET /webhooks/{id} for the stored id and subscribe again if it answers 404. Hand-made webhooks stay in the app and are described in the Slovak help article Webhooks.