# Webhooks

Webhooks let the server notify **your** backend when things happen in a
document, without polling, so you can sync content to your own database,
send notifications, trigger workflows, or index for search.

Enable them by pointing the server at a URL:

```bash
WEBHOOK_URL=https://your-app.example.com/collab/webhook
WEBHOOK_SECRET=a-long-random-shared-secret
```

## What you receive

On each event the server sends an HTTP `POST` with a JSON body:

```json
{
	"event": "comment.created",
	"docId": "report-42",
	"timestamp": 1751979000000,
	"data": { "id": "c1", "authorId": "u1", "body": "Looks good", "...": "..." }
}
```

| `event`            | When                                                  | `data`                                      |
| ------------------ | ----------------------------------------------------- | ------------------------------------------- |
| `document.changed` | A batch was sequenced (throttled; see below)          | `{ seq }`, the new document sequence number |
| `comment.created`  | A comment thread was added                            | the full `CommentThread`                    |
| `comment.updated`  | A thread was edited, replied to, or resolved/reopened | the updated `CommentThread`                 |
| `comment.deleted`  | A thread was deleted                                  | `{ commentId }`                             |
| `session.ended`    | The **last** participant left the document            | `{ seq }`                                   |

Restrict which events you get with `WEBHOOK_EVENTS` (a comma-separated list;
empty = all):

```bash
WEBHOOK_EVENTS=document.changed,session.ended
```

> **Tip: The `session.ended` + HTML export pattern**
>
> A common integration: on `session.ended`, fetch the final document with
> [`GET …/docs/{docId}/html`](history.md#export-the-current-document-as-html)
> and store it in your CMS. `document.changed` (throttled) covers "save
> periodically while people are editing".

## Verifying the signature

When `WEBHOOK_SECRET` is set, each request carries an
`X-Collab-Signature: sha256=<hex>` header: an HMAC-SHA256 of the **raw
request body** with your secret. Verify it before trusting the payload:

```js
import { createHmac, timingSafeEqual } from 'node:crypto';

app.post(
	'/collab/webhook',
	express.raw({ type: 'application/json' }),
	(req, res) => {
		const expected =
			'sha256=' +
			createHmac('sha256', process.env.WEBHOOK_SECRET)
				.update(req.body)
				.digest('hex');
		const got = req.get('x-collab-signature') ?? '';
		if (
			got.length !== expected.length ||
			!timingSafeEqual(Buffer.from(got), Buffer.from(expected))
		) {
			return res.status(401).end();
		}
		const payload = JSON.parse(req.body);
		// … handle payload.event / payload.docId / payload.data …
		res.status(200).end();
	}
);
```

## Delivery semantics

- **Best-effort, fire-and-forget.** A slow or failing endpoint never blocks or
  crashes a collaboration room; failures are logged server-side. There is **no
  retry queue** in this version; treat webhooks as notifications, and use the
  [history](history.md) / [HTML export](history.md#export-the-current-document-as-html)
  APIs as the source of truth when you need guaranteed consistency.
- **Throttling.** `document.changed` is coalesced to at most one delivery per
  document per `WEBHOOK_DOC_THROTTLE_MS` (default 2000 ms); you get a
  heartbeat of "this doc changed", not one call per keystroke. Comment and
  session events are delivered every time.
- **Timeout.** Each delivery is aborted after `WEBHOOK_TIMEOUT_MS`
  (default 5000 ms).
- **Ordering.** Deliveries are not strictly ordered; use `timestamp` / `seq`
  to order on your side.

## Configuration

| Variable                  | Default | Purpose                                                               |
| ------------------------- | ------- | --------------------------------------------------------------------- |
| `WEBHOOK_URL`             | (unset) | Endpoint to POST events to. Unset = webhooks disabled.                |
| `WEBHOOK_SECRET`          | (unset) | HMAC-SHA256 signing key (`X-Collab-Signature`). Strongly recommended. |
| `WEBHOOK_EVENTS`          | all     | Comma-separated allowlist of event names.                             |
| `WEBHOOK_TIMEOUT_MS`      | `5000`  | Per-delivery timeout.                                                 |
| `WEBHOOK_DOC_THROTTLE_MS` | `2000`  | Min interval between `document.changed` deliveries per document.      |

Programmatically, pass a `WebhookDispatcher` in the `webhooks` field of
`ServerConfig` (see the exported `WebhookDispatcher` / `WebhookConfig`).
