Webhooks
Webhooks are how Omniflow tells your systems about things that happened — a conversation resolved, a ticket was created, a scorecard was scored. They’re HTTP POSTs with a JSON body and an HMAC signature header.
Event catalog
| Event | Triggered when |
|---|---|
conversation.created | A new conversation lands in the inbox. |
conversation.message_added | A new message is sent or received on a conversation. |
conversation.resolved | A conversation is marked resolved. |
conversation.reopened | A resolved conversation gets a new reply. |
ticket.created | A new ticket is opened. |
ticket.updated | A ticket’s status, priority, or assignee changes. |
ticket.resolved | A ticket is resolved. |
ticket.merged | One ticket is merged into another. |
contact.created | A new contact is created. |
contact.updated | A contact’s data changes. |
scorecard.created | The QA grader scores a conversation. |
scorecard.overridden | A coach overrides an AI score. |
agent.published | An AI agent config is published. |
agent.failed | An AI agent failed at runtime (failover triggered). |
training_attempt.completed | A trainee finishes a practice call. |
training_attempt.reviewed | A coach reviews an attempt. |
automation.triggered | A routing or notification rule fired. |
sla.breach_imminent | An SLA target is within 30 minutes of breach. |
sla.breached | An SLA target was breached. |
Payload shape
Every payload has a stable envelope:
{
"id": "evt_a1b2",
"type": "conversation.resolved",
"created_at": "2026-05-01T14:30:00Z",
"workspace_id": "ws_demo",
"data": { ... event-specific ... }
}data is the resource at the moment of the event. Nested resources are included by reference (contact_id) — fetch with the API if you need details.
Headers
| Header | Notes |
|---|---|
Content-Type | application/json. |
X-Omniflow-Event | The event type (conversation.resolved). |
X-Omniflow-Signature | HMAC-SHA256 of the body, hex-encoded. |
X-Omniflow-Timestamp | Unix seconds; reject if more than 5 minutes off. |
X-Omniflow-Delivery | UUID for this delivery attempt; same per retry. |
Signature verification
import crypto from 'node:crypto'
function verify(req: Request, secret: string): boolean {
const signature = req.headers.get('x-omniflow-signature')!
const timestamp = req.headers.get('x-omniflow-timestamp')!
const body = await req.text()
// Reject stale deliveries
const skew = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10))
if (skew > 300) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
)
}Always verify the signature. Don’t trust the source IP — IPs change and aren’t authentication. The signature + timestamp combo is what proves the request is from Omniflow.
Delivery semantics
- At-least-once. Your endpoint must be idempotent on
id. Two deliveries ofevt_a1b2should produce one outcome. - Ordered per resource. Events for the same
conversation_idarrive in order; events across resources can interleave. - Retries. Failed deliveries (non-2xx response or timeout) retry with exponential backoff up to 24 hours: 30s, 1m, 5m, 30m, 2h, 6h, 24h.
- Backoff on sustained failure. After 24h of failure, the delivery is dropped and the endpoint is flagged in the dashboard.
- Timeout. Omniflow waits 8 seconds for a response; longer is treated as a failure.
Best practices for handlers
- Return 2xx fast. Acknowledge in <1s; do work in a background queue.
- Idempotency. Dedupe on the event
id. - Replay-safety. Treat each event as an upsert — re-running shouldn’t break state.
- Schema flexibility. Omniflow may add fields; ignore unknown fields gracefully.
Replay
Failed deliveries can be replayed manually:
- Settings → Integrations → Webhooks → Deliveries.
- Filter by failed.
- Click Replay on individual deliveries or Replay all.
Test endpoint
For development:
- Create a webhook subscription pointing to a tunneling tool (ngrok, cloudflared).
- Trigger events from Omniflow (resolve a conversation, create a ticket).
- Inspect the request locally.
Disable a webhook
Toggle off in Settings → Integrations → Webhooks. Disabling preserves the subscription and history but stops new deliveries until re-enabled.
Open in Omniflow
Related
| If you want to… | Go to |
|---|---|
| Use the API | API Reference |
| Receive Slack notifications instead | Notifications |
| Build a custom inbound integration | Custom API & Webhooks |