Get a single POST to your server every time a workflow execution finishes, carrying the outcome and the full path the contact took through your workflow.
Set a webhook URL on a workflow and Bolna sends your server one request per execution, at the moment that execution finishes. There is no request per node — the single payload carries the outcome plus the complete trail of every node the contact passed through, so you can reconstruct the whole journey without polling.Every execution reports when it finishes, not just the ones that finish happily. A contact whose call never connected, a run you canceled, and a run still in flight when you aborted its campaign all send a webhook, with a status and termination_reason that tell you which. Contacts still waiting to start when a campaign is aborted never begin an execution, so they send nothing.
This is separate from the call status webhook, which fires per phone call. A workflow that places two calls sends two call webhooks and one execution webhook.
The webhook lives in the workflow’s settings, so it applies to every execution of the workflow, including ones already running.
Dashboard
API
Open the workflow in the editor and click the Workflow settings (gear) icon in the toolbar.
Webhook Configuration in the Workflow settings panel
Turn on Send a webhook and enter your endpoint in Webhook URL.
To authenticate deliveries, turn on Add headers and enter them as a JSON object, e.g. {"X-Api-Key": "your-receiver-token"}. Saved values stay hidden as **********; leave the mask in place to keep a value.
Click Save.
Turning Send a webhook off and saving stops the webhook.
Setting a URL is what turns the webhook on. headers are sent with every delivery and are the usual way to authenticate the request as genuinely from Bolna.A few rules the URL has to satisfy:
https only, on port 443 or 8443.
No internal IP addresses. A host that is a private, loopback, link-local or carrier-NAT IP address is rejected, including alternate spellings of one.
Redirects are not followed. Point the URL at its final destination — a server that answers with a redirect is treated as not having received the webhook.
Moving the URL to a different host or port drops the stored headers, so a credential only ever goes to the host it was entered for. Send the headers again in the same request. Renaming a workflow never touches its webhook.
Header values are never returned. Because updates are merged, you never need them: to keep a header, leave it out of your update. Sending the mask ********** back as a value is rejected.
The sample is built from your published graph, so publish first.
2
Fire a test
Dashboard
API
Click Send Test in the Workflow settings panel. It is enabled once your webhook changes are saved and the workflow is published, and the result shows under the button.
delivered is true only for a 2xx reply. error_type: "http_error" means your server was reached and replied with anything else, redirects included; "unreachable" means it could not be contacted at all.
The sample is not a generic fixture. It walks the first branch of each node in your published workflow, so it carries your node ids and your own extraction field names — whatever you write against the sample will match what you receive in production.
Your own identifier for the contact, exactly as you uploaded it. This is what you join on.
status
How the execution ended: completed, failed, cancelled or aborted.
outcome
The end node’s classification — success, failure or neutral. null when the execution never reached an end node.
termination_reason
The end node’s label, or why it stopped without reaching one: internal_error, required_variable_missing, cancelled, campaign_aborted, zombie or terminated.
occurred_at
When the execution finished. A resent webhook carries the same value.
trail
Every node attempt in order, with the branch each one took and the values it produced. Always sent whole, however long the journey.
Each entry is one attempt at one node. A node reached twice through a retry appears twice, with attempt 1 and 2. to_node_id is the node it moved to next, and matched_case_index is which of that node’s branches matched — so the trail is the exact route through your workflow, not a summary of it.outputs keys are namespaced by what produced them: call.* from an agent node, extraction.* for each disposition you selected, response.* from an API node, whatsapp.* from a WhatsApp node.
Reached an end node. The outcome is the one you gave that end node.
cancelled
null
You canceled the execution.
failed
null
The execution could not continue — for example a required variable never resolved.
aborted
null
The campaign was aborted while this execution was in flight.
Reaching any end node produces status: "completed", including one you labeled a failure. To separate genuine successes from handled failures, read outcome, not status.
Deliveries are not retried automatically. If your endpoint returns a non-2xx status or times out after 10 seconds, that delivery is not attempted again on its own. Return a 2xx as soon as you have durably accepted the payload, and do slow work afterwards. To deliver a missed webhook, resend it.
One delivery per execution. A duplicate is not expected, but a resend delivers the same payload again, so key your handler on execution_id.
No ordering between executions. Two executions finishing at the same moment can arrive in either order. Each payload is self-contained, so this only matters if you are sequencing across contacts yourself.
execution_id is stable and is the same id the execution APIs use, so you can always fetch the full record if a delivery is missed.
curl -X POST https://api.bolna.ai/workflow-executions/{execution_id}/webhook:resend \ -H "Authorization: Bearer YOUR_API_KEY"
It sends the webhook again to the workflow’s current URL and headers, with the payload the execution originally produced — the same occurred_at included. That makes it the way to recover a delivery your server missed, and to redeliver after correcting a wrong URL. The response is 202 once the webhook is queued; the execution must have finished, and its workflow must have a webhook configured.