Pular para o conteúdo

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.

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.

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"
}
}
}

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.

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"
}
}
}

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
}
}
}

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
}
}
}

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"
}
}
}

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"
}
}
}

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.

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."
}
}
Roteando por tipo (Node.js)
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);
}