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

# Webhooks

> Receive a notification when a recording is created, edited, or deleted.

Create a webhook in Settings or through `POST /webhooks` using a signed-in
account session. Choose a public HTTPS URL and the events you need:

```json theme={null}
{
  "url": "https://your-receiver.com/italic",
  "events": ["recording.created", "recording.updated", "recording.deleted"]
}
```

Creation returns the webhook and a one-time signing `secret`. Store the secret
before dismissing it. `GET /webhooks` lists active event subscriptions;
`DELETE /webhooks/{id}` disables a subscription. Accounts can have ten active
subscriptions. API keys cannot manage destinations.

## Notifications

```json theme={null}
{
  "id": "123",
  "type": "recording.created",
  "recordingId": "C5A80766-9674-4EE2-B238-63AC6DD51495",
  "revision": 1,
  "createdAt": 1790121600000
}
```

Notifications contain an ID and change details. Fetch the current recording
with a key that has `recordings:read`. They contain no transcript or audio.
Created means the recording text item was saved; a pending local capture alone
does not emit it. Notifications include changes made by existing app clients.

Verify the `Italic-Signature` header before processing. It has the form
`t=<unix-seconds>,v1=<hex-hmac>`. Compute HMAC-SHA256 with your webhook secret over
`timestamp + "." + rawRequestBody`, compare in constant time, and reject timestamps
more than five minutes from your clock. Use the original bytes, not reserialized JSON.

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

export function verify(rawBody, signature, secret) {
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(signature ?? '');
  if (!match || Math.abs(Date.now() / 1000 - Number(match[1])) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(match[1] + '.').update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
}
```

Acknowledge with a 2xx response. Failures retry up to eight attempts. Delivery can
repeat or arrive out of order, so deduplicate by event ID and avoid assuming order.
Disabling a subscription cancels queued sends; an in-flight request may finish.
To rotate one secret, disable that subscription and create a replacement.

## Catch up with events

If you need polling or recovery, use `GET /events?limit=100`. It returns `events`
and `nextCursor`. After processing a page, save `nextCursor` and supply it as
`cursor` on the next request. An empty page means you are caught up. Cursors have
gaps and should be treated as opaque values. Events remain until account deletion.

## Sending recording text

The app also supports text delivery, capture rules, and manual sends. Those
existing features use the [compatibility webhook interface](/compatibility/webhooks).
They are separate from the simple event subscriptions described here and do not
appear in the v2 webhook list when configured in text mode.
