> ## Documentation Index
> Fetch the complete documentation index at: https://developers.nuvia.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receba eventos da Nuvia no seu servidor: formato do payload, eventos disponíveis e garantias de entrega

Um webhook faz o caminho inverso da API: em vez de você consultar a Nuvia, a Nuvia envia um `POST`
para o seu servidor sempre que algo acontece na sua operação — uma mensagem chega, um contato é
criado, um campo é preenchido pelo agente.

<Note>
  Cada empresa tem **no máximo um** webhook. Não é uma coleção: você configura uma URL e escolhe
  quais eventos ela recebe. Tentar criar um segundo devolve `409`.
</Note>

## Configurar

`POST /v1/webhooks` com a URL e a lista de eventos. Escopo necessário: `webhooks:create`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.nuvia.ai/v1/webhooks \
    -H "Authorization: Bearer $NUVIA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://seu-servidor.com/nuvia/webhook",
      "events": ["MESSAGE_RECEIVED", "CONTACT_CREATED"],
      "custom_headers": { "X-Meu-Token": "um-segredo-seu" },
      "status": "ACTIVE"
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch("https://api.nuvia.ai/v1/webhooks", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NUVIA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url: "https://seu-servidor.com/nuvia/webhook",
      events: ["MESSAGE_RECEIVED", "CONTACT_CREATED"],
      custom_headers: { "X-Meu-Token": "um-segredo-seu" },
      status: "ACTIVE",
    }),
  });
  ```

  ```python Python theme={null}
  import os, requests
  requests.post(
      "https://api.nuvia.ai/v1/webhooks",
      headers={"Authorization": f"Bearer {os.environ['NUVIA_API_KEY']}"},
      json={
          "url": "https://seu-servidor.com/nuvia/webhook",
          "events": ["MESSAGE_RECEIVED", "CONTACT_CREATED"],
          "custom_headers": {"X-Meu-Token": "um-segredo-seu"},
          "status": "ACTIVE",
      },
  )
  ```
</CodeGroup>

| Campo            | Tipo                   | Obrigatório | Descrição                                                            |
| ---------------- | ---------------------- | ----------- | -------------------------------------------------------------------- |
| `url`            | string                 | Sim         | URL que vai receber os `POST`.                                       |
| `events`         | string\[]              | Sim         | Ao menos um evento da tabela abaixo.                                 |
| `custom_headers` | objeto                 | Não         | Headers extras enviados em toda entrega — use para validar a origem. |
| `status`         | `ACTIVE` \| `INACTIVE` | Não         | Default `ACTIVE`. Em `INACTIVE` nada é entregue.                     |

## O que a Nuvia envia

Toda entrega é um `POST` com estes headers:

| Header            | Valor                                             |
| ----------------- | ------------------------------------------------- |
| `Content-Type`    | `application/json`                                |
| `X-Webhook-Event` | O nome do evento, igual ao campo `event` do corpo |
| *(seus headers)*  | Tudo que você configurou em `custom_headers`      |

E o corpo tem sempre a mesma estrutura de três campos:

```json theme={null}
{
  "event": "MESSAGE_RECEIVED",
  "timestamp": "2026-08-13T14:03:00.000Z",
  "data": {}
}
```

* **`event`** — o evento que disparou a entrega.
* **`timestamp`** — ISO 8601 UTC, gerado no momento em que a entrega foi enfileirada.
* **`data`** — o recurso relacionado ao evento (veja a tabela abaixo).

<Warning>
  O corpo **não** identifica a empresa. Não existe `companyId` no envelope — se o seu servidor
  atende várias empresas da Nuvia, use uma URL por empresa ou um valor distinto em
  `custom_headers` para saber de quem é o evento.
</Warning>

## Eventos disponíveis

| Evento                 | Quando dispara                             | O que vem em `data`                    |
| ---------------------- | ------------------------------------------ | -------------------------------------- |
| `MESSAGE_RECEIVED`     | Mensagem recebida de um contato            | A mensagem                             |
| `MESSAGE_SENT`         | Mensagem enviada pela sua operação         | A mensagem                             |
| `MESSAGE_DELIVERED`    | WhatsApp confirmou a entrega               | A mensagem, com o status já atualizado |
| `MESSAGE_READ`         | O contato leu a mensagem                   | A mensagem, com o status já atualizado |
| `CONVERSATION_CREATED` | Nova conversa aberta                       | A conversa                             |
| `CONVERSATION_UPDATED` | Conversa alterada                          | A conversa                             |
| `CONTACT_CREATED`      | Novo contato criado                        | O contato                              |
| `CONTACT_UPDATED`      | Contato alterado                           | O contato                              |
| `FOLLOWUP_SENT`        | Follow-up disparado pelo agente            | O follow-up                            |
| `FIELD_UPDATED`        | Campo personalizado preenchido ou alterado | Estrutura própria (veja abaixo)        |

Nos eventos de mensagem, conversa e contato, `data` traz o recurso no mesmo formato que a
[Referência da API](/api-reference) devolve nos `GET` correspondentes — por exemplo,
`GET /v1/messages/conversation/{id}` para mensagens.

<Note>
  Trate `data` de forma tolerante: leia os campos que você usa e ignore o resto. Campos novos podem
  aparecer conforme a plataforma evolui, e isso não é considerado quebra de contrato.
</Note>

### `FIELD_UPDATED` é diferente

Esse evento não devolve uma entidade, e sim a descrição da alteração:

```json theme={null}
{
  "event": "FIELD_UPDATED",
  "timestamp": "2026-08-13T14:03:00.000Z",
  "data": {
    "field": {
      "_id": "65f1a2b3c4d5e6f7a8b9c0d1",
      "slug": "orcamento",
      "title": "Orçamento",
      "context": "contact"
    },
    "value": "50000",
    "contactId": "65f1a2b3c4d5e6f7a8b9c0d2",
    "conversationId": "65f1a2b3c4d5e6f7a8b9c0d3",
    "companyId": "65f1a2b3c4d5e6f7a8b9c0d4"
  }
}
```

`field.context` indica a que o campo pertence: `contact`, `conversation` ou `business`. `value` tem
o tipo do campo — string, número, booleano ou lista. `conversationId` só vem quando a alteração
aconteceu dentro de uma conversa.

## Validar que a chamada veio da Nuvia

<Warning>
  **Não há assinatura HMAC.** A Nuvia não assina o corpo da requisição, então não existe como
  verificar criptograficamente a origem.
</Warning>

O mecanismo disponível é o `custom_headers`: cadastre um valor secreto e confira em toda requisição.

```javascript Node.js theme={null}
app.post("/nuvia/webhook", (req, res) => {
  if (req.get("X-Meu-Token") !== process.env.NUVIA_WEBHOOK_TOKEN) {
    return res.sendStatus(401);
  }
  res.sendStatus(200); // responda primeiro
  processarEmBackground(req.body); // processe depois
});
```

Use HTTPS e um valor longo e aleatório. Como o segredo viaja em header a cada entrega, trate-o como
credencial: rotacione com `PUT /v1/webhooks/{id}` se suspeitar de vazamento.

## Garantias de entrega

Estas são as características reais da entrega. Leia antes de construir algo que dependa de webhook:

<AccordionGroup>
  <Accordion title="Não há retry — uma tentativa por evento" icon="rotate-left">
    Se o seu servidor estiver fora do ar, devolver `5xx` ou estourar o tempo limite, **o evento é
    perdido**. A falha é registrada do lado da Nuvia, mas nada é reenviado. Para dados críticos,
    reconcilie periodicamente com a API (por exemplo, listando mensagens da conversa) em vez de
    confiar só no webhook.
  </Accordion>

  <Accordion title="Tempo limite de 10 segundos" icon="clock">
    A Nuvia aguarda no máximo 10 segundos pela sua resposta. Responda `2xx` imediatamente e faça o
    processamento em background — se você processar antes de responder, entregas legítimas viram
    timeout.
  </Accordion>

  <Accordion title="A ordem não é garantida" icon="arrow-down-up-across-line">
    Os eventos de status vêm do WhatsApp, que pode entregá-los fora de ordem (um `MESSAGE_DELIVERED`
    chegando depois do `MESSAGE_READ` da mesma mensagem). A Nuvia não reordena. Trate cada evento
    pelo estado que ele carrega, sem assumir sequência.
  </Accordion>

  <Accordion title="Eventos de status só disparam na transição real" icon="filter">
    `MESSAGE_DELIVERED` e `MESSAGE_READ` só são enviados quando o status realmente muda. O WhatsApp
    reenvia o mesmo status várias vezes, e essas repetições são descartadas — você não recebe
    duplicata por causa disso. Ainda assim, escreva um handler idempotente: use o identificador da
    mensagem como chave.
  </Accordion>

  <Accordion title="Nada é entregue com o webhook inativo" icon="power-off">
    Com `status: "INACTIVE"`, ou para eventos que você não incluiu em `events`, a Nuvia não enfileira
    entrega alguma — e não há histórico para recuperar depois. Reativar não recupera o período
    parado.
  </Accordion>
</AccordionGroup>

## Gerenciar a configuração

| Ação      | Chamada                    | Escopo            |
| --------- | -------------------------- | ----------------- |
| Consultar | `GET /v1/webhooks`         | `webhooks:read`   |
| Atualizar | `PUT /v1/webhooks/{id}`    | `webhooks:update` |
| Remover   | `DELETE /v1/webhooks/{id}` | `webhooks:delete` |

O `GET` devolve **um objeto** com a configuração da empresa, ou `null` se não houver nenhuma — não é
uma lista. O `id` usado no `PUT` e no `DELETE` vem desse objeto. Depois de remover, a empresa volta a
poder criar um webhook novo.

Para pausar temporariamente sem perder a configuração, prefira `PUT` com `status: "INACTIVE"` em vez
de apagar.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Exemplos de integração" icon="plug" href="/guias/exemplos-integracao">
    O webhook dentro de um caso de uso completo, com resposta automática.
  </Card>

  <Card title="Referência da API" icon="code" href="/api-reference/webhooks">
    Parâmetros e respostas dos endpoints de webhook.
  </Card>
</CardGroup>
