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

# Webhooks

> Get an HTTP request on your server when integrations or connections change in your workspace.

Webhooks tell your backend when something changes in your BundleUp workspace, so you don’t have to poll. The most common use is keeping your own database in sync with connections: a user connected an account, a token was refreshed, a connection was deleted.

**Use webhooks to:**

* Save a new connection even if the user closed the tab before reaching your callback (`connection.created`).
* Stop syncing data for a connection that was deleted (`connection.deleted`).
* Track when tokens are refreshed (`connection.refreshed`).
* Audit integration changes made in the dashboard (`integration.*`).

All nine events are listed in the [event catalogue](/docs/guides/webhooks/events).

## How delivery works

```mermaid theme={null}
sequenceDiagram
    participant BU as BundleUp
    participant W as Your webhook endpoint
    participant API as BundleUp Platform API

    Note over BU: A connection is created
    BU->>W: POST (signed JSON, 3 s timeout)<br/>BundleUp-Signature, BundleUp-Event, BundleUp-Workspace
    W->>W: Verify signature and timestamp
    W-->>BU: 2xx, quickly
    W->>W: Process the event in the background
    alt Endpoint failed, timed out or was down
        Note over BU,W: The event is not resent
        W->>API: Periodically reconcile with GET /v1/connections
    end
```

When an event happens, BundleUp sends one signed `POST` to every webhook in the workspace that subscribes to that event. Your endpoint verifies the signature, answers `2xx` straight away, and does the real work afterwards. Because a failed delivery isn’t resent, a periodic reconciliation against the Platform API catches anything you missed.

Delivery details:

| Property | Value |
| - | - |
| Method | `POST`, `Content-Type: application/json` |
| Timeout | 3 seconds |
| Attempts | **One.** Failed or timed-out deliveries are not retried. |
| Success | Any `2xx` response |
| On failure | Nothing is resent, and later events are still sent. Failures aren’t shown in the dashboard; `last_triggered_at` on the Platform API’s webhook object shows when a delivery was last attempted. |
| Ordering | Not guaranteed. Use `webhookTimestamp` and the connection’s timestamps, not arrival order. |

## Setting up a webhook

You need the **admin** or **owner** role.

1. Open **Settings → Webhooks** in the dashboard and create a webhook.
2. Enter a name (3 to 50 characters) and your endpoint’s **Webhook URL**. It must be publicly reachable; use HTTPS.
3. Choose the **events** to receive.
4. Copy the **signing secret** and store it with your server’s secrets. You need it to [verify signatures](/docs/guides/webhooks/verifying).

You can also manage webhooks with the Platform API: `GET`, `POST /v1/webhooks` and `GET`, `PATCH`, `DELETE /v1/webhooks/{id}`. See the [Platform API reference](/docs/api-reference/platform-api).

## Request format

**Headers:**

| Header | Example |
| - | - |
| `BundleUp-Signature` | `5d41402abc4b2a76b9719d911017c592...` (hex HMAC-SHA256 of the body) |
| `BundleUp-Event` | `connection.created` |
| `BundleUp-Workspace` | Your workspace ID |

**Body:**

```json theme={null}
{
  "action": "connection.created",
  "workspaceId": "cm0workspace123",
  "webhookId": "cm0webhook456",
  "webhookTimestamp": 1757700000000,
  "data": {
    "id": "conn_abc123",
    "externalId": "user_42",
    "integrationId": "cm0integration789",
    "createdAt": "2026-09-12T18:00:00.000Z",
    "updatedAt": "2026-09-12T18:00:00.000Z",
    "refreshedAt": null
  }
}
```

| Field | Description |
| - | - |
| `action` | The event name. Same as the `BundleUp-Event` header. |
| `workspaceId` | The workspace the event belongs to. |
| `webhookId` | The webhook this delivery is for. |
| `webhookTimestamp` | When the delivery was sent, in **milliseconds** since the epoch. |
| `data` | The object the event is about. Its shape depends on the event. |

## Writing a reliable endpoint

1. **Verify the signature** before trusting anything in the body. See [Verifying signatures](/docs/guides/webhooks/verifying).
2. **Respond within 3 seconds.** Return `200` as soon as the signature checks out, then process the event asynchronously (a job queue is ideal). Slow endpoints time out and the event is lost.
3. **Be idempotent.** Handle the same event twice without side effects. Key your processing on `action` plus `data.id` plus the relevant timestamp.
4. **Treat webhooks as hints, not the source of truth.** Since deliveries aren’t retried, run a periodic job that lists connections with `GET /v1/connections` (filter with `external_id` or `integration_identifier`) and reconciles them with your database.
5. **Monitor your endpoint.** Alert on errors and slow responses in your own logs, since BundleUp doesn’t report failed deliveries.

## Testing locally

Your local machine isn’t reachable from the internet, so expose it with a tunnel (for example `ngrok http 3000` or `cloudflared tunnel`) and register the tunnel URL as a webhook in your **development** workspace. Then connect an account to trigger `connection.created`.


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