Eventos do Codewo
no seu servidor.
Cadastre um endereço https:// e escolha os eventos. Quando uma conversa, um contato ou uma venda muda, o Codewo envia um POST com JSON assinado para o seu servidor. Seu sistema reage logo em seguida, sem consultar a API de tempos em tempos.
X-Codewo-Signature: t=<unix>,v1=<hmac_hex> HMAC-SHA256 de "{t}.{corpo}" com o segredo do endereço. Nas 24 h depois de trocar o segredo, chegam dois v1.
11 eventos para escolher
5 tentativas, de 0 s a 2 h
Pausa automática após 5 falhas seguidas.
Visão geral
Você cadastra um endereço https:// e escolhe os eventos que ele recebe. Quando um deles acontece, o Codewo põe a entrega numa fila e faz um POST com JSON assinado para o seu servidor. Resposta 2xx encerra a entrega; qualquer outra coisa entra nas retentativas.
Cadastre o endereço
Em Configurações › Integrações › Webhooks: nome, endereço e eventos. O segredo de assinatura aparece uma vez só.
O Codewo envia
POST JSON com até 10 s para responder. Os cabeçalhos dizem o tipo e o id do evento.
Você responde 2xx
Outra resposta, ou nenhuma, gera nova tentativa. 5 falhas seguidas pausam o endereço.
Webhooks estão nos planos a partir do Professional e no teste grátis de 7 dias. Quem cadastra precisa da permissão "Gerenciar integrações". Você pode ter mais de um endereço, cada um com o próprio segredo e os próprios eventos, e acompanhar cada entrega num histórico com status, tentativa e tempo de resposta.
Formato do envio
Todo POST tem o mesmo envelope, com quatro campos.
{
"id": "01M3MA8R8GT3V6W9X2Y5Z8A1BC",
"type": "quote.accepted",
"created_at": "2026-09-28T12:31:23-03:00",
"data": { "...": "conteúdo do evento" }
}id- Id do evento. É o mesmo em todas as tentativas automáticas e igual ao cabeçalho
X-Codewo-Delivery. type- Um dos 11 eventos abaixo, igual ao cabeçalho
X-Codewo-Event. created_at- Hora deste POST, em ISO 8601 no horário de Brasília. Muda a cada tentativa: não use para ordenar eventos.
data- O conteúdo do evento, fotografado no momento em que ele aconteceu. Uma retentativa horas depois leva o mesmo conteúdo; para o estado atual, consulte a API.
Conversa e contato vêm dentro de um objeto (data.conversation, data.contact). Os 4 eventos de venda põem os campos direto na raiz (data.id, data.number...). Um leitor genérico precisa dos dois caminhos. Campos novos podem ser acrescentados com o tempo: ignore os que o seu código não conhece.
Os 11 eventos
Cada endereço assina os eventos que quiser. Cada evento sai uma vez por acontecimento, e o exemplo de corpo de cada um traz os mesmos campos, na mesma ordem e com os mesmos formatos que o Codewo envia.
Conversas
conversation.createdConversa criadaDisparado quando uma nova conversa é aberta em qualquer canal.
- Uma vez por conversa nova, em qualquer um dos 8 canais: a que começa pela mensagem do cliente e a que a equipe abre pela Caixa de Entrada, por uma Transmissão ou por um aviso da Agenda. Quando é o primeiro contato da pessoa, chega também um
contact.created. - Conversa aberta por uma ligação do WhatsApp, por um encaminhamento de mensagem ou por uma automação ainda não gera este evento. Os eventos seguintes dela (atribuição, status, resolução) saem normalmente.
channel_typediz o canal:whatsappé a API oficial do WhatsApp ewhatsapp_webé a conexão por QR Code.
conversation.assignedConversa atribuídaDisparado quando muda o responsável pela conversa (pessoa, equipe ou atendente virtual), inclusive quando ela fica sem responsável.
assigned_to.typeéuser(pessoa),team(equipe) ouai_agent(atendente virtual). Tirar o responsável também dispara, comassigned_toigual anull.- Se o status muda no mesmo momento da atribuição, sai só este evento, já com o status novo. A resolução é a exceção: ela sempre sai como
conversation.closed.
conversation.status_changedStatus da conversa alteradoDisparado quando o status muda sem ser resolução: aberta, pendente, em andamento, adiada ou spam, inclusive ao reabrir. Resolver dispara "Conversa resolvida".
- Cobre
open,pending,in_progress,snoozedespam, inclusive a reabertura de uma conversa resolvida. A resolução sai comoconversation.closed, não aqui.
conversation.closedConversa resolvidaDisparado quando a conversa é resolvida (status = resolved).
- O evento se chama
closed, mas o status que chega éresolved: não existe statusclosednas conversas. Em geralresolved_atvem preenchido; em dois casos raros (a conversa que uma Transmissão já abre resolvida e o grupo do WhatsApp de que o seu número saiu) ele cheganull.
Contatos
contact.createdContato criadoDisparado quando um novo contato é cadastrado, inclusive o que nasce da primeira mensagem num canal.
- Inclui o contato que nasce da primeira mensagem num canal. Nesse caso
sourcetraz o tipo do canal estatuscheganull, embora o contato seja gravado comoactive. O mesmo vale para o contato criado semstatuspela API. - A origem medida pela atribuição (anúncio, campanha) não vem aqui: leia em
GET /v1/contacts/{id}, no campoattribution. - Contato criado pela API já com dono da carteira chega como
contact.createdseguido decontact.updatedcomassigned_toemchanged_fields.
contact.updatedContato atualizadoDisparado quando muda nome, e-mail, telefone, empresa, status, origem do cadastro, foto ou o dono da carteira do contato.
- Só sai quando muda
name,email,phone,company_name,status,source,avatar_urlouassigned_to.changed_fieldslista quais mudaram. - O dono da carteira e a foto não vêm no objeto do contato: quando
changed_fieldstrazassigned_to, leia o novo dono emGET /v1/contacts/{id}(assigned_toeportfolio_owner). - Mudança só em campos personalizados não dispara. Etiqueta tem evento próprio.
contact.tag_addedEtiqueta adicionada ao contatoDisparado quando uma etiqueta entra no contato, um evento por etiqueta.
- Sai por qualquer caminho: ficha do contato e painel da conversa, Automações, Transmissões, atendente virtual, dados extraídos pela IA e integração. Anexar de novo uma etiqueta que o contato já tem não dispara.
data.tagé a etiqueta que entrou;data.contact.tagsjá traz a lista completa, com ela.- Exceção: ao mesclar dois contatos, as etiquetas herdadas do contato absorvido não geram este evento. Tirar etiqueta não tem evento.
Vendas
order.createdVenda registradaDisparado a cada pedido registrado, por qualquer caminho: negócio ganho no funil, proposta aceita, venda pela conversa, pedido manual, API, integração ou recorrência.
- Sai para todo pedido, de qualquer um dos 7 caminhos. Os campos vêm na raiz de
data, sem objeto em volta. - Hoje este evento é montado no instante em que o pedido nasce, antes de as linhas serem gravadas:
total_cents,items_countedelivery_statuschegamnull, ecurrencytambém pode chegarnull. Para o valor, busqueGET /v1/orders/{id}ao receber o evento.order.status_changedeorder.deliveredjá trazem os totais.
order.status_changedStatus do pedido alteradoDisparado quando o pedido passa a contar como venda (confirmado) ou deixa de contar (cancelado ou de volta a rascunho).
- Sai quando o pedido passa a contar como venda ou deixa de contar: rascunho para confirmado, confirmado para cancelado e confirmado de volta para rascunho. De rascunho para cancelado não dispara.
- Mudar a etapa do pedido no quadro não dispara este evento. Cancelar pela tela também marca a entrega como
cancelled.
order.deliveredPedido entregueDisparado quando a entrega do pedido passa a Entregue, inclusive na retirada pelo cliente.
- Sai quando
delivery_statuspassa adelivered, por qualquer caminho: a tela do pedido, uma etapa que marca a entrega, a ação Marcar entrega das Automações ou uma integração. Na retirada pelo cliente, também. - Se a entrega sair de
deliverede voltar, sai de novo.
quote.acceptedOrçamento aceitoDisparado quando a proposta é aceita, pelo cliente no link ou registrada pela equipe como aceita por fora.
- Sai tanto no aceite pelo cliente, no link da proposta, quanto no aceite registrado pela equipe. O corpo não diz por qual dos dois caminhos veio.
- O pedido gerado chega também como
order.created, comsourceigual aquote. Aceitar de novo não gera outro pedido nem outro evento.
Campos de data
O que cada objeto traz e os valores possíveis de cada campo.
Conversa data.conversation
Nos 4 eventos conversation.*.
id- Número da conversa.
statusopen,pending,in_progress,resolved,snoozedouspam.prioritylow,medium,highouurgent.channel_type- Canal mais recente da conversa:
whatsapp(API oficial),whatsapp_web(conexão por QR Code),instagram,facebook(Messenger),telegram,email,webchatousms. contactid,name,emailephonedo contato, ounull.assigned_toid,nameetype(user,teamouai_agent), ounullquando ninguém é responsável.created_at, last_message_at, resolved_at- Datas em ISO 8601.
resolved_atficanullenquanto a conversa não é resolvida.
Contato data.contact
Nos 3 eventos contact.*, com changed_fields no contact.updated e tag no contact.tag_added.
id- Número do contato.
name, email, phone- Como estão no cadastro. E-mail e telefone podem vir
null. company_name- Empresa do contato, ou
null. statusactive,inactiveoublocked. Pode chegarnullnocontact.created(veja o evento acima).source- Texto de origem do cadastro. Quando o contato nasce de uma mensagem, é o tipo do canal (
whatsapp,instagram...); pela API,apiou o valor que a sua integração mandar; na ficha de pessoa jurídica, o campo "Origem do Lead" grava valores comositeeindicacao. A origem medida pela atribuição fica emGET /v1/contacts/{id}. tags- Todas as etiquetas atuais, como lista de
{id, name, color}. created_at, updated_at- Datas em ISO 8601.
Pedido data
Nos 3 eventos order.*, direto na raiz de data.
id- Número interno do pedido (o mesmo de
GET /v1/orders/{id}). number- Número exibido na tela, como
PED-2026-0137. statusdraft(rascunho),confirmed(conta como venda) oucancelled.source- Caminho de origem:
manual(pedido criado na tela),conversation(venda pela conversa),crm(negócio ganho no funil),quote(proposta aceita),integration(API ou integração) ourecurrence(ciclo de recorrência). sold_at- Data da venda, em ISO 8601.
total_cents- Total em centavos, número inteiro:
248000é R$ 2.480,00. Cheganullnoorder.created. currencyBRL. Pode chegarnullnoorder.created.items_count- Quantidade de linhas do pedido. Chega
nullnoorder.created. contact_id- Contato da venda, ou
nullem venda sem cliente identificado. delivery_statuspending,preparing,ready,shipped,in_transit,deliveredoucancelled. Cheganullnoorder.created.
Proposta data
No quote.accepted, direto na raiz de data.
quote_id- Número interno da proposta.
number- Número exibido, como
ORC-00042. version- Versão aceita. Revisar uma proposta cria uma versão nova com o mesmo número.
order_id- Pedido gerado pelo aceite.
total_cents- Total aceito, em centavos, já com as escolhas do cliente nos itens opcionais e nos grupos "escolha uma".
contact_id- Contato da proposta.
accepted_by- Nome digitado pelo cliente no link ou informado pela equipe. Pode vir
null.
- Linhas do pedido: estão em
GET /v1/orders/{id}. Pagamentos e parcelas não estão no webhook nem na API. - Dono da carteira, foto e origem de marketing do contato: estão em
GET /v1/contacts/{id}. - Campos personalizados do contato: não estão no webhook nem na API.
Cabeçalhos
Em destaque, os três que importam para a implementação: X-Codewo-Event para rotear, X-Codewo-Delivery para deduplicar e X-Codewo-Signature para autenticar.
| Cabeçalho | Exemplo | Para que serve |
|---|---|---|
Content-Type | application/json | Corpo em JSON, UTF-8. |
User-Agent | Codewo-Webhooks/1.0 | Identifica o Codewo no seu log. |
X-Codewo-Event | order.created | Tipo do evento, igual ao type do corpo. Dá para rotear antes de ler o JSON. |
X-Codewo-Delivery | 01M3MA8R8GH4J9K2M7N1P5Q8RS | Id do evento, igual ao id do corpo. É o mesmo em todas as tentativas automáticas: use como chave para não processar duas vezes. |
X-Codewo-Signature | t=<unix>,v1=<hex>[,v1=<hex>] | HMAC-SHA256 de {t}.{corpo}. Nas 24 h depois de trocar o segredo, chegam dois v1. Valide antes de processar. |
Verificar a assinatura
Valide a assinatura antes de processar qualquer evento. Sem ela, qualquer pessoa que descubra o seu endereço consegue mandar um evento falso.
- Calcule o HMAC-SHA256 de
"{t}.{corpo}"com o segredo inteiro, incluindo o prefixowhsec_, e compare em hexadecimal. - Use o corpo cru, byte a byte. Não interprete e remonte o JSON antes: o Codewo envia acentos e barras sem escape, e qualquer mudança de formatação muda a assinatura.
- Compare em tempo constante (
hash_equals,timingSafeEqual,compare_digest), nunca com==: a comparação comum vaza informação pelo tempo de resposta. - Recuse
tcom mais de 5 minutos de diferença. A assinatura prova a origem; otimpede que alguém reenvie um corpo antigo capturado. Cada tentativa é assinada de novo, comtnovo, então a janela não recusa retentativas. - Aceite qualquer
v1que bater. Nas 24 h depois de trocar o segredo, o cabeçalho traz dois. Os trechos abaixo já tratam isso.
// Segredo completo, com o prefixo whsec_, lido de variável de ambiente
$secret = getenv('CODEWO_WEBHOOK_SECRET');
$header = $_SERVER['HTTP_X_CODEWO_SIGNATURE'] ?? '';
$body = file_get_contents('php://input'); // corpo cru, byte a byte
// Durante a rotação o cabeçalho traz vários "v1=": "t=<unix>,v1=<novo>,v1=<antigo>".
$timestamp = null;
$signatures = [];
foreach (explode(',', $header) as $part) {
[$key, $value] = array_pad(explode('=', $part, 2), 2, null);
if ($key === 't') {
$timestamp = $value;
} elseif ($key === 'v1' && $value !== null) {
$signatures[] = $value;
}
}
// Janela de 5 minutos contra o reenvio de um corpo antigo capturado
if ($timestamp === null || abs(time() - (int) $timestamp) > 300) {
http_response_code(403);
exit;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);
// Aceita se QUALQUER "v1" bater (comparação em tempo constante)
$valid = false;
foreach ($signatures as $signature) {
if (hash_equals($expected, $signature)) {
$valid = true;
break;
}
}
if (! $valid) {
http_response_code(403);
exit;
}
// Idempotência: X-Codewo-Delivery (igual ao "id" do corpo) é o mesmo em todas
// as tentativas automáticas do evento. É a chave para deduplicar.
$deliveryId = $_SERVER['HTTP_X_CODEWO_DELIVERY'] ?? null;
// ... se já processou $deliveryId, responda 200 e pare aqui
$event = json_decode($body, true);
// ... guarde o evento numa fila e responda logo (limite de 10 s)
http_response_code(200);Tentativas e pausa automática
Só conta como entregue a resposta 2xx em até 10 segundos. Qualquer outra resposta (4xx, 5xx), tempo esgotado ou falha de conexão gera nova tentativa, até 5 no total.
| Tentativa | Quando |
|---|---|
| 1 | Imediata |
| 2 | 30 segundos depois da 1ª falha |
| 3 | 5 minutos depois da 2ª falha |
| 4 | 30 minutos depois da 3ª falha |
| 5 | 2 horas depois da 4ª falha |
A pausa conta as falhas seguidas do endereço, somando todos os eventos. Na 5ª, o endereço é pausado e quem o criou recebe um aviso no Codewo. Um evento que falha nas 5 tentativas, sem nenhuma entrega com sucesso no meio, pausa o endereço; com a conta movimentada, 5 eventos diferentes falhando em sequência também pausam, às vezes em poucos minutos. Qualquer entrega com sucesso zera a contagem.
Se as 5 tentativas de um evento falharem sem pausar o endereço (porque outras entregas deram certo no meio), o Codewo para de tentar aquele evento. Ele fica no histórico como falha, pronto para o botão Reenviar.
Enquanto está pausado, nada fica guardado para depois: eventos novos não são enfileirados e as retentativas pendentes são descartadas. Reativar zera a contagem, mas não reenvia o que se perdeu. Para recuperar, reative o endereço, use o botão Reenviar no histórico de entregas (elas ficam guardadas por 30 dias) e consulte pela API o que aconteceu durante a pausa.
O botão Testar manda um corpo fictício, com ids 0 e _test: true dentro de data. Para os 4 eventos de venda, o teste ainda usa o formato de contato, e o contato de teste vem com status igual a lead, que não existe nos contatos reais: valide o seu leitor com os exemplos desta página ou com um pedido de verdade.
Repetições e ordem
O mesmo evento pode chegar mais de uma vez: basta o seu servidor processar e responder depois de 10 segundos, ou a conexão cair antes de a resposta chegar. O seu código precisa aguentar isso.
Deduplique pelo id
O id do corpo (igual a X-Codewo-Delivery) é o mesmo em todas as tentativas automáticas. Guardar os ids por 7 dias dá folga: as retentativas terminam cerca de duas horas e meia depois da primeira.
Reenviar gera id novo
Reenviar pelo histórico e Testar criam entregas novas, com id novo, de propósito. Se um processamento não pode rodar duas vezes nem nesse caso, combine também uma chave do próprio dado, como type e data.id do pedido.
Responda rápido
Confira a assinatura, guarde o evento numa fila e responda 2xx. Processamento pesado dentro da requisição estoura os 10 segundos e vira retentativa.
A ordem não é garantida
As entregas correm em paralelo, e uma retentativa pode chegar depois de um evento mais novo. Para saber o estado atual, compare as datas do próprio objeto (updated_at do contato, last_message_at da conversa) ou consulte a API.
Troca do segredo
Cada endereço tem o próprio segredo, whsec_ seguido de 48 caracteres, mostrado uma vez só. O Codewo o guarda cifrado, e não como hash, porque precisa dele para assinar cada envio.
Ao clicar em Rotacionar secret, um segredo novo é gerado e, por 24 horas, cada entrega sai assinada com os dois: t=<unix>,v1=<novo>,v1=<antigo>. É o tempo para trocar a variável de ambiente do seu servidor sem perder entregas. Depois disso, só o novo vale.
Nunca em página, aplicativo ou código que roda no navegador. Se ele aparecer em log, console, repositório ou chamado de suporte, rotacione na hora.
O que ainda não tem evento
Melhor saber agora do que procurar depois.
- Mensagens. Não há evento de mensagem recebida nem enviada.
- Negociações do funil. Entrar no funil, mudar de etapa, ganhar ou perder não geram webhook. Quando a negociação ganha vira pedido, chega o
order.created. - Proposta, além do aceite. Proposta enviada, aberta, recusada ou vencendo existe como gatilho de Automações. Webhook, só o aceite.
- Pagamentos e parcelas. Pagamento recebido, pedido quitado e parcela vencendo ou vencida existem como gatilho de Automações, não como webhook.
- Etiqueta removida. Só a entrada de etiqueta tem evento.
- Histórico importado de ERP. Na primeira carga de uma integração, pedidos e contatos antigos entram sem disparar webhook. O que chega depois, ao vivo, dispara normalmente.
- Endereço sem HTTPS. Só endereços que começam com
https://são aceitos.
Monte uma automação com o gatilho do fato (por exemplo "Pagamento recebido" ou "Negociação ganha") e o passo "Chamar um sistema externo", que envia a requisição que você montar, com método, cabeçalhos e corpo com variáveis. Esse passo não tem a assinatura nem as retentativas dos webhooks.
Ver as AutomaçõesPerguntas frequentes sobre webhooks
Ligue o Codewo ao sistema que você já usa.
Teste grátis por 7 dias com webhooks e API liberados, sem cartão e sem cobrança automática no fim do teste.