> ## 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.

# Text webhooks

> Send recording text to your integrations with capture rules or manual delivery.

Configure text webhooks in **Settings → API & integrations**. Use a spoken prefix,
capture input, or recording type to choose what gets sent, or send a recording
manually from its detail screen. Webhooks do not upload or deliver audio. Clips
delete local audio after successful processing and a safe text save; Recordings
retain it locally.

This guide describes the existing `/api/v1` interface. Use
`https://app.italic.com/api/v1` as the base URL for relative endpoints below.
For simple v2 change notifications, see [Webhooks](/guides/webhooks).

The v1 event feed covers writes from current and compatibility clients:
`recording.created`, `recording.updated`, `recording.transcript_ready`, and
`recording.deleted`. Created means the recording text resource was inserted;
metadata-only pending captures do not emit a created event. Transcript-ready
also fires when nonempty transcript text changes.

`GET /events?after=0&limit=100` returns account-scoped events, `cursor`, and
`hasMore`. Persist the returned cursor after processing the page. IDs are
monotonic but can have gaps. The feed currently remains until account deletion.

Create subscriptions in web Settings. A public HTTPS endpoint on port 443 is
required. Private/local addresses, credentials in the URL, and redirects are
rejected. An account can have ten active subscriptions. The signing secret is
shown once; retain it securely before dismissing it.

## Verify before processing

The default `event` payload contains `id`, `type`, `recordingId`, `revision`, and
`createdAt`. Existing subscriptions retain this shape. It contains no transcript,
audio, or account bearer token. Fetch current content using a separately
authorized read token when needed.

The `Italic-Signature` header is `t=<unix-seconds>,v1=<hex-hmac>`. Compute
HMAC-SHA256 with the webhook secret over the exact UTF-8 bytes of
`timestamp + "." + rawRequestBody`. Compare the digest in constant time and
reject timestamps more than five minutes from your server clock. Do not parse
and reserialize the body before verification. Deduplicate by event `id`.

## Delivery behavior

Delivery is at least once and may arrive out of order. A scheduled worker claims
up to 20 deliveries per minute, with four concurrent requests and a five-second
HTTP timeout. Any 2xx response acknowledges the event. Other statuses and network
failures retry up to eight attempts with exponential backoff starting at one
minute. Deliveries are persisted before sending; process crashes can produce a
repeat. Exhausted deliveries remain recorded for diagnosis. Use the event feed
to catch up independently of webhooks.

Disabling a subscription cancels queued deliveries; an already dispatched request
may still arrive. Recreate subscriptions after rotating the backend signing
master (`BETTER_AUTH_SECRET`), because derived subscription secrets also change.
Never rotate that authentication secret solely to rotate one webhook: disable
and recreate the individual subscription instead.

## Text payloads

Choose **Recording text** in settings or create a subscription with
`POST /webhooks` and `payloadMode: "text"` using a signed-in account session.
Text deliveries include an immutable snapshot of the
selected revision. Retries retain the original body even if the recording was
edited. Bodies exclude audio, coordinates, local files and credentials.

```json theme={null}
{
  "id": "123",
  "eventId": "123",
  "type": "recording.transcript_ready",
  "recordingId": "C5A80766-9674-4EE2-B238-63AC6DD51495",
  "revision": 1,
  "createdAt": 1790164800000,
  "kind": "clip",
  "title": "Parser fix",
  "transcript": "Codex fix the parser",
  "summary": "",
  "recordedAt": 1790164790000,
  "fetchUrl": "https://app.italic.com/api/v1/recordings/C5A80766-9674-4EE2-B238-63AC6DD51495",
  "command": "fix the parser"
}
```

`fetchUrl` requires separate authentication and returns the current revision.
It is not a public download or a signed credential. Deletion events contain
empty text and null kind/capture time. Deleting a recording erases its stored
delivery text and cancels unstarted text deliveries. Previously delivered text
is under the receiver's control.

## Capture rules

`kind` uses the existing values `clip` and `memo`; `memo` means a Recording.

```json theme={null}
{
  "url": "https://your-receiver.com/italic",
  "events": ["recording.transcript_ready"],
  "payloadMode": "text",
  "rules": {"prefix": "Codex", "captureInput": "hold", "kind": "clip"}
}
```

Every configured condition must match. Every matching subscription receives its
own delivery. Prefixes are case-insensitive and anchored to the beginning of the
transcript. The full transcript is preserved; `command` removes only the matched
prefix and leading whitespace from the remainder.

Capture rules apply once per subscription and newly admitted app/device
recording, starting when the subscription is created. They use the first
nonempty transcript. Later edits and MCP/API imports do not retrigger capture
rules. Input conditions wait for authoritative capture metadata, including when
it arrives after the transcript. Import-provided text is not a native capture.
Subscriptions without rules keep their revision-based event behavior.

## Manual delivery and status

Use **Webhooks & delivery** in a recording's web or iOS detail. Setup remains in
the authenticated web integration settings. The signed-in session API is:

```http theme={null}
POST /api/v1/recordings/{id}/deliveries
Idempotency-Key: send-2026-09-23-1
Content-Type: application/json

{"webhookId":"subscription-id","expectedRevision":3}
```

Manual sends bypass automatic rules and preserve the subscription's payload mode.
The event type is `recording.manual`, its ID begins with `manual_`, and it is not
added to the recording-change feed. Reuse the same key and selection after a
network failure. A stale revision is rejected, and disabled subscriptions cannot
receive new sends. `GET /api/v1/recordings/{id}/deliveries` returns statuses and
pending capture evaluations. Pending transcription, waiting for capture input,
queued, delivered, failed and disabled are distinct states.

**Delivered** means only that the receiver returned 2xx. The receiver owns
starting, authorizing and tracking any downstream agent work. For each
subscription, deduplicate on the payload's `id` before starting a task.

## Signature verification example (Node.js)

```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'));
}
```
