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

# Webhooks

> Send board events to another tool, and let another tool start an automation.

Work has two kinds of webhook. A **webhook subscription** sends a board's events out to a URL you own. An **inbound webhook** lets another tool start an automation. Both use the same signature, so one verifier works for both.

Both are set up through the API or by an Agent over MCP. There is no screen for them yet.

## Send board events out

A subscription belongs to one board. You need Manage on the board to create, change or delete one. A board can have up to 10.

Create one with `POST /v1/boards/{boardId}/webhooks`:

```json theme={null}
{ "name": "Billing sync", "url": "https://example.com/hooks/work", "eventTypes": ["item.created", "item.status_changed"] }
```

An empty `eventTypes` list means every event. The response shows the signing `secret` once. Store it then: it is never shown again.

### Events

| Event | When it is sent |
| - | - |
| `item.created` | An item is added |
| `item.updated` | Any cell of an item changes. `data.changes` lists every cell the write touched |
| `item.status_changed` | A Status cell changes (one event per Status cell) |
| `item.archived` | An item is archived |
| `item.restored` | An archived item is restored |
| `comment.created` | An update is posted on an item |

### The URL must answer a challenge

Before it stores a subscription, Work sends one signed request to the URL with the body `{"type": "webhook.challenge", "challenge": "<value>"}`. Your endpoint must answer 2xx with the same value, as JSON `{"challenge": "<value>"}` or as the bare text. If it does not, the subscription is not created, and the board's data is never sent to that URL.

The URL must be `https` and point to a public host. Addresses on private networks are refused, both when you create the subscription and on every delivery.

### What a delivery looks like

Each event is sent as a `POST` with a JSON body and these headers:

| Header | Value |
| - | - |
| `x-bot-signature` | `sha256=` and the hex HMAC-SHA256 of the timestamp, a dot, and the raw body |
| `x-bot-timestamp` | Unix time in seconds |
| `x-worksong-event` | The event name |
| `x-worksong-event-id` | The id of the event |
| `x-worksong-delivery` | The id of this delivery. It stays the same on a retry |

To check a delivery, compute the HMAC with your secret over `{x-bot-timestamp}.{raw body}`, compare it to the signature in constant time, and reject a timestamp more than 5 minutes old.

A subscription gets exactly one delivery per event. Delivery is at least once: if Work crashes after your endpoint answered but before it recorded the answer, it sends again. Use `x-worksong-delivery` to ignore a repeat.

### Retries and the delivery log

A 2xx answer ends the delivery. A 408, 425, 429, a 5xx or a network error is retried, after 10 seconds, 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, 8 attempts in all. Any other 4xx is not retried. A delivery that runs out of attempts is marked failed.

`GET /v1/webhooks/{id}/deliveries` lists recent deliveries with their status, attempts and last HTTP status. Large events are cut to 200 KB and flagged `truncated`.

### Manage a subscription

| Call | What it does |
| - | - |
| `GET /v1/boards/{boardId}/webhooks` | Lists the board's subscriptions. The secret and the full URL are not returned |
| `PATCH /v1/webhooks/{id}` | Changes the name, the events or `enabled`. `rotateSecret: true` issues a new secret, shown once |
| `DELETE /v1/webhooks/{id}` | Deletes the subscription and its log |

The URL cannot be edited. A new URL needs a new subscription, because the challenge verified the old one.

## Start an automation from outside

Give an automation the **Inbound webhook** trigger. Then `PUT /v1/automations/{id}/webhook` returns its URL and a signing secret, once. Calling it again rotates the secret, and the old one stops working at once. `DELETE` removes the webhook, and its URL answers 404.

Send a signed `POST` with a JSON object to the URL (`/v1/hooks/{tenantId}/{automationId}`), using the same `x-bot-timestamp` and `x-bot-signature` headers as above. The call needs no login.

The object is flattened to dotted keys: `{"customer": {"name": "Dana"}}` becomes `customer.name`. The automation's actions can then copy these keys into columns, for example with a CreateItem action. A body can have up to 200 fields.

| Answer | Meaning |
| - | - |
| 202 | Accepted. `deduplicated: true` means the same signed request already arrived |
| 400 | The body is not a JSON object |
| 401 | Missing or wrong signature, or a timestamp older than 5 minutes |
| 404 | No such webhook, or it is disabled |
| 413 | The body is over 1 MB |
| 429 | Too many requests |

A repeated request with the same signature is accepted but runs the automation only once.

<sub>Source: `worksong-ai/worksong:services/work/src/Boardy.Api/WebhooksApi.cs@f8b76d6`, `worksong-ai/worksong:services/work/src/Boardy.Edge/Domain/WebhookSubscription.cs@f8b76d6`, `worksong-ai/worksong:services/work/src/Boardy.Workers/WebhookDeliverer.cs@f8b76d6`. Docs version: Worksong 2.0 with Work 1.2.</sub>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.