Skip to content

Webhooks de eventos ​

O WayChat manda um POST para a sua URL quando algo acontece: mensagem recebida, conversa encerrada, contato criado, lead que marcou visita, WhatsApp que caiu e muitos outros. Configure em Configurações → API → Criar webhook. Os webhooks fazem parte do plano (módulo Webhooks).

Como é um envio ​

http
POST /sua-url
Content-Type: application/json
User-Agent: WayChat-Webhooks/1.0
X-WayChat-Evento: mensagem.recebida
X-WayChat-Entrega-Id: 3f2a9c1e-5b7d-4e2a-9c1f-8d6e4b2a7c10
X-WayChat-Tentativa: 1
X-WayChat-Timestamp: 1792761600
X-WayChat-Assinatura: sha256=5f1c…
json
{
  "id": "6b0c3f0e-8a51-4d1e-9f4e-0c2b7a9d1e55",
  "evento": "mensagem.recebida",
  "criado_em": "2026-10-30T14:02:12.000Z",
  "dados": {
    "conversa": { "id": 4821, "protocolo": 104821, "status": "aberta", "setor": { "id": 2, "nome": "Suporte técnico" } },
    "contato": { "id": 917, "nome": "Maria Souza", "telefone": "+5544999998888" },
    "mensagem": { "id": 99120, "direcao": "entrada", "texto": "Meu site está fora do ar" }
  }
}
CabeçalhoO que é
X-WayChat-EventoNome do evento (o mesmo de evento no corpo)
X-WayChat-Entrega-IdId desta entrega (evento + webhook). Igual em todas as tentativas e reenvios
X-WayChat-Tentativa1 na primeira tentativa, 2 na segunda…
X-WayChat-TimestampHora do envio (segundos desde 1970, UTC)
X-WayChat-Assinaturasha256= + HMAC-SHA256 de timestamp + "." + corpo cru com o segredo do webhook
X-WayChat-Assinatura-AnteriorSó nas 24 h depois de trocar o segredo: a mesma assinatura feita com o segredo antigo
X-WayChat-Reenvio1 quando alguém da equipe reenviou a entrega pelo painel

A lista completa de eventos, com um exemplo de corpo para cada um, está no Catálogo de eventos.

Conferir a assinatura ​

Cada webhook tem um segredo (whsec_...), mostrado uma vez ao criar. Para cada envio:

  1. Recuse se X-WayChat-Timestamp estiver a mais de 5 minutos da sua hora (evita repetição de um envio antigo).
  2. Calcule sha256= + HMAC-SHA256 de timestamp + "." + corpo cru com o segredo. Use o corpo exatamente como chegou, antes de converter o JSON.
  3. Aceite se for igual a X-WayChat-Assinatura. Durante a troca de segredo, aceite também se for igual a X-WayChat-Assinatura-Anterior calculada com o segredo antigo (veja Trocar o segredo).
  4. Compare em tempo constante.
js
import { createHmac, timingSafeEqual } from 'node:crypto'

// segredos: [segredoNovo, segredoAntigo] (o antigo só durante a troca; pode ser só [segredo])
export function assinaturaValida(headers, corpoCru, segredos) {
  const ts = String(headers['x-waychat-timestamp'] || '')
  if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false
  const recebidas = [headers['x-waychat-assinatura'], headers['x-waychat-assinatura-anterior']].filter(Boolean).map(String)
  return segredos.filter(Boolean).some((segredo) => {
    const esperada = Buffer.from('sha256=' + createHmac('sha256', segredo).update(`${ts}.${corpoCru}`).digest('hex'))
    return recebidas.some((r) => r.length === esperada.length && timingSafeEqual(Buffer.from(r), esperada))
  })
}

// Express: guarde o corpo cru
// app.post('/waychat', express.raw({ type: 'application/json' }), (req, res) => {
//   if (!assinaturaValida(req.headers, req.body.toString('utf8'), [process.env.WAYCHAT_SEGREDO, process.env.WAYCHAT_SEGREDO_ANTIGO])) return res.sendStatus(401)
//   const evento = JSON.parse(req.body)
//   res.sendStatus(200)
// })
php
<?php
// $segredos: [segredoNovo, segredoAntigo] (o antigo só durante a troca)
function assinaturaValida(array $segredos): bool {
    $ts = $_SERVER['HTTP_X_WAYCHAT_TIMESTAMP'] ?? '';
    if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300) return false;
    $corpo = file_get_contents('php://input');
    $recebidas = array_filter([$_SERVER['HTTP_X_WAYCHAT_ASSINATURA'] ?? '', $_SERVER['HTTP_X_WAYCHAT_ASSINATURA_ANTERIOR'] ?? '']);
    foreach (array_filter($segredos) as $segredo) {
        $esperada = 'sha256=' . hash_hmac('sha256', $ts . '.' . $corpo, $segredo);
        foreach ($recebidas as $r) if (hash_equals($esperada, $r)) return true;
    }
    return false;
}

if (!assinaturaValida([getenv('WAYCHAT_SEGREDO'), getenv('WAYCHAT_SEGREDO_ANTIGO')])) { http_response_code(401); exit; }
$evento = json_decode(file_get_contents('php://input'), true);
http_response_code(200);
python
import hashlib, hmac, time

def assinatura_valida(headers, corpo_cru: bytes, segredos) -> bool:
    """segredos: [segredo_novo, segredo_antigo] (o antigo só durante a troca)"""
    ts = headers.get('X-WayChat-Timestamp', '')
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        return False
    recebidas = [h for h in (headers.get('X-WayChat-Assinatura'), headers.get('X-WayChat-Assinatura-Anterior')) if h]
    for segredo in filter(None, segredos):
        esperada = 'sha256=' + hmac.new(segredo.encode(), ts.encode() + b'.' + corpo_cru, hashlib.sha256).hexdigest()
        if any(hmac.compare_digest(esperada, r) for r in recebidas):
            return True
    return False

# Flask:
# @app.post('/waychat')
# def waychat():
#     if not assinatura_valida(request.headers, request.get_data(), [os.environ['WAYCHAT_SEGREDO'], os.environ.get('WAYCHAT_SEGREDO_ANTIGO')]):
#         abort(401)
#     evento = request.get_json()
#     return '', 200

Trocar o segredo ​

Em Editar → Trocar segredo, o WayChat gera um segredo novo e mostra uma vez.

  • O novo passa a assinar na hora (X-WayChat-Assinatura).
  • O antigo continua valendo por 24 horas: nesse período, cada envio leva também X-WayChat-Assinatura-Anterior, feita com o segredo antigo. Assim você atualiza o sistema que recebe sem perder eventos.
  • Se o segredo vazou, marque Encerrar o segredo antigo agora: o antigo para de assinar na hora.

Entregas e novas tentativas ​

  • Responda com qualquer status 2xx em até 10 segundos. Redirecionamentos (3xx) contam como falha: use o endereço final.
  • Se falhar (erro de rede, tempo esgotado ou status fora de 2xx), o WayChat tenta de novo até 6 vezes no total, com espera crescente: 30 s, 1 min, 2 min, 4 min e 8 min (cerca de 15 minutos no total).
  • Depois de 10 falhas seguidas, os admins recebem um e-mail. Depois de 50 falhas seguidas, o webhook é desligado por falhas. Corrija o endereço e clique em Reativar.
  • Só endereços https públicos são aceitos (nada de rede interna).

Idempotência ​

O mesmo evento pode chegar mais de uma vez: a sua resposta se perdeu no caminho, demorou mais de 10 s, ou alguém reenviou pelo painel. Para processar cada evento uma vez só:

  • Guarde o X-WayChat-Entrega-Id (ou o id do corpo) dos eventos que você já processou e ignore os repetidos.
  • Os dois são iguais em todas as tentativas e nos reenvios. X-WayChat-Tentativa mostra qual tentativa é.
  • A ordem de chegada não é garantida. Use criado_em quando a ordem importar.

Filtros ​

Cada webhook pode receber só uma parte dos eventos:

FiltroRecebe só quando
Canaiso evento é de um dos canais escolhidos
Setoresa conversa está num dos setores escolhidos
Etiquetaso contato (ou a conversa) tem pelo menos uma das etiquetas
  • Filtro vazio = tudo.
  • O filtro só vale para eventos que têm aquela informação. Um ticket.criado não tem canal, então chega mesmo com filtro de canal. Uma conversa sem setor não passa num filtro de setor.
  • Conversas e mensagens têm canal, setor e etiquetas. Contatos e leads têm etiquetas. Campanhas e canais têm canal.

Cabeçalhos próprios ​

Até 10 cabeçalhos por webhook, por exemplo Authorization: Bearer … ou X-Api-Key, para o seu sistema reconhecer o WayChat.

  • Os valores ficam cifrados no banco e não aparecem de novo na tela nem no registro de envios (aparecem como ••••).
  • Nomes com letras, números e hífen. Não dá para usar Host, Content-Length, Content-Type, Connection, Transfer-Encoding, User-Agent e outros de conexão, nem nada que comece com X-WayChat-.
  • Para trocar um valor, digite o novo. Deixar em branco mantém o atual.

Ver, reenviar, pausar e testar ​

  • Envios mostra cada tentativa: evento, resultado, status, tempo, o corpo enviado, os cabeçalhos e o começo (2 KB) da resposta. Filtre por evento e por sucesso ou falha. Ficam os últimos 200 por webhook, por até 30 dias.
  • Reenviar manda de novo uma entrega (mesmo corpo e mesmo X-WayChat-Entrega-Id, com X-WayChat-Reenvio: 1).
  • Reenviar falhas reenvia, depois de confirmar, todas as entregas que falharam num período (até 31 dias), no máximo 500 por vez.
  • Pausar para os envios sem apagar o webhook. Eventos que acontecem enquanto ele está pausado não ficam guardados. Retomar volta a enviar.
  • Enviar teste manda na hora um exemplo realista do evento que você escolher, com "teste": true no corpo.

Receitas ​

n8n ​

  1. Crie um workflow com o nó Webhook (método POST, Respond: Immediately). Copie a Production URL.
  2. No WayChat, crie o webhook com essa URL e os eventos que quer. Clique em Enviar teste e veja o exemplo chegar no n8n.
  3. Para conferir a assinatura, ligue Raw Body no nó Webhook e use um nó Code com a função de Node.js acima (o segredo em uma credencial ou variável de ambiente).
  4. Use um nó Switch no campo body.evento para separar cada evento.

Make ​

  1. Crie um cenário com Webhooks → Custom webhook e copie o endereço.
  2. Crie o webhook no WayChat com esse endereço e clique em Enviar teste: o Make aprende a estrutura com o exemplo (clique em Redetermine data structure se precisar).
  3. Use um Router com filtros em evento para cada caminho. Para exigir uma chave, crie um cabeçalho próprio (ex.: X-Api-Key) e confira no primeiro módulo.

Zapier ​

  1. Crie um Zap com o gatilho Webhooks by Zapier → Catch Raw Hook (ou Catch Hook) e copie a URL.
  2. Crie o webhook no WayChat com essa URL e clique em Enviar teste. No Zapier, clique em Test trigger.
  3. Use Filter by Zapier em evento para seguir só com os eventos que interessam. Com Catch Raw Hook você recebe os cabeçalhos e pode conferir a assinatura num passo Code by Zapier.