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

# Webhooks API

Subscribe to events, fire test deliveries, inspect history, rotate secrets, and use the long-tail retry workflow.

# Webhooks API

External systems react to RogerIQ events. Inline retries handle transient
failures; a Cloudflare Workflow handles the long tail (up to 12 hours).

## Endpoints

| Method | Path | Scope |
|--------|------|-------|
| `GET` | `/v1/projects/{projectId}/webhooks` | `read` |
| `POST` | `/v1/projects/{projectId}/webhooks` | `webhooks:write` |
| `GET` | `/v1/projects/{projectId}/webhooks/{webhookId}` | `read` |
| `PATCH` | `/v1/projects/{projectId}/webhooks/{webhookId}` | `webhooks:write` |
| `DELETE` | `/v1/projects/{projectId}/webhooks/{webhookId}` | `webhooks:write` |
| `POST` | `/v1/projects/{projectId}/webhooks/{webhookId}/test` | `webhooks:write` |
| `GET` | `/v1/projects/{projectId}/webhooks/{webhookId}/deliveries` | `read` |
| `POST` | `/v1/projects/{projectId}/webhooks/{webhookId}/rotate-secret` | `webhooks:write` |

## Create

```bash cURL
curl -X POST https://api.rogeriq.com/api/v1/projects/prj_xxx/webhooks \
  -H "X-API-Key: riq_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/rogeriq-hook",
    "events": ["conversation.*", "message.created"]
  }'
```

```bash CLI
rogeriq webhooks create \
  --url https://example.com/rogeriq-hook \
  --events 'conversation.*,message.created'
```

```ts SDK
const wh = await roger.webhooks.create({
  url: "https://example.com/rogeriq-hook",
  events: ["conversation.*", "message.created"],
});
// wh.secret is populated — store it.
```

The response includes `secret` exactly once. Store it for signature
verification.

## Events

| Event | Fired when |
|-------|-----------|
| `conversation.created` | A new conversation opens |
| `conversation.updated` | Subject / status / priority / assignee / tags change |
| `conversation.resolved` | Conversation transitions to `resolved` |
| `message.created` | New message posted (customer / agent / AI) |
| `contact.created` | Contact created via API, widget, or form |
| `contact.updated` | Contact fields change |
| `agent.escalated` | AI agent escalates to a human |
| `agent.action_executed` | Agent performs an automation action |
| `form.submitted` | Public form receives a submission |
| `webhook.test` | Sent by the `test` endpoint |

Wildcards supported: `*` (everything), `conversation.*` (prefix).

## Test delivery

```bash
curl -X POST https://api.rogeriq.com/api/v1/projects/prj_xxx/webhooks/wh_xxx/test \
  -H "X-API-Key: riq_xxx"
```

```json
{
  "data": {
    "delivered": true,
    "status_code": 200,
    "duration_ms": 143
  }
}
```

Test events skip the long-tail retry workflow and carry
`event: "webhook.test"` in the payload so receivers can filter.

## Deliveries

```bash
curl https://api.rogeriq.com/api/v1/projects/prj_xxx/webhooks/wh_xxx/deliveries \
  -H "X-API-Key: riq_xxx"
```

Last 50 attempts with status code, duration, attempt number, and status
(`delivered` / `failed`).

## Rotate secret

```bash
curl -X POST https://api.rogeriq.com/api/v1/projects/prj_xxx/webhooks/wh_xxx/rotate-secret \
  -H "X-API-Key: riq_xxx"
```

Returns `{ secret: "whsec_..." }`. The old secret stops working
**immediately** — update your receiver first.

## Delivery semantics

**Inline retries** (within the original request worker invocation):

| Attempt | Wait before |
|---------|-------------|
| 1 | 0 |
| 2 | 1s |
| 3 | 5s |
| 4 | 30s |

Network errors, 5xx, 408, 425, and 429 trigger retry. 4xx (other than
408/425/429) bail out immediately — your endpoint said "no".

**Long-tail retries** (Cloudflare Workflow scheduled after inline
exhaustion):

| Attempt | Wait before |
|---------|-------------|
| 5 | 1m |
| 6 | 5m |
| 7 | 30m |
| 8 | 2h |
| 9 | 12h |

The workflow re-fetches the webhook row on each attempt, so disabling
or rotating mid-retry behaves correctly.

After 10 consecutive failures the webhook is auto-disabled. Re-enable
with `PATCH .../status=active`.

## Verifying signatures

Use [`@rogeriq/sdk`](/sdk/webhooks):

```ts
import { verifyWebhookSignature } from "@rogeriq/sdk";

const rawBody = await req.text();
const sig = req.headers.get("x-rogeriq-signature");
if (!(await verifyWebhookSignature(rawBody, sig, process.env.WEBHOOK_SECRET!))) {
  return new Response("invalid signature", { status: 401 });
}
```

Or manually with Node crypto:

```ts
import crypto from "node:crypto";

const expected = "sha256=" + crypto
  .createHmac("sha256", secret)
  .update(rawBody)
  .digest("hex");

if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
  throw new Error("invalid signature");
}
```

## Headers on every delivery

| Header | Use |
|--------|-----|
| `X-RogerIQ-Event` | Event type |
| `X-RogerIQ-Delivery` | Unique delivery id — dedupe on this for at-least-once safety |
| `X-RogerIQ-Signature` | `sha256=<hex>` |
| `X-RogerIQ-Timestamp` | Unix seconds — reject if drift > 5 min |
| `X-RogerIQ-Attempt` | 1-9 (1 = first try, 5-9 = long-tail) |

Rotate a webhook secret if it is logged, pasted into a client, or
shared with a system that should no longer receive events.