Eventos e payloads
Todo evento chega no mesmo envelope (id, object: "event", type, created_at, organization_id, data). Os objetos dentro de data têm exatamente o mesmo formato da API REST: mensagem, contato, mensagem agendada, canal e campanha.
| Evento | Quando dispara | data |
|---|---|---|
message.received |
Um contato mandou mensagem (WhatsApp, grupo ou Chat do site). | message, contact |
message.sent |
Uma mensagem saiu pela conta (equipe, IA, campanha, Agenda ou API). | message, contact |
message.status_updated |
O status de entrega de uma mensagem enviada mudou. | message, contact, previous_status |
contact.created |
Um contato novo entrou na base. | contact |
contact.updated |
Mudou nome, e-mail, etapa, responsável, IA, arquivamento ou finalização. | contact, changed_fields, previous |
contact.stage_changed |
O card mudou de etapa no Kanban. | contact, stage, previous_stage |
scheduled_message.sent |
Uma mensagem agendada foi enviada. | scheduled_message |
scheduled_message.failed |
Uma mensagem agendada falhou. | scheduled_message |
channel.status_updated |
Um WhatsApp conectou (o QR Code foi lido), caiu ou foi desconectado. | channel, previous_status |
campaign.status_updated |
Uma campanha foi aprovada, teve ajustes pedidos, começou a enviar, foi pausada, concluída ou falhou. | campaign, previous_status |
ping |
Teste pedido no painel (Enviar teste) ou pela API. | webhook_id, message |
Ao criar o webhook, assine eventos específicos ou * para receber todos. O ping sempre é entregue quando você pede um teste.
message.received
Seção intitulada “message.received”Um contato mandou uma mensagem para a empresa — no WhatsApp, num grupo ou no Chat do site. message.direction é sempre inbound.
{ "id": "597b7d23-208d-4b5b-82a2-fb1703a81d63", "object": "event", "type": "message.received", "created_at": "2026-11-11T14:12:40Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "message": { "id": "b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "inbound", "type": "text", "text": "Vocês entregam no sábado?", "media": null, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:12:40Z", "edited_at": null }, "contact": { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 1, "last_message_at": "2026-11-11T14:12:40Z", "last_message_preview": "Vocês entregam no sábado?", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:12:40Z" } }}Um áudio recebido chega com type: "audio", o arquivo em media e, quando disponível, a transcrição em transcript:
{ "id": "3f08322f-2c8f-4633-a478-5d25a5342f66", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "inbound", "type": "audio", "text": null, "media": { "url": "https://arquivos.exemplo.com/audios/3f08322f.ogg", "mime_type": "audio/ogg", "filename": null }, "status": "delivered", "error": null, "reply_to_id": null, "reaction": null, "sender_name": null, "transcript": "Oi, queria saber se ainda tem o tênis 39 em estoque.", "created_at": "2026-11-11T14:16:02Z", "edited_at": null}Em grupos, contact.is_group é true e message.sender_name diz quem mandou.
message.sent
Seção intitulada “message.sent”Uma mensagem saiu pela conta — enviada pela equipe no painel, pela IA, por uma campanha, pela Agenda ou pela API. message.direction é sempre outbound.
O evento pode chegar com status: "queued" (ainda na fila). Para saber se foi entregue, acompanhe message.status_updated.
{ "id": "62b273bc-c0b1-4b61-b1d4-de8d115483cd", "object": "event", "type": "message.sent", "created_at": "2026-11-11T14:30:12Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "message": { "id": "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Entregamos sim! Sábado das 8h às 14h.", "media": null, "status": "queued", "error": null, "reply_to_id": "b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba", "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:30:12Z", "edited_at": null }, "contact": { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 0, "last_message_at": "2026-11-11T14:30:12Z", "last_message_preview": "Entregamos sim! Sábado das 8h às 14h.", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:30:12Z" } }}message.status_updated
Seção intitulada “message.status_updated”O status de entrega de uma mensagem enviada mudou: queued → sent → delivered → read, ou failed. Só existe para mensagens outbound. previous_status traz o status anterior.
{ "id": "a6d85050-d17c-4ce6-ae54-4639a7f38536", "object": "event", "type": "message.status_updated", "created_at": "2026-11-11T14:31:05Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "message": { "id": "03ab9a84-86b0-4a1c-a9b2-bbfa205cc52e", "object": "message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "direction": "outbound", "type": "text", "text": "Entregamos sim! Sábado das 8h às 14h.", "media": null, "status": "read", "error": null, "reply_to_id": "b7fa4c19-3e25-4ce9-9794-4e7f60efa4ba", "reaction": null, "sender_name": null, "transcript": null, "created_at": "2026-11-11T14:30:12Z", "edited_at": null }, "contact": { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 0, "last_message_at": "2026-11-11T14:30:12Z", "last_message_preview": "Entregamos sim! Sábado das 8h às 14h.", "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:30:12Z" }, "previous_status": "delivered" }}Quando a entrega falha, message.status é failed e message.error explica o motivo.
contact.created
Seção intitulada “contact.created”Um contato novo entrou na base — por uma mensagem recebida, pelo Chat do site, pelo painel, pela API ou por outra origem (veja contact.source).
{ "id": "2d3339bd-e78f-4ebd-84b5-46afdb2b348d", "object": "event", "type": "contact.created", "created_at": "2026-11-11T15:01:18Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "contact": { "id": "6586daec-7197-478e-b0ff-5b6659c11d26", "object": "contact", "name": "Ana Lima", "phone": "5531991234567", "email": null, "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": null, "ai_enabled": true, "ai_agent_id": null, "assigned_to": null, "pinned": false, "unread_count": 0, "last_message_at": null, "last_message_preview": null, "resolved_at": null, "archived_at": null, "source": "whatsapp", "created_at": "2026-11-11T15:01:18Z", "updated_at": "2026-11-11T15:01:18Z" } }}contact.updated
Seção intitulada “contact.updated”Disparado quando muda um campo de negócio do contato:
Valor em changed_fields |
O que mudou |
|---|---|
name |
Nome. |
email |
E-mail. |
stage |
Etapa do Kanban (também dispara contact.stage_changed). |
assigned_to |
Responsável. |
ai_enabled |
IA ligada ou desligada na conversa. |
archived_at |
Arquivado ou desarquivado. |
resolved_at |
Finalizado ou reaberto. |
Ele não dispara a cada mensagem nova (contador de não lidas, prévia da última mensagem etc.) — para isso existem os eventos de mensagem.
previous traz os valores anteriores só dos campos que mudaram. Para stage, o valor anterior é um objeto de etapa (id, name, is_final) ou null.
{ "id": "417bc8d0-a2e2-492b-b54a-2d1c7a982e85", "object": "event", "type": "contact.updated", "created_at": "2026-11-11T14:35:21Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "contact": { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "3c671a88-07fb-4ccc-80db-184be834360c", "name": "Finalizado", "is_final": true }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 0, "last_message_at": "2026-11-11T14:30:12Z", "last_message_preview": "Entregamos sim! Sábado das 8h às 14h.", "resolved_at": "2026-11-11T14:35:21Z", "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:35:21Z" }, "changed_fields": ["stage", "resolved_at"], "previous": { "stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false }, "resolved_at": null } }}contact.stage_changed
Seção intitulada “contact.stage_changed”O card mudou de etapa no Kanban — pelo painel, pela IA ou pela API. stage é a etapa nova e previous_stage a anterior (qualquer uma pode ser null, quando o card entra ou sai do Kanban).
Se stage.is_final for true, o card foi para a etapa final e a conversa foi finalizada.
{ "id": "d11785e0-67f1-45b8-9796-ccf867b500ba", "object": "event", "type": "contact.stage_changed", "created_at": "2026-11-11T14:35:21Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "contact": { "id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "object": "contact", "name": "Maria Souza", "phone": "5511999998888", "email": "maria@exemplo.com", "channel_id": "d781e759-4047-4c35-bfcd-eae8c666369a", "channel_type": "whatsapp", "is_group": false, "stage": { "id": "3c671a88-07fb-4ccc-80db-184be834360c", "name": "Finalizado", "is_final": true }, "ai_enabled": true, "ai_agent_id": null, "assigned_to": "4083deb8-f96e-4093-8598-ebdd4dee786b", "pinned": false, "unread_count": 0, "last_message_at": "2026-11-11T14:30:12Z", "last_message_preview": "Entregamos sim! Sábado das 8h às 14h.", "resolved_at": "2026-11-11T14:35:21Z", "archived_at": null, "source": "whatsapp", "created_at": "2026-10-28T19:03:55Z", "updated_at": "2026-11-11T14:35:21Z" }, "stage": { "id": "3c671a88-07fb-4ccc-80db-184be834360c", "name": "Finalizado", "is_final": true }, "previous_stage": { "id": "9b2f5a8e-6a3b-4d0e-9a51-2a7f4f1d0c11", "name": "Negociação", "is_final": false } }}scheduled_message.sent
Seção intitulada “scheduled_message.sent”Uma mensagem agendada (Agenda) foi enviada. message_id aponta para a mensagem gerada — que também gera um message.sent.
{ "id": "a5ad15a7-bccf-4ff7-ac3c-64168d032258", "object": "event", "type": "scheduled_message.sent", "created_at": "2026-11-10T13:00:41Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "scheduled_message": { "id": "d3440bed-1a6f-4470-9d89-cec62fe2fc67", "object": "scheduled_message", "contact_id": "721bfe94-e549-4354-b90c-a8d679e9b271", "to": "5521987654321", "title": "Renovação do plano", "text": "Oi {{nome}}! Seu plano vence na sexta. Quer que a gente renove?", "media": [], "send_at": "2026-11-10T13:00:00Z", "status": "sent", "sent_at": "2026-11-10T13:00:41Z", "message_id": "8e395744-cc4c-4eae-9ccd-06c8dde834e0", "error": null, "created_at": "2026-11-03T18:22:10Z", "updated_at": "2026-11-10T13:00:41Z" } }}scheduled_message.failed
Seção intitulada “scheduled_message.failed”Uma mensagem agendada não pôde ser enviada. error explica o motivo (ex.: WhatsApp desconectado no horário do envio).
{ "id": "d161f19b-c31d-4378-88f7-3f6c23c3e135", "object": "event", "type": "scheduled_message.failed", "created_at": "2026-11-12T12:00:38Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "scheduled_message": { "id": "603916c7-604c-4c56-8a58-f3f76886634b", "object": "scheduled_message", "contact_id": "3f1c2b9a-7d4e-4c55-8a0e-1b2c3d4e5f60", "to": "5511999998888", "title": "Lembrete de consulta", "text": "Oi {{nome}}, passando para lembrar da sua consulta amanhã às 9h.", "media": [], "send_at": "2026-11-12T12:00:00Z", "status": "failed", "sent_at": null, "message_id": null, "error": "WhatsApp desconectado.", "created_at": "2026-11-11T14:50:00Z", "updated_at": "2026-11-12T12:00:38Z" } }}channel.status_updated
Seção intitulada “channel.status_updated”A conexão de um WhatsApp mudou: o celular leu o QR Code (ou digitou o código de pareamento) e o canal conectou, a sessão caiu ou alguém desconectou o número. data.channel é o canal já com o status novo e previous_status é o status anterior (connected, connecting, disconnected ou null).
É o jeito mais rápido de saber que o pareamento terminou — sem consultar GET /channels/{channel_id}/qr — e de avisar a equipe quando um número cai.
- Só dispara para canais
whatsapp(o Chat do site não gera este evento). - Não dispara quando o QR Code só é renovado durante o pareamento.
{ "id": "d6778bf5-f7d2-40d4-ba5e-0edaafd4af41", "object": "event", "type": "channel.status_updated", "created_at": "2026-11-11T15:21:14Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "channel": { "id": "742539b3-2c69-4acc-988f-fcd836e565a9", "object": "channel", "type": "whatsapp", "name": "Pós-venda", "phone": "5511955554444", "status": "connected", "is_primary": false, "created_at": "2026-11-11T15:20:00Z" }, "previous_status": "connecting" }}Quando um número cai, chega channel.status igual a disconnected e previous_status igual a connected: avise a equipe e gere um novo QR Code com POST /channels/{channel_id}/connect.
campaign.status_updated
Seção intitulada “campaign.status_updated”Uma campanha mudou de status: foi aprovada pela TopTalking, teve ajustes pedidos, começou a enviar, foi pausada, concluída ou falhou. data.campaign é a campanha atualizada e previous_status o status anterior.
{ "id": "a6624a26-d283-4db2-9585-58d26bbf213d", "object": "event", "type": "campaign.status_updated", "created_at": "2026-11-11T16:20:00Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "campaign": { "id": "f2143187-f4f8-43b0-9c03-57a26c575f41", "object": "campaign", "name": "Black Friday 2026", "status": "approved", "review_notes": null, "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 3, "pending": 3, "sent": 0, "delivered": 0, "read": 0, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 1.05, "charged_total": 0 }, "created_at": "2026-11-11T16:00:00Z", "submitted_at": "2026-11-11T16:00:00Z", "reviewed_at": "2026-11-11T16:20:00Z", "started_at": null, "completed_at": null }, "previous_status": "pending_approval" }}Quando a revisão pede ajustes, campaign.status vem changes_requested e campaign.review_notes explica o que mudar:
{ "id": "c3d40a38-2062-4259-b999-047a297ba3aa", "object": "event", "type": "campaign.status_updated", "created_at": "2026-11-11T16:18:40Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "campaign": { "id": "1eaa64c7-7f3c-40e6-9554-754d3e4b097c", "object": "campaign", "name": "Black Friday 2026 - lista comprada", "status": "changes_requested", "review_notes": "A lista parece incluir pessoas que nunca falaram com a loja. Envie só clientes que aceitaram receber mensagens.", "template": { "name": "promo_black_friday", "language": "pt_BR", "category": "MARKETING" }, "totals": { "recipients": 1500, "pending": 1500, "sent": 0, "delivered": 0, "read": 0, "failed": 0 }, "pricing": { "unit_price": 0.35, "currency": "BRL", "estimated_total": 525, "charged_total": 0 }, "created_at": "2026-11-11T15:50:00Z", "submitted_at": "2026-11-11T15:50:00Z", "reviewed_at": "2026-11-11T16:18:40Z", "started_at": null, "completed_at": null }, "previous_status": "pending_approval" }}Evento de teste. Enviado quando você clica em Enviar teste no painel ou chama POST /webhooks/{webhook_id}/test — mesmo que o webhook esteja desativado e mesmo que ele não assine *. Responda 2xx e ignore o conteúdo.
{ "id": "252a2c18-7b60-47a6-9feb-9c8322f2ba50", "object": "event", "type": "ping", "created_at": "2026-11-11T15:10:00Z", "organization_id": "e044a615-e879-44bb-8510-948b10b5e93c", "data": { "webhook_id": "f03b0773-5912-4a45-9139-c29375485843", "message": "Evento de teste enviado pela TopTalking." }}Tratando eventos no código
Seção intitulada “Tratando eventos no código”async function processarEvento(evento) { if (await jaProcessado(evento.id)) return; // a mesma entrega pode chegar mais de uma vez switch (evento.type) { case 'message.received': await salvarMensagem(evento.data.message, evento.data.contact); break; case 'message.status_updated': await atualizarStatus(evento.data.message.id, evento.data.message.status); break; case 'contact.stage_changed': await sincronizarEtapaNoCrm(evento.data.contact.id, evento.data.stage?.name ?? null); break; case 'channel.status_updated': if (evento.data.channel.status === 'disconnected') await avisarEquipe(evento.data.channel.name); break; case 'campaign.status_updated': await atualizarCampanha(evento.data.campaign.id, evento.data.campaign.status); break; case 'ping': break; // teste: nada a fazer default: break; // ignore tipos que você não usa (novos eventos podem surgir) } await marcarComoProcessado(evento.id);}