Webhook trigger
POST /v1/hooks/{token} — run a deployed decision from a secret URL without an API key, from forms, CRMs, Zapier, n8n, Make or any backend, optionally signed with a whsec_ secret.
POST https://api.dcision.io/v1/hooks/whk_…Every decision can have a webhook URL: a secret address that runs the decision's active version when something posts to it — a form, a CRM, Zapier, n8n, Make, Pipedream or another backend. It needs no API key, so tools that can only call a URL can trigger a decision, and you can add a secret so that only callers who know it are accepted.
The webhook trigger is inbound: someone calls Dcision. Webhook destinations go the other way — Dcision calls you after it decides — and use the decision.completed event.
Set it up
Open the decision in the app and go to its Advanced tab:
- Turn the webhook on. Dcision creates the URL:
https://api.dcision.io/v1/hooks/whk_followed by 32 letters and digits. Copy it. - Generate a secret (recommended). The full
whsec_…value is shown only once — store it where the caller can read it, for example asDCISION_WEBHOOK_SECRET. - Send a test from the tab's console: Dcision calls the real URL, signed with the secret, and shows the request and the response. The test runs the decision like any caller and counts as a run.
The tab also lists the latest calls as they arrive, refusals included, and has ready-made cURL, Node.js and Python snippets with your URL. Turning the webhook on or off needs the Member role; rotating the URL and generating, replacing or removing the secret need Admin — see Team and roles.
The URL runs the deployed version: deploy the decision before you point a system at it.
Request
Path
| Parameter | Description |
|---|---|
token | The whk_… token of the decision's webhook. It identifies both the decision and the workspace. |
Query parameters
| Parameter | Description |
|---|---|
include | Optional. probabilities adds the full distribution of every question, as in Run a decision. |
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Recommended | application/json (any other value is read as JSON too) — or text/plain to send a text state as is. |
Dcision-Signature | With a secret | t=<unix seconds>,v1=<hex> — see Sign the request. |
Authorization | With a secret, instead of the signature | Bearer whsec_… — the secret itself, for tools that can't compute an HMAC. |
Idempotency-Key isn't supported on webhook URLs: a retried call runs — and is billed — again.
Body
Send the state wrapped, as for the API, or the payload itself:
{ "state": { "message": "Can I get a demo next week?", "company_size": 80 } }{ "message": "Can I get a demo next week?", "company_size": 80 }When the body is a JSON object whose only key is state, Dcision uses the value of state. Anything else — an object with other keys, an array, a string — is the state. That lets a third-party tool post its own payload unchanged; its fields must still match the decision's state schema.
A payload with a single state field
If the system you connect sends something like { "state": "CA" } — a field that happens to be called state and nothing else — Dcision unwraps it and the state becomes "CA". Wrap such payloads yourself: { "state": { "state": "CA" } }.
The body must be at most 128 KB.
Sign the request
With a secret, every call must prove it knows it, in one of two ways.
Dcision-Signature (recommended) — the same scheme Dcision uses to sign its own webhook deliveries:
signed_payload = "<t>." + <the raw request body>
v1 = hex( HMAC-SHA256( secret, signed_payload ) )
header = Dcision-Signature: t=<t>,v1=<v1>t is the current Unix time in seconds; calls whose t is more than 300 seconds away from Dcision's clock are refused, so a captured request can't be replayed later. Sign the exact bytes you send — serialize the body once and reuse that string.
Authorization: Bearer whsec_… — the secret itself, compared in constant time. It's simpler for no-code tools, but the secret travels with every request and a captured request can be replayed; prefer the signature when you can compute it. Either proof is enough: if your tool adds its own Authorization header (a credential of its own, an API gateway token), a valid Dcision-Signature still passes.
URL="https://api.dcision.io/v1/hooks/whk_…" # from the Advanced tab
BODY='{"state":{"message":"Can I get a demo next week?","company_size":80}}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$DCISION_WEBHOOK_SECRET" | sed 's/^.* //')
curl -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Dcision-Signature: t=$TS,v1=$SIG" \
-d "$BODY"In Zapier, n8n or Make, use an HTTP or webhook step with method POST, the URL, a JSON body and a header Authorization = Bearer whsec_… stored as a credential.
Without a secret
A webhook without a secret accepts any call to its URL: the URL itself is the credential. That's fine for a quick test or a trusted internal network, but anyone who sees the URL — in a tool's logs, a shared screenshot, a browser's history — can run the decision, and every run is billed to your workspace.
- Add a secret before you put the URL in a third-party tool, and keep the secret out of the URL.
- If a URL leaks, rotate it in the Advanced tab: the old URL stops working at once and answers
404. - Turning the webhook off makes the URL answer
404 NOT_FOUND, the same answer as a URL that never existed.
Removing the secret makes the URL accept unsigned calls again.
Response
The same response as POST /v1/decisions/{slug} — result, confidence, scores, composites, action, action_reason, destinations and metrics:
{
"decision_id": "dec_3fKq9ZtW1mXcV7bN2pLa",
"execution_id": "exec_8HsT2kQw9ZyR4vMn1cXe",
"schema": "lead-qualification",
"version": 1,
"result": { "purchase_intent": 0.9412, "priority": "high", "route": "sales" },
"confidence": { "purchase_intent": 0.8824, "priority": 0.69, "route": 0.85 },
"scores": { "priority": 2.81 },
"composites": { "lead_score": 0.8414 },
"action": "continue",
"action_reason": { "type": "default" },
"metrics": {
"latency_ms": 412,
"engine": "jev",
"model": "jev-1.13.0",
"estimated_cost_usd": 0.00001575,
"input_tokens": 375,
"output_tokens": 36
}
}Runs through a webhook URL:
- are billed like API calls and count toward the plan's included volume — past it they use credits, and without credits they answer
402 CREDITS_EXHAUSTED; - share the workspace's decisions rate limit with the API — see Rate limits;
- run the decision's destinations with
livemode: true; - appear in Executions, in the decision's Overview with the API traffic, and in
GET /v1/executionswithsource: "webhook".
Errors
Errors use the standard format, { "error": { "code", "message", "request_id" } }.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_REQUEST | An empty body, or a body that isn't valid JSON. |
| 401 | UNAUTHORIZED | The webhook has a secret and the call has no valid proof: no Dcision-Signature and no Authorization, a malformed or wrong signature, a timestamp outside the 300-second window, or a wrong Bearer secret. The message says which. |
| 402 | CREDITS_EXHAUSTED | The plan's included volume is used up and there are no credits left. |
| 402 | SPEND_CAP_REACHED | The spend cap of the billing cycle is reached. |
| 402 | QUOTA_EXCEEDED | The included volume of a plan with a hard cap is used up. |
| 404 | NOT_FOUND | Unknown token, a rotated URL, or a webhook that is turned off. |
| 409 | DECISION_NOT_DEPLOYED | The decision has never been deployed. |
| 409 | DECISION_DISABLED | The decision's endpoint is disabled. |
| 413 | PAYLOAD_TOO_LARGE | The body is larger than 128 KB. |
| 422 | INVALID_STATE | The state doesn't match the decision's state schema or the engine's token budget. |
| 429 | RATE_LIMITED | The workspace's webhook rate limit for the minute is used up (a window of its own, separate from API keys) — or the webhook received more than 30 calls with a wrong signature or secret this minute (Retry-After: 60). |
| 424, 5xx | ENGINE_…, INTERNAL_ERROR | As in Run a decision. |
Refused calls — a bad signature, invalid JSON, the rate limit — don't become executions, but they appear in the Advanced tab's list of latest calls, so you can see what a misconfigured tool sends.
Who sees the URL
Without a secret, the URL alone runs the decision — billed, with live destinations. So the Advanced tab shows it only to members and admins; viewers see that the webhook exists, but not its URL. Rotating the URL and managing the secret are for admins. When someone leaves the team, rotate the URL (and the secret) they could see.
The Send test request button runs in live mode as well (destinations fire for real) and counts as a webhook execution. The request it shows has the signature masked, so it can't be replayed.
List executions
GET /v1/executions — recent runs of your decisions with answers, action, reason and metrics, filtered by decision and status and paginated with a cursor. Inputs are never returned.
Webhook events
The decision.completed event of the OpenAPI document — the request sent to webhook destinations, its headers, every field of the event and the answers Dcision expects.