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

# Webhooks

> Receive real-time events when nodes and phases change.

Webhooks deliver signed event payloads to your URL when nodes or phases are created, updated, or deleted. Use them to connect ProductBrain to Make.com, Zapier, n8n, or any workflow tool.

## Register a webhook

```
POST /api/v1/webhooks
```

```json theme={null}
{
  "projectId": "my-project",
  "url": "https://your-endpoint.example.com/webhook",
  "events": ["node.added", "node.updated"]
}
```

* `url` must be HTTPS and publicly routable
* `events` is optional — omit to subscribe to all six event types
* Maximum 10 webhooks per project

**Response:**

```json theme={null}
{
  "id": "wh_abc123",
  "url": "https://your-endpoint.example.com/webhook",
  "events": ["node.added", "node.updated"],
  "secret": "whsec_..."
}
```

The `secret` is returned once at registration. Store it — you'll need it to verify signatures.

## Event types

| Event               | Fires when                                         |
| ------------------- | -------------------------------------------------- |
| `node.added`        | A node is created                                  |
| `node.updated`      | A node's data changes (label, status, phase, etc.) |
| `node.deleted`      | A node is deleted                                  |
| `iteration.added`   | A phase is created                                 |
| `iteration.updated` | A phase is renamed or status changes               |
| `iteration.deleted` | A phase is deleted                                 |

<Note>Phases are named `iteration` in the API. The event names and the payload field are the stable API identifier and won't change; the product UI calls the same thing a **phase**.</Note>

## Payload

```json theme={null}
{
  "event": "node.updated",
  "project_id": "my-project",
  "timestamp": "2026-06-11T10:30:00Z",
  "node": {
    "id": "job-42",
    "type": "job",
    "parentId": "approach-1",
    "data": {
      "label": "Price comparison shows for scanned barcode",
      "status": "delivered",
      "iteration": "MVP"
    }
  }
}
```

For iteration events, the payload includes `iteration` instead of `node`. Rename events include `previous_name`.

## Headers

| Header                     | Description                                                                               |
| -------------------------- | ----------------------------------------------------------------------------------------- |
| `X-ProductBrain-Event`     | Event type (e.g. `node.updated`)                                                          |
| `X-ProductBrain-Delivery`  | Delivery ID — **stable across all retries** of one event. Dedup on it to stay idempotent. |
| `X-ProductBrain-Signature` | `sha256=<HMAC-SHA256 hex digest of raw body>`                                             |

## Verifying signatures

```javascript theme={null}
const crypto = require('crypto');

function verify(body, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
```

## Delivery semantics

**At-least-once.** Every event is persisted durably *before* the first attempt, so a receiver that's briefly down never loses it. We attempt delivery immediately; if your endpoint returns a non-2xx status or doesn't respond within 5 seconds, we retry with exponential backoff (\~2, 4, 8, 16, 32 minutes) up to **6 attempts total**, then mark the delivery `failed`.

Because it's at-least-once, **your receiver must be idempotent** — the same event can arrive more than once (e.g. you process it but acknowledge past the 5-second window, so we retry). **Dedup on `X-ProductBrain-Delivery`**, which is stable across every retry of one event.

Each attempt re-signs the body with your webhook's *current* secret, so rotating the secret never breaks an in-flight retry.

* **Health monitoring** — `GET /api/v1/webhooks` returns the last delivery time and HTTP status per webhook; poll it to monitor health.
* **Reconcile failures** — a delivery that exhausts all 6 attempts is marked `failed` and not retried further; catch up by reading `GET /api/v1/nodes`.

## List webhooks

```
GET /api/v1/webhooks?projectId=my-project
```

## Delete a webhook

```
DELETE /api/v1/webhooks?projectId=my-project&webhookId=wh_abc123
```

## Integration guides

Webhooks power the delivery tracker integrations via Make.com:

* [Linear integration](/guides/linear-integration)
* [GitHub integration](/guides/github-integration)
* [Jira integration](/guides/jira-integration)
* [Azure DevOps integration](/guides/azure-devops-integration)
