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.

Events

These four are the whole list. An endpoint must subscribe to at least one, and an unknown event name is refused when you register.

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. 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.

Register an endpoint

All five management calls need the webhooks:manage scope.
secret is returned once, when the endpoint is created. It always starts with whsec_. Watx keeps only an encrypted copy and no other endpoint returns it. 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.

Zapier and n8n are the same endpoints

Connecting Zapier or n8n from the dashboard registers exactly this kind of endpoint, subscribed to the same four events and delivered 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. The Send test event button lives on those pages and sends a zapier.test or n8n.test event; there is no test endpoint in the API.

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.
  • Re-enable with a PATCH. {"is_active": true} turns the endpoint back on and clears the failure counter at the same time.
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 broadcast’s counters.