> ## Documentation Index
> Fetch the complete documentation index at: https://www.bolna.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow Webhook APIs Overview

> Configure, test and resend the webhook every workflow execution sends when it finishes, and see the payload it carries.

## What is the workflow webhook?

A workflow can send your server **one POST per execution**, the moment that execution finishes — completed, failed, cancelled or aborted. The single payload carries how the execution ended plus the full trail of every node the contact passed through, so you never need to poll the [execution APIs](/docs/api-reference/workflow-executions/overview) to learn an outcome.

The webhook belongs to the workflow, in its `settings`. Setting a URL turns it on; it applies to every execution of the workflow, including ones already running.

## Endpoints

```
PATCH  /workflows/{workflow_id}                              Set, change or remove the webhook (settings.webhook)
POST   /workflows/{workflow_id}/webhook:test                 Send a sample payload and see your server's reply
POST   /workflow-executions/{execution_id}/webhook:resend    Send a finished execution's webhook again
```

## Configure the webhook

Set the URL and the headers to send with every delivery through the [Update Workflow API](/docs/api-reference/workflows/update):

```bash theme={"system"}
curl -X PATCH https://api.bolna.ai/workflows/{workflow_id} \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "settings": {
          "webhook": {
            "url": "https://hooks.example.com/bolna/workflow",
            "headers": {"X-Api-Key": "your-receiver-token"}
          }
        }
      }'
```

`settings` is merged into what is stored, so send only what changes: `{"settings": {"webhook": {"headers": {"X-Old": null}}}}` removes one header and keeps the rest, and `{"settings": {"webhook": null}}` stops sending webhooks. Reading the workflow back returns every header value as `**********`.

The URL must be `https` on port 443 or 8443, and its host can't be a private, loopback, link-local or carrier-NAT IP address. Redirects are not followed. Moving the URL to a different host or port drops the stored headers, so send them again in the same request.

## The payload

```json theme={"system"}
{
  "event": "execution.terminal",
  "schema_version": 1,
  "execution_id": "exec:8f14e45f-2c8e-4b1a-9d3f-6a7b8c9d0e1f:CUST-9931",
  "workflow_id": "b6f1a2c4-1e2d-4a3b-9c5d-7f8e9a0b1c2d",
  "workflow_version": 3,
  "campaign_id": "8f14e45f-2c8e-4b1a-9d3f-6a7b8c9d0e1f",
  "reference_id": "CUST-9931",
  "status": "completed",
  "termination_reason": "promise kept",
  "outcome": "success",
  "occurred_at": "2026-09-22T11:02:41Z",
  "trail": [
    {
      "node_id": "n_call1",
      "node_type": "agent",
      "attempt": 1,
      "status": "completed",
      "to_node_id": "n_extract",
      "matched_case_index": 0,
      "inputs": {"recipient": "+919999999999"},
      "outputs": {"call.status": "completed", "call.duration_s": 148}
    },
    {
      "node_id": "n_extract",
      "node_type": "extraction",
      "attempt": 1,
      "status": "completed",
      "to_node_id": "n_end",
      "matched_case_index": 0,
      "inputs": {},
      "outputs": {"extraction.promise_to_pay": true}
    }
  ]
}
```

Every field is described on the [Execution webhook payload](/docs/api-reference/workflow-webhooks/execution-terminal) page. `status` says how the execution ended and `outcome` which end node it reached — reaching *any* end node is `completed`, so read `outcome` to tell a success from a handled failure.

## Delivery

* **One delivery per execution**, sent when it finishes. Contacts still waiting to start when a campaign is aborted never begin an execution, so they send nothing.
* **Reply with a 2xx** as soon as you have accepted the payload. A non-2xx reply, a redirect, or no reply within 10 seconds is logged as a failed delivery and not attempted again on its own.
* **Resend to recover.** The [Resend Webhook API](/docs/api-reference/workflow-webhooks/resend) sends the original payload again, to the workflow's current URL and headers. Key your handler on `execution_id`, since a resend repeats it.
* **No ordering between executions.** Each payload is self-contained.

For the full walkthrough — testing before you run, reading the trail, and the status combinations worth handling — see the [Execution webhook guide](/docs/workflows/execution-webhook).
