# Webhooks

> Push Bookbag events to your own endpoints in real time. Covers the event catalog, the signed payload format, HMAC-SHA256 signature verification, and delivery behavior.

Webhooks let Bookbag push events to your own systems the moment they happen. Register a URL, choose which events to subscribe to, and Bookbag POSTs a signed JSON payload to that URL whenever a matching event fires — no polling required.

> **MANAGE WEBHOOKS IN YOUR WORKSPACE:** Webhooks are configured per workspace by an admin. Each webhook has a URL, a list of subscribed events, and a signing secret. The secret is shown when you create the webhook — store it safely.

## Create a webhook

1. **Add an endpoint URL** — In your workspace integrations, add a webhook with the HTTPS URL Bookbag should POST to.
2. **Choose events** — Subscribe to specific events, or use the wildcard `*` to receive everything.
3. **Save the signing secret** — Bookbag generates a secret (or you can supply one). Use it to verify that incoming requests really came from Bookbag.

## Events

Subscribe to the events your integration cares about. Subscribing to `*` delivers all events.

| Event | Fires when |
| --- | --- |
| `conversation.replied` | The agent posts a reply in a conversation. |
| `conversation.ended` | A conversation is closed or resolved. |
| `lead.created` | A lead is captured via the collect-leads action. |
| `action.run` | An action (custom action, escalation, etc.) executes. |

> **TIP:** Start narrow — subscribe only to the events you'll act on. You can always widen the subscription later.

## Payload format

Every delivery is a `POST` with a JSON body containing the event name, a `data` object, and a millisecond timestamp:

```json
{
  "event": "conversation.replied",
  "data": {
    "conversation_id": "conv_123",
    "agent_id": 42,
    "message": "Your order shipped today."
  },
  "ts": 1733155200000
}
```

## Verifying signatures

Each request includes an `X-Bookbag-Signature` header so you can confirm it came from Bookbag and wasn't tampered with. The value is `sha256=` followed by the HMAC-SHA256 of the **raw request body**, keyed with your webhook secret.

> **SIGN THE RAW BODY:** Compute the HMAC over the exact bytes you received, before any JSON parsing or re-serialization. Re-stringifying the body can change whitespace and break the signature. Always compare using a constant-time equality check.

```javascript
import crypto from 'node:crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const a = Buffer.from(header || '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: use the raw body, not the parsed object.
app.post('/bookbag-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(req.body, req.get('X-Bookbag-Signature'), process.env.BOOKBAG_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  const payload = JSON.parse(req.body.toString());
  // handle payload.event ...
  res.status(200).end();
});
```

## Delivery behavior

- **Respond 2xx quickly.** Acknowledge receipt fast and do heavy work asynchronously so deliveries don't time out.
- **Be idempotent.** Design handlers so a duplicate delivery is harmless.
- **HTTPS only.** Endpoints must be reachable public HTTPS URLs.

> **INFO:** For event listeners on the front end (reacting to widget events in the browser) and the deeper developer webhook guide, see the developer docs.

## What's next

- [Webhook guide](/docs/developers/webhooks) — The in-depth developer guide to consuming webhooks.
- [Event listeners](/docs/developers/event-listeners) — React to widget events in the browser.
- [Zapier](/docs/integrations/zapier) — Route webhook events into thousands of apps, no code.
