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

MethodPathScope
GET/v1/projects/{projectId}/webhooksread
POST/v1/projects/{projectId}/webhookswebhooks: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}/testwebhooks:write
GET/v1/projects/{projectId}/webhooks/{webhookId}/deliveriesread
POST/v1/projects/{projectId}/webhooks/{webhookId}/rotate-secretwebhooks:write

Create

bash
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"] }'

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

Events

EventFired when
conversation.createdA new conversation opens
conversation.updatedSubject / status / priority / assignee / tags change
conversation.resolvedConversation transitions to resolved
message.createdNew message posted (customer / agent / AI)
contact.createdContact created via API, widget, or form
contact.updatedContact fields change
agent.escalatedAI agent escalates to a human
agent.action_executedAgent performs an automation action
form.submittedPublic form receives a submission
webhook.testSent 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):

AttemptWait before
10
21s
35s
430s

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):

AttemptWait before
51m
65m
730m
82h
912h

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:

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

HeaderUse
X-RogerIQ-EventEvent type
X-RogerIQ-DeliveryUnique delivery id — dedupe on this for at-least-once safety
X-RogerIQ-Signaturesha256=<hex>
X-RogerIQ-TimestampUnix seconds — reject if drift > 5 min
X-RogerIQ-Attempt1-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.

Ask a question... ⌘I