Webhooks
Webhooks push Orqestra events to your own system the moment they happen: a customer sends a message,
a chat is assigned, a ticket is resolved, a broadcast finishes. Instead of polling the API, you give
Orqestra an HTTPS URL and pick the events you care about. Orqestra then sends a signed
POST for each one.
Typical uses: copy every conversation into your CRM, open a case in your helpdesk when a chat is escalated, notify a Slack channel when a broadcast completes, or record ad leads in your own analytics.
How it works
- Something changes in Orqestra — for example, a customer sends a WhatsApp message.
- Orqestra records an event in the same database transaction as the change. An event is never lost, and it never describes a change that did not commit.
- Every active endpoint subscribed to that event gets a delivery: an HTTPS
POSTwith a JSON body, signed with that endpoint's secret. - Your server checks the signature and answers
2xxwithin 10 seconds. If it does not, Orqestra retries for up to 24 hours.
Webhooks and the REST API work together. A webhook tells you that something changed; the API reads the current state and acts on it.
Quick start
1. Build a receiver
You need a public HTTPS URL that accepts POST and answers with any 2xx status.
Start with the smallest handler that works, then add
signature verification before you act on anything it receives.
import express from 'express'
const app = express()
app.post('/webhooks/orqestra', express.raw({ type: 'application/json' }), (req, res) => {
const event = JSON.parse(req.body.toString('utf8'))
console.log(event.type, event.id)
res.sendStatus(200)
})
app.listen(3000)
Developing locally? Orqestra only delivers to public HTTPS addresses. It refuses
localhost and private networks, so expose your local server through an HTTPS tunnel such
as ngrok or Cloudflare Tunnel and use the tunnel's URL.
2. Add the endpoint
In the dashboard, open Developers → Webhooks and click Add endpoint.
| Field | What to enter |
|---|---|
| Name | A label for your team, such as “CRM sync”. |
| HTTPS endpoint URL | Your receiver's full URL. It must be https://, with no
username/password and no #fragment. Query strings are allowed. |
| Channel events | Events that happen on a channel — messages, chats, sessions, broadcasts, orders, automation runs — and the channel instances to send them for. Tick each WhatsApp number, Instagram account, etc. that the endpoint should hear about. |
| Organization events | Events that belong to the whole organization: contacts and tickets. Only users with access to every channel can select these. |
Nothing is pre-selected, and “select all” selects what exists today. A channel you connect later, or an event type Orqestra adds later, is not sent to an existing endpoint until you edit the endpoint and tick it. That way a new WhatsApp number never starts flowing into an integration that wasn't built for it.
3. Copy the signing secret
Creating the endpoint shows its signing secret once. Store it where your receiver can read it, such as an environment variable or secret manager. If you lose it, use Rotate signing secret to issue a new one. See Rotating the secret.
4. Send a test, then activate
A new endpoint starts as Needs test. Click Send test: Orqestra POSTs a
webhook.test event, signed like any real delivery. If your URL answers 2xx,
the endpoint becomes Inactive (verified but not yet receiving), and you can click
Activate.
Delivery starts at that moment. Events from before activation are never sent: there is no backfill. To catch up on history, read it through the API.
Endpoint status
- Needs test — new, or its URL changed. Nothing is delivered. Next: send a test that
gets a
2xx. - Inactive — verified, but switched off. Nothing is delivered, and events that happen now are not queued for later. Next: activate.
- Active — receiving every subscribed event.
Orqestra never deactivates an endpoint on its own, even if it keeps failing. Failures show up in the endpoint's delivery history instead. See Delivery & Retries.
What a delivery looks like
Every delivery is a POST with these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Orqestra-Webhooks/1.0 |
X-Orqestra-Event-Id | The event ID — identical to id in the body, and
the same on every retry and redelivery. Deduplicate on this. |
X-Orqestra-Delivery-Id | This delivery to this endpoint. It stays the same across automatic retries and changes when you redeliver manually. |
X-Orqestra-Timestamp | Unix time in seconds when this attempt was signed. |
X-Orqestra-Signature | v1=<hex HMAC-SHA256>. Two comma-separated values
during a secret rotation. See Verifying Signatures. |
The body is a JSON envelope. Every event has the same outer fields, and what happened is in
data:
{
"apiVersion": "2026-09-01",
"channel": {
"id": "cmf0q9b2d0003chn7docs0002",
"label": "Customer Care WA",
"type": "WHATSAPP"
},
"createdAt": "2026-09-24T02:15:07.000Z",
"data": {
"message": {
"caption": null,
"chatId": "cmf2c5h8p0013cht7docs0004",
"contactId": "cmf2c4t7n0011cnt7docs0003",
"createdAt": "2026-09-24T02:15:07.000Z",
"deletedAt": null,
"direction": "INBOUND",
"externalId": "wamid.HBgNNjI4MTIzNDU2Nzg5MBUCABIYFDNBQjE2QzY0MUE0NzJGOTg4QkQ5AA==",
"id": "cmf2c7m3s0017msg7docs0009",
"media": null,
"referral": null,
"replyToMessageId": null,
"sessionId": "cmf2c6k1r0015ses7docs0005",
"status": "RECEIVED",
"subject": null,
"text": "Halo, pesanan saya #1042 sudah dikirim belum?",
"type": "text",
"updatedAt": "2026-09-24T02:15:07.000Z"
}
},
"id": "evt_7f3a9c2e41b84d0f9e6a5b3c2d1e0f98",
"organizationId": "cmf0q8z1k0000org7docs0001",
"type": "message.received"
}
Envelope fields
| Field | Description |
|---|---|
id | Unique event ID. Treat it as an opaque string; don't parse the prefix. |
type | What happened, e.g. message.received. See the Event Reference. |
apiVersion | The payload contract version, currently 2026-09-01. |
createdAt | When the change happened in Orqestra (UTC, ISO 8601). Use this for ordering; not the timestamp header, which changes on every retry. |
organizationId | Your organization. |
channel | For channel events, { id, type, label } of the channel instance, as it was
when the event happened. type is one of WHATSAPP, INSTAGRAM, WEBCHAT, TELEGRAM, TIKTOK, EMAIL. For organization events, null. |
data | The event's payload. Its shape depends on type. |
Keys arrive in alphabetical order, as compact JSON. Don't depend on key order or whitespace, and
ignore fields you don't recognise. New fields can appear within the same
apiVersion. Removing, renaming or retyping a field only happens under a new version.
Channel events and organization events
19 event types belong to a channel. They are sent only for the
channel instances you tick on the endpoint, and channel in the envelope says which one.
8 event types — contacts and tickets — belong to the organization.
They aren't tied to any channel, arrive with channel: null, and ignore the channel
selection.
One endpoint can receive both kinds. If it subscribes to any channel event, it needs at least one channel selected before it can be activated.
Managing an endpoint
Each row on Developers → Webhooks has these actions:
- Edit — change the name, events or channels at any time. Changing the URL sets the endpoint back to Needs test, cancels its pending deliveries, and requires a new successful test before it can be activated again.
- Send test — sends a
webhook.testevent. A test never switches off an active endpoint, even when it fails. - Rotate signing secret — issues a new secret. The old one keeps working for 24 hours.
- Delivery history — every delivery from the last 14 days, each attempt's response code and timing, and Redeliver.
- Deactivate — stops delivery immediately and cancels anything still retrying.
- Delete — removes the endpoint for good.
Who can manage webhooks
Webhooks share the API tokens permission family. A role needs:
| To | Permission |
|---|---|
| See endpoints and delivery history | api_tokens.view |
| Create and edit endpoints, send tests, activate, rotate secrets, redeliver | api_tokens.create |
| Deactivate or delete endpoints | api_tokens.revoke |
A user limited to some channels can only select those channels, can't select organization events, and only sees endpoints that stay within their channels.
Before you go live
- Verify
X-Orqestra-Signatureon every request, against the raw body. - Store the event (or put it on a queue), answer
2xx, then do the slow work. - Deduplicate on the event
id. The same event can arrive more than once. - Don't assume events arrive in order. Compare
createdAt/updatedAt, or read the current state from the API. - Ignore unknown fields and unknown event types.
- Keep the signing secret out of source control, and plan how you will rotate it.