Pular para o conteúdo principal

Eventos do webhook

8 min de leituraAtualizado em 29 out. 2026

A cada evento, a Atalia faz um POST com JSON para o endereço cadastrado em Chave e webhook. Esta página traz o formato exato de cada um, com exemplos completos para você (ou a sua IA) escrever o código que recebe.

Como chega​

HeaderExemploO que é
Content-Typeapplication/jsonSempre JSON em UTF-8.
X-Atalia-Eventmessage.receivedO tipo do evento, o mesmo do campo type.
X-Atalia-Event-Idevt_9f2c4b1a7e3d5c6b8a0f1e2dO id do evento, o mesmo do campo id.
X-Atalia-Signaturet=1793206931,v1=5c8f…A assinatura do corpo. Veja Verificar a assinatura.
User-AgentAtalia-Webhooks/1.0

Todo evento tem o mesmo envelope:

{
"id": "evt_9f2c4b1a7e3d5c6b8a0f1e2d",
"type": "message.received",
"created_at": "2026-10-28T17:02:11.482Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": { "id": "ch_2f81a9c0d1", "type": "whatsapp", "phone": "+55 11 98765-4321" },
"data": { }
}
CampoO que é
idId único do evento (evt_ + 24 caracteres). Use para ignorar repetidos.
typeO tipo do evento (lista abaixo).
created_atQuando o evento foi criado, em ISO 8601 (UTC).
company_idA sua conta na Atalia.
client_idSó para revenda: o cliente dono do canal. null nos seus próprios canais.
channelA caixa de entrada: id (ch_...), type (whatsapp, whatsapp_web, instagram ou messenger) e o identificador dela (phone no WhatsApp, username no Instagram, page_name no Messenger). null nos eventos que não são de um canal.
dataO conteúdo, diferente em cada tipo.
Duas datas, dois formatos

O created_at do envelope é ISO 8601 em UTC. O created_at de dentro da mensagem (data.message.created_at) é o horário em que ela foi gravada, no formato "YYYY-MM-DD HH:MM:SS" e no horário de Brasília, igual ao de GET /api/v1/messages/{id}.

Os eventos​

EventoQuando
message.receivedUm cliente mandou mensagem.
message.echoAlguém respondeu pelo app (WhatsApp Business em coexistência, Instagram ou Messenger).
message.statusUma mensagem enviada mudou de situação.
channel.connectedUma caixa de entrada foi conectada ou reconectada.
channel.disconnectedUma caixa de entrada foi apagada.
client.connectedRevenda: um cliente conectou o primeiro canal pela sua página de conexão.
client.no_slotRevenda: um cliente tentou conectar e o seu pacote estava cheio.
pingTeste enviado pelo botão "Enviar mensagem de teste" ou por POST /api/v1/webhook/test.

message.received​

Um cliente mandou mensagem. Para responder, use o conversation_id em POST /api/v1/messages.

{
"id": "evt_9f2c4b1a7e3d5c6b8a0f1e2d",
"type": "message.received",
"created_at": "2026-10-28T17:02:11.482Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": { "id": "ch_2f81a9c0d1", "type": "whatsapp", "phone": "+55 11 98765-4321" },
"data": {
"conversation_id": "48213",
"message": {
"id": "6f1c2a7e-0b8e-4d0a-9a51-3f7c1e2d9b10",
"external_id": "wamid.HBgNNTUxMTk4NzY1NDMyMRUCABIYFjNFQjA",
"direction": "inbound",
"type": "text",
"content_type": null,
"text": "Oi! Vocês entregam no centro hoje?",
"media": null,
"status": "delivered",
"contact": { "name": "Maria Souza", "phone": "+5511988887777" },
"created_at": "2026-10-28 14:02:11"
}
}
}
Campo de data.messageO que é
idId da mensagem na Atalia.
external_idId da mensagem no WhatsApp, Instagram ou Messenger.
directioninbound (do cliente) ou outbound (da empresa).
typetext, image, video, audio, document, sticker, interactive, template e outros.
content_typeDetalhe do tipo, quando há. Ex.: interactive_button_reply quando o cliente tocou num botão (o título do botão vem em text).
textO texto da mensagem, a legenda da mídia ou o título do botão tocado. null em template.
templateSó quando type é template: o template enviado.
medianull ou { mime_type, download_path }. Veja o exemplo abaixo.
statusSituação da mensagem.
contactNome e telefone de quem mandou. No Instagram e no Messenger, phone é null.
created_atHorário de Brasília, "YYYY-MM-DD HH:MM:SS".

Com foto, áudio, vídeo ou documento, media traz o caminho para baixar o arquivo com a sua chave:

"message": {
"id": "1d9b2c3e-7a44-4f1d-8c2b-5e6f7a8b9c0d",
"external_id": "wamid.HBgNNTUxMTk4NzY1NDMyMRUCABIYFkM4QjE",
"direction": "inbound",
"type": "image",
"content_type": null,
"text": "Segue o comprovante",
"media": { "mime_type": "image/jpeg", "download_path": "/api/v1/media/1d9b2c3e-7a44-4f1d-8c2b-5e6f7a8b9c0d" },
"status": "delivered",
"contact": { "name": "Maria Souza", "phone": "+5511988887777" },
"created_at": "2026-10-28 14:03:40"
}

GET https://chat.atalia.com.br/api/v1/media/{id} com o header x-api-key responde 302 para um link temporário (5 minutos). Se responder 409 MEDIA_PROCESSING, o arquivo ainda está sendo baixado: tente de novo em alguns segundos.

message.echo​

Alguém da empresa respondeu pelo próprio app: o WhatsApp Business (na coexistência), o Instagram ou o Messenger. Mesmo formato do message.received, com direction: "outbound". Use para manter o histórico do seu sistema completo e, se fizer sentido, pausar a sua automação naquela conversa.

{
"id": "evt_0a1b2c3d4e5f6a7b8c9d0e1f",
"type": "message.echo",
"created_at": "2026-10-28T17:05:02.110Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": { "id": "ch_2f81a9c0d1", "type": "whatsapp", "phone": "+55 11 98765-4321" },
"data": {
"conversation_id": "48213",
"message": {
"id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
"external_id": "wamid.HBgNNTUxMTk4NzY1NDMyMRUCABEYEjQ5RkE",
"direction": "outbound",
"type": "text",
"content_type": null,
"text": "Oi, Maria! Aqui é a Carla, da loja.",
"media": null,
"status": "sent",
"contact": { "name": "Maria Souza", "phone": "+5511988887777" },
"created_at": "2026-10-28 14:05:01"
}
}
}

message.status​

Uma mensagem enviada pela API mudou de situação. message_id é o id devolvido por POST /api/v1/messages.

{
"id": "evt_5d6e7f8a9b0c1d2e3f4a5b6c",
"type": "message.status",
"created_at": "2026-10-28T17:02:15.020Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": { "id": "ch_2f81a9c0d1", "type": "whatsapp", "phone": "+55 11 98765-4321" },
"data": {
"message_id": "6f1c2a7e-0b8e-4d0a-9a51-3f7c1e2d9b10",
"external_id": "wamid.HBgNNTUxMTk4NzY1NDMyMRUCABEYEjI3QjE",
"status": "read",
"timestamp": "2026-10-28T17:02:14.000Z",
"errors": null
}
}

status é sent (aceita pela Meta), delivered (chegou no aparelho), read (lida) ou failed. Na falha, errors traz o motivo:

"data": {
"message_id": "6f1c2a7e-0b8e-4d0a-9a51-3f7c1e2d9b10",
"external_id": "wamid.HBgNNTUxMTk4NzY1NDMyMRUCABEYEjI3QjE",
"status": "failed",
"timestamp": "2026-10-28T17:02:14.000Z",
"errors": [{ "code": 131047, "title": "Re-engagement message" }]
}

No Instagram e no Messenger chegam só delivered e read. Os status podem chegar fora de ordem: use o timestamp.

channel.connected​

Uma caixa de entrada foi conectada. reconnected é true quando o canal já existia e foi conectado de novo (por exemplo, depois de trocar a senha do Facebook).

{
"id": "evt_1f2e3d4c5b6a7f8e9d0c1b2a",
"type": "channel.connected",
"created_at": "2026-10-28T16:40:00.000Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": { "id": "ch_2f81a9c0d1", "type": "whatsapp", "phone": "+55 11 98765-4321" },
"data": { "reconnected": false }
}

channel.disconnected​

Uma caixa de entrada foi apagada (pelo painel ou por DELETE /api/v1/channels/{id}). Depois disso, o channel.id não existe mais. Ao apagar, a Atalia também desvincula o número (ou a conta) dela na Meta, para que ele possa ser conectado em outro lugar. unlinked diz como foi: ok, failed (fale com o suporte se precisar conectar o número em outro lugar) ou skipped (nada a desvincular, por exemplo porque outro canal ainda usa o número).

{
"id": "evt_2a3b4c5d6e7f8a9b0c1d2e3f",
"type": "channel.disconnected",
"created_at": "2026-10-29T12:00:00.000Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": { "id": "ch_2f81a9c0d1", "type": "whatsapp", "phone": "+55 11 98765-4321" },
"data": { "reason": "deleted", "unlinked": { "meta": "ok" } }
}

client.connected​

Só para revenda. O cliente concluiu a página de conexão: aceitou os termos e conectou o primeiro canal. Chega uma vez por cliente. Os canais que ele conectar depois chegam como channel.connected, com o mesmo client_id.

{
"id": "evt_3c4d5e6f7a8b9c0d1e2f3a4b",
"type": "client.connected",
"created_at": "2026-10-28T18:10:00.000Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": "c21a7f0e-2b4d-4e8a-9f1c-0d2e3f4a5b6c",
"channel": null,
"data": {
"client": { "id": "c21a7f0e-2b4d-4e8a-9f1c-0d2e3f4a5b6c", "name": "Clínica Bem-Estar", "email": "contato@clinicabemestar.com.br", "tax_id": "11222333000181" },
"channels": ["whatsapp"],
"ref": "usuario-8812"
}
}

ref é o que veio no link da página de conexão (?ref=), ou null. Use para saber qual usuário do seu sistema conectou. Guarde o client.id junto desse cadastro. Sem ref, case pelo tax_id (o CNPJ só com números) ou pelo email. Veja Conectar os números dos seus clientes.

client.no_slot​

Só para revenda. O cliente tentou conectar e o seu pacote de canais estava cheio. Libere uma vaga (canal avulso ou pacote maior) e mande o link de novo.

{
"id": "evt_4d5e6f7a8b9c0d1e2f3a4b5c",
"type": "client.no_slot",
"created_at": "2026-10-28T18:12:00.000Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": "c21a7f0e-2b4d-4e8a-9f1c-0d2e3f4a5b6c",
"channel": null,
"data": {
"client": { "id": "c21a7f0e-2b4d-4e8a-9f1c-0d2e3f4a5b6c", "name": "Clínica Bem-Estar", "email": "contato@clinicabemestar.com.br", "tax_id": "11222333000181" },
"ref": "usuario-8812"
}
}

ping​

Teste enviado pelo botão "Enviar mensagem de teste" ou por POST /api/v1/webhook/test. Responda 2xx e ignore.

{
"id": "evt_5e6f7a8b9c0d1e2f3a4b5c6d",
"type": "ping",
"created_at": "2026-10-28T16:00:00.000Z",
"company_id": "6b1f2c9e-4d3a-4c55-9a51-3f7c1e2d9b10",
"client_id": null,
"channel": null,
"data": { "message": "Teste do webhook da Atalia" }
}

Verificar a assinatura​

X-Atalia-Signature: t=<epoch em segundos>,v1=<hex>, onde v1 é o HMAC-SHA256 de "<t>.<corpo cru>" com o segredo whsec_... que aparece em Chave e webhook › Segredo da assinatura. Calcule sobre o corpo CRU, antes de ler o JSON, e recuse t com mais de 5 minutos.

import crypto from 'node:crypto';

function verifyAtaliaSignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''));
}
import hashlib, hmac, time

def verify_atalia_signature(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))

Tentativas, repetidos e ordem​

  • Responda 2xx em até 10 segundos. Grave o evento e processe depois. Qualquer outra resposta, ou demora, conta como falha. Redirecionamentos não são seguidos.
  • Falhou, a Atalia tenta de novo, até 9 tentativas no total, com espera crescente: 30 s, 1 min, 2 min, 4 min, 8 min, 16 min, 32 min e 1 h (cerca de 2 horas no total). Depois disso, o evento fica como "Falhou" em Entregas do webhook, e você pode reenviar de lá.
  • O mesmo evento pode chegar mais de uma vez (tentativa depois de um timeout, ou reenvio pelo painel). Ele chega com o mesmo id: guarde os ids recebidos e ignore os repetidos.
  • A ordem não é garantida. Use o created_at do envelope e o timestamp do message.status.

Entregas do webhook​

No painel, Entregas do webhook lista os eventos dos últimos 7 dias: o que foi entregue, o que está sendo tentado de novo e o que falhou. Em cada um aparecem o JSON enviado, a resposta do seu sistema (o começo dela), o código HTTP, o tempo e o erro. O botão Reenviar manda o mesmo evento outra vez, com o mesmo id e uma assinatura nova.

Responder​

Para responder um message.received, envie pelo conversation_id que chegou no evento:

curl -X POST https://chat.atalia.com.br/api/v1/messages \
-H "x-api-key: $ATALIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reply-6f1c2a7e-0b8e-4d0a-9a51-3f7c1e2d9b10" \
-d '{"channel_id":"ch_2f81a9c0d1","conversation_id":"48213","type":"text","text":"Entregamos sim!"}'

Um sistema completo que recebe e responde estes eventos está no projeto de exemplo. A referência de todas as rotas está em llms.txt.