# Webhooks

Vatio POSTs what happened to your server, so you never poll: what became of
each message your backend [sent](https://vatio.ai/docs/api/messages), every rating a visitor
gives a conversation, and account-level channel issues. Create an endpoint with
[`vatio webhooks create`](https://vatio.ai/docs/cli/webhooks#webhooks), in the inbox under
**Webhooks** (the **New endpoint** button at the top right), or [over the API](#endpoints).

## 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](#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](#delivery)).

Every event comes in the same envelope:

```json
{
  "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`](https://vatio.ai/docs/api/messages#lists-and-templates)
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`:

```json
{
  "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.

```json
{
  "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](https://vatio.ai/docs/api/messages#reply-to-a-comment).
Your own comments are not sent. `data.comment` is the comment as
`GET /instagram/comments/:id` returns it:

```json
{
  "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](https://vatio.ai/docs/channels/whatsapp#ratings), in the [web widget](https://vatio.ai/docs/channels/widget#visitor-feedback),
and in your app through the [SDK](https://vatio.ai/docs/sdk/reference). Every answer is a
`feedback.submitted`:

```json
{
  "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 }
  }
}
```

- `rating` is `good`, `neutral` (So-so) or `bad`.
- `comment` is 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_id` is the agent's reply that was rated, and `chat.url` opens
  the conversation in the inbox.
- `channel` is where the conversation is: `whatsapp`, `widget`, `web` (your
  page on the browser SDK), `ios` or `android`.
- `contact` is `null` when 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](https://vatio.ai/docs/channels/whatsapp#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`](https://vatio.ai/docs/sdk/ruby#webhooks) 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`](https://vatio.ai/docs/authentication/sessions#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`](https://vatio.ai/docs/cli/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.
