#Webhooks
Vatio POSTs what happened to your server, so you never poll: what became of
each message your backend sent, every rating a visitor
gives a conversation, and account-level channel issues. Create an endpoint with
vatio webhooks create, in the inbox under
Webhooks (the New endpoint button at the top right), or over the API.
#Events
| Event | When |
|---|---|
message.sent | A message your backend sent went out to WhatsApp |
message.failed | Vatio has no safe attempts left; data.message.error says why |
message.delivered, message.read | Meta says it reached the phone, or was read |
message.delivery_failed | Meta marked it undelivered and Vatio has no attempts left |
message.replied | The person answered, once per message; data.reply has the text |
feedback.submitted | A visitor rated the conversation, or added a comment to their rating |
channel.issue | Meta reported an account-level WhatsApp problem, such as billing or eligibility; emitted once until the number becomes healthy again |
instagram.comment | Someone commented on your Instagram post, reel or live; see Instagram comments |
ping | A test from vatio webhooks test or Send test in the inbox |
An endpoint hears the events it lists, or all of them when its list is empty.
New types are added over time, so answer 2xx to one you do not handle: a
type that keeps failing switches the whole endpoint off (Delivery).
Every event comes in the same envelope:
{ "id": "evt_3141", "type": "message.replied", "created_at": "…", "workspace": "acme", "environment": "live", "data": { "…": "…" }}data is written when it happened, so a retry an hour later still says what
was true then.
#Messages
data.message is the message as GET /messages/:id
returns it. Internal retries keep the same message ID and idempotency key;
only a terminal failure produces a failure event. message.sent may occur
again if Meta first accepts an attempt and later marks its delivery failed.
message.replied adds
data.reply:
{ "type": "message.replied", "data": { "message": { "id": 81, "status": "sent", "external_ref": "order-1042", "...": "…" }, "reply": { "chat_message_id": 90433, "content": "Great, send me the link", "at": "…" } }}#Channel issues
An account-level WhatsApp error marks the number as needing attention in the
inbox and sends one channel.issue event, even if many messages fail for the
same reason. Vatio automatically retries confirmed failures, including payment
eligibility code 131042; fix the cause in Meta so those attempts can succeed.
Use Check on the inbox's WhatsApp page to refresh the channel health.
{ "type": "channel.issue", "data": { "channel": "whatsapp", "phone_number_id": "123456789", "error": { "code": "131042", "message": "Business eligibility payment issue" } }}#Instagram comments
Each comment on your Instagram account's posts, reels and lives, once, so your
backend can answer it with a private reply.
Your own comments are not sent. data.comment is the comment as
GET /instagram/comments/:id returns it:
{ "type": "instagram.comment", "environment": "live", "data": { "comment": { "id": "17895695668004550", "text": "Price?", "from": { "id": "17841400000000000", "username": "ana.perez" }, "media": { "id": "17918195224117851", "product_type": "FEED" }, "parent_id": null, "live": false, "created_at": "…", "reply_until": "…", "replied_message_id": null } }}#Ratings
When a conversation wraps up, Vatio asks the visitor how it went: on
WhatsApp, in the web widget,
and in your app through the SDK. Every answer is a
feedback.submitted:
{ "type": "feedback.submitted", "data": { "feedback": { "rating": "good", "comment": null, "question": "¿Cómo te ayudó Sofía?", "chat_message_id": 90433, "submitted_at": "…" }, "chat": { "id": 9120, "channel": "whatsapp", "url": "https://inbox.vatio.ai/acme/9120" }, "contact": { "id": 4211, "name": "Ana Pérez", "phone_number": "+56911112222", "email": null } }}ratingisgood,neutral(So-so) orbad.commentis what the visitor wrote with a So-so or Bad rating in the widget or your app; WhatsApp has none. A comment added after the rating sends the event again, with the same rating and the comment.chat_message_idis the agent's reply that was rated, andchat.urlopens the conversation in the inbox.channelis where the conversation is:whatsapp,widget,web(your page on the browser SDK),iosorandroid.contactisnullwhen Vatio knows nothing about the visitor; on WhatsApp it has the phone.
On WhatsApp the agent answers the rating itself, so asking a happy customer for a review is an instruction, not code: see Ratings. The webhook is for what your backend does with it, like storing it next to the customer or alerting someone about a bad one.
#Signature
Every delivery carries Vatio-Event, Vatio-Delivery (the event id) and
Vatio-Signature: t=1696262400,v1=…. To verify it, compute the hex
HMAC-SHA256 of "#{t}.#{raw body}" with the endpoint's secret, compare it in
constant time, and reject a t older than five minutes. In Ruby, the
vatio-sdk gem's receiver does it for you.
The same secret signs the one request Vatio sends that is not an event: the
contact.identify lookup to your auth.identify_url,
which asks who a WhatsApp number is and waits for your answer.
#Delivery
Answer 2xx within ten seconds. Anything else, including a redirect, is
retried with backoff for about a day. After 50 failures in a row the endpoint
is switched off until you enable it again. Deliveries are at least once, so
dedupe on id.
#Endpoints
You register and manage endpoints with vatio webhooks (list,
create, test, deliveries, enable, rm) or in the inbox under
Webhooks. create prints the signing secret once; test queues a ping
and deliveries shows the last 50 deliveries and how each went.
The URL must be https on a public host. Each endpoint belongs to one
environment and hears only that environment's events, so a staging server
never receives a real customer's rating.