Skip to main content
A webhook endpoint is an https:// address on your server that OMNI tells about what happened. Add endpoints in Developer Tools → Webhooks, or with POST /v1/webhooks; each asks for the events it wants, or * for all of them, including events added later.
The webhooks screen, listing endpoints with their events and whether each is working

Developer Tools → Webhooks

What arrives

Each delivery is one POST with a JSON body:
and these headers: Answer with any 2xx within 10 seconds. Do slow work after answering. Redirects are not followed.

Checking the signature

Every delivery is signed with the endpoint’s secret (whsec_…, shown once when the endpoint is made, or when you make a new one). The signature is the HMAC-SHA256, in hex, of "<t>.<body>" — the timestamp, a full stop, and the raw body exactly as received. Check it before trusting the body, and refuse deliveries whose t is more than a few minutes old:
Use the raw body bytes. A body parsed and re-encoded by your framework will not match.

Retries, and an endpoint switched off

A delivery not answered 2xx is tried again after 30 seconds, 5 minutes, 30 minutes, 2 hours and 12 hours, then given up. An endpoint that has failed for three days without a single success is switched off, and says so in the panel; switch it back on once it’s fixed. Deliveries can arrive more than once and out of order. Use X-FireFlo-Delivery (or the event’s id) to ignore a repeat, and the times in data rather than arrival order. Every endpoint’s deliveries — each event, its answer and its tries — are in Developer Tools → Webhooks → the endpoint, where any of them can be sent again, and in GET /v1/webhooks/{endpoint_id}/deliveries. Send a test sends a webhook.test event at once.

Events

A message, with failover

data.message is the message as GET /v1/messages/{message_id} shows it, with every attempt.

A message on a channel

Every message any channel carries — sent through the API, a broadcast, the Inbox or an AI agent. data.channel_message:

A call

data.call is the call as GET /v1/calls/{call_id} shows it, with its channel.

Testing

webhook.test — sent only when you ask, with data.endpoint naming the endpoint.