Skip to main content

What you get

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 cancelled, 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.

Configuring it

The webhook lives in the workflow’s settings. Set it with the Update Workflow API:
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.

Changing it later

settings is merged into what is already stored, so you only send what changes: 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.

Reading it back

GET /workflows/{workflow_id} returns the settings with every header value masked:
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.

Testing it before you run anything

1

Publish the workflow

The sample is built from your published graph, so publish first.
2

Fire a test

Call the Test Webhook API:
3

Read the verdict

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.

The payload

Every field is also described on the Execution webhook payload reference page.

Reading the trail

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.

Status combinations worth handling

Reaching any end node produces status: "completed", including one you labelled a failure. To separate genuine successes from handled failures, read outcome, not status.

What to expect in delivery

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.

Resending a webhook

Call the Resend Webhook API with the execution’s id:
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.