Webhooks
Cada endpoint de webhook recebe um segredo whsec_... exibido uma única vez, no momento da criação. O corpo de cada entrega é assinado sem nenhuma normalização posterior — a assinatura cobre os bytes exatos enviados.
Headers de assinatura
Toda entrega chega com três headers:
X-Hub-Delivery-Id— UUID único desta tentativa, usado para deduplicar.X-Hub-Timestamp— o instante do envio, em segundos desde a época Unix.X-Hub-Signature-256— assinatura HMAC-SHA256 do corpo cru, formatosha256=HEX.
Verificando em Node
Calcule o HMAC do corpo cru com o segredo do endpoint e compare com timingSafeEqual, nunca com === — uma comparação de string comum vaza o tempo de execução por posição de byte, o que dá a um atacante um jeito de descobrir a assinatura correta por tentativa e erro.
import { createHmac, timingSafeEqual } from 'node:crypto'
const expected = createHmac('sha256', process.env.WEBHOOK_SECRET).update(rawBody).digest('hex')
const received = request.headers['x-hub-signature-256']?.replace('sha256=', '')
const valid = !!received &&
/^[0-9a-f]{64}$/.test(received) &&
timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expected, 'hex'))Regras de recebimento
- Leia o corpo cru antes de fazer parse de JSON — reserializar o JSON pode mudar a assinatura esperada.
- Rejeite qualquer entrega sem o header de assinatura, ou com assinatura inválida.
- Deduplique por
X-Hub-Delivery-Id: uma retentativa reenvia o mesmo id. - Responda
2xxem até 10 segundos — processe o evento de forma assíncrona se demorar.
Retentativas e pausa
Uma entrega que falha (timeout, resposta fora de 2xx, erro de conexão) é reenviada até dez vezes. Depois de 20 falhas consecutivas, o endpoint pausa automaticamente — reative-o no painel depois de corrigir o que impedia o recebimento.
O que é bloqueado no destino
A URL de um endpoint de webhook passa por uma verificação de destino. São bloqueados:
- HTTP puro — só HTTPS é aceito.
- Redes internas (loopback, link-local e faixas privadas de IP).
- Redirects — a entrega não segue um redirecionamento HTTP.
- DNS privado — um domínio que resolve para um IP interno é recusado.
Modos de payload
Cada endpoint escolhe um entre dois modos de payload:
normalized(padrão) — envelope do CRPRO Hub:{"id", "event", "channel_id", "occurred_at", "data"}, igual para qualquer evento.meta_raw— o payload bruto da Meta para o evento; sem correspondente bruto, cai de volta paranormalized.
channel.createdchannel.connectedchannel.degradedchannel.disconnectedmessage.receivedmessage.sentmessage.statusmessage.echotemplate.statusphone.qualityaccount.updatewebhook.test.