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

# Webhook de Confirmação de Leitura

> Webhook assinado enviado em cada alteração de status de entrega (enviado, entregue, lido, falhado) de uma mensagem do WhatsApp que você envia

O Webhook de Confirmação de Leitura entrega um callback HTTP assinado ao seu servidor toda vez que uma mensagem do WhatsApp que você envia muda de status — `enviado`, `entregue`, `lido` ou `falhado`. Use-o para rastreamento de entrega, confirmação de leitura e trilhas de auditoria.

Ele é configurado **por remetente do WhatsApp**, então remetentes diferentes podem apontar para diferentes endpoints.

## Configuração do Webhook

Para habilitar as confirmações de leitura para um remetente:

1. Edite seu [remetente do WhatsApp](/whatsapp/senders) e abra a seção **Webhook de Confirmação de Leitura**
2. Insira sua **URL do Webhook** e salve
3. Um **Segredo de Assinatura** é gerado automaticamente — use-o para verificar a assinatura em cada solicitação

Cada payload carrega o `whatsapp_message_id` retornado pelos endpoints [Enviar Modelo](/api-reference/whatsapp/send-template) e [Enviar Livre](/api-reference/whatsapp/send-freeform), para que você possa correlacionar cada atualização com a mensagem original.

## Formato da Solicitação

O webhook é enviado como uma solicitação POST para sua URL configurada com um corpo JSON e um cabeçalho `X-Signature-256`.

### Estrutura do Payload

<ResponseField name="event" type="string">
  O tipo de evento. Valor: `message_status`
</ResponseField>

<ResponseField name="whatsapp_message_id" type="integer">
  Identificador numérico da mensagem — o mesmo `whatsapp_message_id` retornado quando você enviou a mensagem. Use isso para correlacionar a atualização de status com o envio original.
</ResponseField>

<ResponseField name="message_sid" type="string">
  Identificador da mensagem do provedor para mensagens enviadas via Twilio, ou `null`
</ResponseField>

<ResponseField name="meta_message_id" type="string">
  Identificador da mensagem do provedor (WhatsApp `wamid`) para mensagens enviadas via Meta Cloud API, ou `null`
</ResponseField>

<ResponseField name="conversation_id" type="string">
  Identificador único (UUID) da conversa à qual a mensagem pertence, ou `null`
</ResponseField>

<ResponseField name="assistant_id" type="string">
  Identificador único (UUID) do assistente conectado ao remetente, ou `null`
</ResponseField>

<ResponseField name="sender" type="object">
  O remetente do WhatsApp de onde a mensagem foi enviada

  <Expandable title="Propriedades do Remetente">
    <ResponseField name="id" type="integer">
      Identificador numérico do remetente
    </ResponseField>

    <ResponseField name="phone_number" type="string">
      O número de telefone do WhatsApp do remetente
    </ResponseField>

    <ResponseField name="display_name" type="string">
      O nome exibido do remetente
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="to" type="string">
  O número de telefone do destinatário
</ResponseField>

<ResponseField name="from" type="string">
  O número de telefone do remetente
</ResponseField>

<ResponseField name="direction" type="string">
  Direção da mensagem. Valor: `outbound`
</ResponseField>

<ResponseField name="status" type="string">
  O novo status de entrega. Valores possíveis: `enviado`, `entregue`, `lido`, `falhado`, `não entregue`
</ResponseField>

<ResponseField name="error_code" type="integer">
  Código de erro do provedor quando `status` é `falhado` ou `não entregue`, caso contrário `null`
</ResponseField>

<ResponseField name="error_message" type="string">
  Mensagem de erro bruta do provedor quando a mensagem falhou, caso contrário `null`
</ResponseField>

<ResponseField name="error_description" type="string">
  Descrição legível do erro, caso contrário `null`
</ResponseField>

<ResponseField name="timestamp" type="string">
  Timestamp ISO 8601 de quando a plataforma registrou a mudança de status, no fuso horário configurado do proprietário do número do WhatsApp
</ResponseField>

<ResponseField name="provider_timestamp" type="string">
  Timestamp ISO 8601 do próprio tempo do evento da operadora, no fuso horário configurado do proprietário. Presente para mensagens enviadas via Meta Cloud API; `null` via Twilio (o callback de status da Twilio não inclui um tempo de evento). Prefira isso quando presente — é o tempo autoritativo da operadora.
</ResponseField>

<ResponseExample>
  ```json Entregue theme={null}
  {
    "event": "message_status",
    "whatsapp_message_id": 890,
    "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "meta_message_id": null,
    "conversation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "assistant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "sender": {
      "id": 42,
      "phone_number": "+19876543210",
      "display_name": "Meu Negócio"
    },
    "to": "+1234567890",
    "from": "+19876543210",
    "direction": "outbound",
    "status": "delivered",
    "error_code": null,
    "error_message": null,
    "error_description": null,
    "timestamp": "2026-06-08T09:30:02+00:00",
    "provider_timestamp": "2026-06-08T09:30:00+00:00"
  }
  ```

  ```json Lido theme={null}
  {
    "event": "message_status",
    "whatsapp_message_id": 890,
    "message_sid": null,
    "meta_message_id": "wamid.HBgLMTIzNDU2Nzg5MBUCABEYEjk...",
    "conversation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "assistant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "sender": {
      "id": 42,
      "phone_number": "+19876543210",
      "display_name": "Meu Negócio"
    },
    "to": "+1234567890",
    "from": "+19876543210",
    "direction": "outbound",
    "status": "read",
    "error_code": null,
    "error_message": null,
    "error_description": null,
    "timestamp": "2026-06-08T09:31:12+00:00",
    "provider_timestamp": "2026-06-08T09:31:10+00:00"
  }
  ```

  ```json Falhado theme={null}
  {
    "event": "message_status",
    "whatsapp_message_id": 891,
    "message_sid": "SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "meta_message_id": null,
    "conversation_id": null,
    "assistant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "sender": {
      "id": 42,
      "phone_number": "+19876543210",
      "display_name": "Meu Negócio"
    },
    "to": "+1234567890",
    "from": "+19876543210",
    "direction": "outbound",
    "status": "failed",
    "error_code": 63016,
    "error_message": "Janela de 24h da mensagem modelo expirada",
    "error_description": "Janela de 24h da mensagem modelo expirada - o cliente deve responder primeiro",
    "timestamp": "2026-06-08T09:32:00+00:00",
    "provider_timestamp": null
  }
  ```
</ResponseExample>

## Verificando a Assinatura

Cada solicitação inclui um cabeçalho `X-Signature-256` contendo um HMAC-SHA256 do **corpo da solicitação bruta**, chaveado com o **Segredo de Assinatura** do seu remetente:

```
X-Signature-256: sha256=<hmac_sha256(raw_body, signing_secret)>
```

Recalcule a assinatura sobre o corpo bruto e compare-a usando uma comparação em tempo constante. Rejeite a solicitação se não corresponder.

<CodeGroup>
  ```javascript Node.js theme={null}
  import crypto from "crypto";

  function isValid(rawBody, signatureHeader, secret) {
    const expected =
      "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signatureHeader || "")
    );
  }
  ```

  ```php PHP theme={null}
  function isValid(string $rawBody, ?string $signatureHeader, string $secret): bool
  {
      $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);

      return hash_equals($expected, (string) $signatureHeader);
  }
  ```
</CodeGroup>

## Comportamento de Retentativa

Se seu endpoint retornar um status não-2xx ou a solicitação falhar, a entrega será reattemptada:

| Tentativa | Atraso |
| - | - |
| 1ª retentativa | 30 segundos |
| 2ª retentativa | 60 segundos |
| 3ª retentativa | 120 segundos |

Erros de servidor (5xx) e limites de taxa (429) são reattemptados. Erros de cliente (4xx) são tratados como um endpoint mal configurado e não são reattemptados.

## Notas Importantes

* O webhook é configurado **por remetente** — cada remetente pode ter sua própria URL e segredo.
* Eventos acionados pelo botão **Fazer solicitação de teste** nas configurações do remetente incluem um campo extra `test: true` e usam valores de espaço reservado. Atualizações de status reais nunca incluem `test`.
* `lido` só é acionado se o destinatário tiver as confirmações de leitura habilitadas nas configurações de privacidade do WhatsApp. `entregue` sempre é acionado.
* Os status podem chegar fora de ordem ou serem reenviados pelo provedor. Nós apenas encaminhamos o verdadeiro progresso, então você não receberá um `entregue` após um `lido` para a mesma mensagem — mas você ainda deve tratar o webhook como a fonte da verdade e desduplicar por `whatsapp_message_id` + `status`.
* `timestamp` é sempre o tempo que a plataforma registrou a mudança (no fuso horário do proprietário do número). `provider_timestamp` é o tempo autoritativo do evento da operadora quando disponível — prefira-o pela precisão e recorra ao `timestamp` quando for `null`.
* Use **Regenerar** nas configurações do remetente para girar o segredo de assinatura se ele for exposto.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.