Skip to main content
Instead of polling, register an endpoint and Watx will POST to it when something happens in your workspace. Every delivery is signed, retried on failure, and identified so you can deduplicate. You can register an endpoint two ways, and both manage the same endpoints:
  • in the dashboard, under Settings → Developer → Webhooks, with no API key needed (see Developer settings);
  • through the API, with a key that has the webhooks:manage scope (see Register an endpoint below).

Events

An endpoint must subscribe to at least one event, and an unknown event name is refused when you register. Some things do not fire an event today, so do not wait for one:
  • conversation.created does not fire for a conversation that Watx opens itself, such as the first POST /messages to a new number, or for website chats.
  • contact.created does not fire for contacts added in the dashboard, imported from a file, created by a form, a store order or a booking, or for website visitors.
  • Cal.com bookings do not fire the booking.* events.
form.submitted and the three booking.* events could not be subscribed to before 19 September 2026. If you tried and were refused, try again.

The delivery

Each delivery is a POST with this envelope:
  • id identifies the event, not the attempt. A retry carries the same id, so deduplicate on it.
  • occurred_at is when the thing happened, not when we last tried to tell you.
  • account_id is the workspace. It is the same for every delivery to a given endpoint.
Three headers ride along:
The same three values are also sent as X-Converse360-Event, X-Converse360-Webhook-Id and X-Converse360-Signature. They are the retired names, kept so receivers written before the rename keep working. Read either pair, not both; new receivers should read X-Watx-*.

data per event

message.received carries channel on Instagram and website events and omits it on WhatsApp, where the platform message id is whatsapp_message_id. A contact created from an Instagram thread has phone: null and carries instagram_username instead. Treat data as an open object: read the keys you need and ignore the rest. On Instagram, conversation.created also fires when someone opens the conversation from an ad or a link, and then carries Meta’s referral object as received. That can happen for a conversation that already existed, so use conversation_id to tell whether you have seen it before. message.status_updated reports WhatsApp delivery only — Instagram has no delivery receipt. The same status can arrive more than once and statuses can arrive out of order, because that is how the platform reports them. form.submitted carries the answers in data, keyed by each field’s key. Its source says where the form was filled in: hosted, embed, widget, whatsapp_flow, api or automation. contact_id is null when the submission could not be matched to a contact. booking.cancelled and booking.rescheduled carry the same keys as booking.created; after a reschedule, starts_at and ends_at are the new time. status is the booking’s status at the moment of the event.

Register an endpoint

From the dashboard

Open Settings → Developer → Webhooks and press Add endpoint. Enter the URL, tick the events, and copy the signing secret from the confirmation. The same screen pauses and resumes an endpoint, sends a test event, and removes it. See Developer settings.

Through the API

All five management calls need the webhooks:manage scope.
secret is returned once, when the endpoint is created, whether you create it in the dashboard or through the API. It always starts with whsec_. Watx keeps only an encrypted copy and nothing returns it again. Store it with your other credentials; if you lose it, delete the endpoint and register a new one.
The url must be https:// and must resolve to a public address. Loopback, private network and link-local targets are refused — at registration and again on every delivery attempt, because DNS can change underneath us. The rest of the management surface:
  • GET /webhooks — list every endpoint in the workspace. Never returns secrets.
  • GET /webhooks/{id} — read one.
  • PATCH /webhooks/{id} — change url, events or is_active.
  • DELETE /webhooks/{id} — remove one. Deliveries stop immediately.
There is no API call that sends a test event. Use Test on the endpoint in Settings → Developer → Webhooks: it sends a signed event named api.test straight away and shows you what your endpoint answered.

Zapier and n8n are the same endpoints

Connecting Zapier or n8n from the dashboard registers exactly this kind of endpoint, delivered and signed the same way. Each row carries a read-only provider of api, zapier or n8n, saying which surface created it. GET /webhooks returns all of them, so you will see endpoints you did not create through the API — and deleting one here disconnects that integration. provider has no effect on delivery; it only decides which dashboard page manages the endpoint. Settings → Developer → Webhooks lists only api endpoints, and the Zapier and n8n pages list only their own. The Zapier and n8n pages offer the four messaging and contact events. Their test buttons send zapier.test and n8n.test respectively.

Verify the signature

v1 is HMAC-SHA256(secret, "<t>.<raw request body>"), hex-encoded. Compute it over the raw bytes of the request — a body that has been parsed and re-serialised will not match — compare in constant time, and reject a timestamp older than a few minutes so an old delivery cannot be replayed at you.
The timestamp is fresh on every attempt, so a delivery retried twenty minutes later still verifies — the t it carries is the time that attempt was made.

Delivery, retries and auto-disable

  • Success is any 2xx. Anything else is a failure. Answer quickly and do your work afterwards: a delivery that takes longer than 5 seconds is abandoned and treated as a failure.
  • Redirects are not followed. A 301 or 302 counts as a failure, so register the final URL.
  • Retries. A timeout, a connection error, a 5xx, a 408 or a 429 is retried — up to 5 attempts, with exponential backoff starting at about 3 seconds. Every other 4xx is treated as permanent and not retried: your receiver has said the request is wrong, and it will be just as wrong later.
  • One endpoint at a time. Each endpoint gets its own delivery, so one receiver being down does not delay or duplicate anyone else’s.
  • Auto-disable. A failed event — after its retries are spent — counts once against the endpoint. After 15 consecutive failures the endpoint is set is_active: false and deliveries stop. Any successful delivery resets the count to zero. The dashboard shows the count as N failed in a row before that happens.
  • Re-enable. Switch the endpoint back on in Settings → Developer → Webhooks, or send {"is_active": true} in a PATCH. Either one turns the endpoint back on and clears the failure counter.
failure_count and last_delivery_at on each endpoint are how you check this from the outside: a climbing count with an old last_delivery_at means your receiver is refusing deliveries.
Order is not guaranteed, and neither is exactly-once. Deliveries can arrive out of order, and an event whose receipt we never saw can arrive twice. Make your handler idempotent on id, and treat the API’s read endpoints as the source of truth when something must be certain — GET /conversations/{id}/messages for a thread, GET /broadcasts/{id} for a campaign’s counters.