Documentação / Guia: como os eventos de webhook se relacionam

Guia: como os eventos de webhook se relacionam

Entrar

Guia: como os eventos de webhook se relacionam

Este guia é para quem integra com os webhooks da Pilot Status e precisa ligar um evento ao outro, em especial quando um contato responde a uma mensagem que você enviou.

O corpo dos eventos segue o formato JSON { "event": "...", "data": { ... } }. Os campos citados abaixo ficam em data, salvo indicação em contrário.


Campos que identificam mensagens

| Campo | O que representa | |-------|------------------| | messageId | Identificador da mensagem no WhatsApp (a mensagem “da conversa” a que o evento se refere naquele momento). | | internalMessageId | Identificador da mesma mensagem na Pilot Status — é o ID que você recebe ao enviar pela API (por exemplo na resposta de envio ou nos relatórios). | | correlationId | Quando existir, é um identificador adicional de correlação da mensagem enviada (útil para alinhar com o seu sistema). | | quotedMessageId | No evento de resposta, é o messageId da mensagem original que o contato está respondendo (lado WhatsApp). | | messageRepliedId | No evento de message.reply, é sempre enviado: é o internalMessageId da mensagem original quando a plataforma correlaciona o envio; caso contrário vem null (sem linha Message correlata no Pilot Status). |

Importante: quotedMessageId e messageRepliedId não são o mesmo valor. O primeiro usa o mesmo tipo de ID que messageId (WhatsApp); o segundo usa o mesmo tipo que internalMessageId (Pilot Status).


Resposta da API pública ao enviar (POST /v1/messages/send)

Quando o envio é aceito, a API responde com HTTP 202 e um JSON que inclui, entre outros:

| Campo na resposta | Uso para correlação com webhooks | |-------------------|----------------------------------| | id | É o identificador interno da mensagem na Pilot Status — o mesmo valor que aparece como internalMessageId nos webhooks de envio e status (message.sent, message.delivered, etc.) e, quando existir, como messageRepliedId no message.reply correlacionado. Guarde este campo como chave do seu lado. | | correlationId | Identificador de correlação de negócio da mensagem; pode reaparecer nos eventos de status de saída (message.sent, message.delivered, message.read, message.failed) e em message.reply / message.received quando houver correlação com o envio, alinhado ao que foi gravado no envio (HTTP 202). | | status, createdAt, origin | Úteis para exibir estado e origem; a correlação entre eventos usa sobretudo id / internalMessageId e o messageId do WhatsApp quando disponível. |

O que a resposta do envio não traz: o messageId do WhatsApp (ID da mensagem na conversa). Esse valor só existe depois que o provedor aceita a mensagem e passa a constar nos webhooks message.sent (e nos eventos de status seguintes) como messageId, juntamente com internalMessageId igual ao id que você guardou no 202.

Consulta posterior: GET /v1/messages/<id> usa o mesmo id devolvido no envio para devolver o estado da mensagem (incluindo IDs e timestamps quando já existirem).

Fluxo típico: persistir o id (e opcionalmente correlationId) ao receber o 202 → quando chegar message.sent, atualizar o registro com o messageId do WhatsApp e reutilizar internalMessageId para todos os eventos seguintes daquela mensagem.

Exemplo: pedido, resposta 202 e webhooks

Valores como cmn0… e A2FH… são ilustrativos; os seus serão diferentes.

1) Corpo do envio (pedido à API)

POST /v1/messages/send
Content-Type: application/json
x-api-key: ps_live_…
{
  "templateId": "consulta-lembrete",
  "destinationNumber": "+5511999999999",
  "variables": { "nome": "Maria", "horario": "08:15" }
}

2) Resposta imediata (HTTP 202)

{
  "id": "cmn0qk33b001kmj01q37f6quv",
  "correlationId": "cid_d815edf55caf4b80a368932cf22cdfa6",
  "status": "QUEUED",
  "createdAt": "2026-04-16T15:00:00.000Z",
  "origin": "Meu WhatsApp"
}

| O que fazer | Motivo | |-------------|--------| | Guardar id no seu banco | Será o mesmo que internalMessageId nos webhooks e (se aplicável) messageRepliedId no reply. | | Opcional: guardar correlationId | Pode repetir nos webhooks de status de saída e em message.reply / message.received correlacionados para cruzar com o seu sistema. | | Ainda não existe messageId do WhatsApp | Só aparece depois, no webhook message.sent. |

3) Webhook message.sent (mensagem aceita pelo WhatsApp)

{
  "event": "message.sent",
  "data": {
    "messageId": "A2FHM8YGTQQQH992YRT4R",
    "internalMessageId": "cmn0qk33b001kmj01q37f6quv",
    "correlationId": "cid_d815edf55caf4b80a368932cf22cdfa6",
    "environment": "LIVE",
    "destinationNumber": "+5511999999999",
    "content": "Olá Maria, lembrete: consulta às 08:15.",
    "status": "SENT",
    "sentAt": "2026-04-16T15:00:03.000Z"
  }
}

| Campo no webhook | Correlaciona com | |------------------|-------------------| | internalMessageId | O id do 202 (mesmo valor). | | messageId | Novo: ID da mensagem no WhatsApp — guarde no mesmo registro. | | correlationId | O mesmo do 202, quando vier no payload. |

4) Webhooks de status (opcionais, mesma mensagem)

message.delivered e message.read repetem o mesmo par messageId + internalMessageId (e o mesmo destinationNumber / conteúdo quando a retenção permitir), mudando só o tipo de evento e o timestamp (deliveredAt, readAt).

5) Webhook message.reply (o contato responde)

{
  "event": "message.reply",
  "data": {
    "from": "+5511999999999",
    "fromNumber": "+5511999999999",
    "destinationNumber": "+552422317827",
    "content": "",
    "replyContent": "SIM",
    "receivedAt": "2026-04-16T15:41:07.445Z",
    "messageId": "ACFC4C4DCBEFA43466256BBB207FC3B0",
    "quotedMessageId": "A2FHM8YGTQQQH992YRT4R",
    "messageRepliedId": "cmn0qk33b001kmj01q37f6quv",
    "environment": "LIVE"
  }
}

| Campo no message.reply | Correlaciona com | |---------------------------|------------------| | quotedMessageId | O messageId do message.sent do passo 3 (= mensagem original no WhatsApp). | | messageRepliedId | O id do 202 e o internalMessageId do envio (= mesma mensagem na Pilot Status). | | messageId (no reply) | Mensagem nova (a resposta do contato), não a original. |

Se messageRepliedId não vier no payload, ainda pode correlacionar por quotedMessageId = messageId guardado no passo 3.


Envio de uma mensagem e status (sem reply)

Para uma mensagem que você enviou pela API, a sequência pode incluir:

  1. message.sent — envio aceito.
  2. message.delivered — entregue ao aparelho (quando disponível).
  3. message.read — lida (quando o contato tem confirmação de leitura ativa no WhatsApp).
  4. message.failed — falha, se ocorrer.

Como saber que todos se referem à mesma mensagem: o par messageId + internalMessageId deve se repetir de um evento para o outro para aquela mensagem. Use esses campos para atualizar o mesmo registro no seu sistema.

Em situações excepcionais, alguns eventos de status podem trazer só o identificador do WhatsApp (messageId), sem internalMessageId. Nesse caso, use messageId (e o número de destino, se vier no payload) para cruzar com o que você já tinha guardado.


Quando alguém responde à sua mensagem (message.reply)

O que significa

O evento message.reply indica que a mensagem recebida foi tratada como resposta a um envio seu anterior. Se não houver esse contexto de resposta, você pode receber message.received em vez de message.reply.

Como ligar a resposta ao envio original

| O que você quer fazer | Como fazer | |------------------------|------------| | Achar a mensagem original pelo ID do WhatsApp | Compare quotedMessageId no message.reply com o messageId do message.sent da mensagem original. | | Achar a mensagem original pelo ID da Pilot Status | Compare messageRepliedId no message.reply com o internalMessageId do message.sent da mensagem original (ou com o ID retornado na API na hora do envio). | | Identificar a mensagem nova (a resposta do contato) | No message.reply, o messageId refere-se à mensagem de resposta recebida, não à mensagem que você tinha enviado antes. |

content e replyContent no message.reply

  • replyContent — texto que o contato enviou na resposta (por exemplo "SIM" ou "Não").
  • content — texto da mensagem original que está sendo respondida (a mensagem “citada” no WhatsApp), não o texto da resposta.

O content pode vir vazio ("") mesmo quando replyContent está preenchido. Isso ocorre quando o payload do WhatsApp não traz o corpo citado em um formato que a plataforma consiga expor, ou em alguns tipos de mensagem (templates, botões, etc.). Não é um bug do seu endpoint: use quotedMessageId para associar a resposta ao messageId do message.sent da mensagem original. Quando a correlação com o seu envio estiver completa, a plataforma pode preencher content a partir do texto do envio armazenado — mas isso nem sempre se aplica a todos os fluxos.

Outros campos úteis no mesmo evento: horário (receivedAt), ambiente (environment), e buttonId se a pessoa respondeu tocando em um botão interativo.

Quando messageRepliedId pode não aparecer

O campo messageRepliedId só é enviado quando a Pilot Status consegue associar com certeza a resposta ao envio feito pela sua conta (incluindo o webhook vinculado à mesma configuração de API que você usa para enviar).

Se essa associação não for possível, você pode receber message.reply sem messageRepliedId, ou receber message.received em vez de message.reply. Nesses casos, use quotedMessageId (quando vier) e o messageId do fluxo de envio para tentar cruzar manualmente no seu sistema.


Outros eventos em poucas palavras

| Evento | Ideia principal para correlação | |--------|-----------------------------------| | message.received | Mensagem recebida que não entrou como message.reply. O messageId identifica essa mensagem recebida. | | message.group | Mensagem em grupo. Use groupId para saber de qual grupo veio; para enviar de volta ao mesmo grupo, use o mesmo groupId na API conforme a documentação de envio. | | optin.created | Registro de opt-in de um contato a um projeto. Os campos principais são projectId e identificadores de destino (como destinationHash); não substituem a correlação por mensagem de conversa. |


Ambientes TEST e LIVE

Os tipos de eventos e o formato do JSON são os mesmos nos dois ambientes. O campo environment em data indica se aquele evento é de TEST (homologação) ou LIVE (produção).

Você precisa cadastrar webhooks separados para TEST e para LIVE (URL e eventos desejados). Quem configurar só o ambiente de teste não receberá automaticamente os eventos de produção.


Resumo rápido (reply)

  • No 202 do envio, guarde id (= internalMessageId nos webhooks); o messageId do WhatsApp só entra depois no message.sent.
  • replyContent = o que o contato digitou na resposta; content = texto da mensagem original citada (pode vir vazio — ver secção acima).
  • quotedMessageId = “qual mensagem no WhatsApp foi respondida” → cruze com messageId do message.sent original.
  • messageRepliedId = “qual envio na Pilot Status foi respondido” → cruze com internalMessageId do message.sent original.
  • O messageId do message.reply é a nova mensagem (a resposta), não a antiga.

Para detalhes de cada evento e exemplos de payload, consulte a documentação de webhooks disponível no painel ou no site da Pilot Status.