Pular para o conteúdo

Webhooks

Janela do terminal
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" -H "Content-Type: application/json" \
-d '{"url":"https://sua-aplicacao.exemplo/zapgate-webhook"}' \
https://sua-instancia.exemplo/instance/{id}/webhooks

events é opcional — vazio ou ausente significa todos os eventos. O segredo (secret) usado para assinar as entregas só aparece nesta resposta de criação; a listagem (GET .../webhooks) nunca devolve segredo (confirmado ao vivo).

Toda entrega chega com o header X-Webhook-Signature, no formato:

t=<unix-timestamp>,v1=<hmac-sha256-hex>

O HMAC cobre "<timestamp>.<corpo-bruto>" — ou seja, concatene o timestamp (em texto), um . e o corpo exato dos bytes recebidos, e calcule HMAC-SHA256(secret, timestamp + "." + body). Compare o hex resultante com o v1 do header. Use o timestamp para rejeitar entregas fora de uma janela aceitável (proteção contra replay).

Exemplo em pseudocódigo:

t, v1 = header.split(",") # "t=...", "v1=..."
ts = t.removeprefix("t=")
sig = v1.removeprefix("v1=")
expected = hmac_sha256(secret, f"{ts}.{raw_body}").hexdigest()
assert hmac.compare_digest(sig, expected)
Janela do terminal
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
https://sua-instancia.exemplo/instance/{id}/webhooks/{webhookId}/test

Dispara um evento sintético webhook.test, assinado e entregue como qualquer outro — ignora o filtro de events do webhook (sempre dispara, mesmo que webhook.test não esteja na lista).

Janela do terminal
curl -H "Authorization: Bearer <ADMIN_TOKEN>" \
https://sua-instancia.exemplo/instance/{id}/webhooks/{webhookId}/deliveries

Últimas 50 entregas. Shape real (confirmado ao vivo):

[
{
"id": 1299,
"eventType": "webhook.test",
"status": "pending",
"attempts": 0,
"nextAttemptAt": "..."
},
{
"id": 1298,
"eventType": "message.receipt",
"status": "delivered",
"attempts": 1,
"lastCode": 200,
"createdAt": "..."
}
]

Eventos reais observados em produção: message.receipt e connection.update, além do webhook.test sintético.

Se uma entrega falhar, ela fica pending/failed com lastError preenchido (ex.: connection refused) e tenta de novo com backoff exponencial. Para reenfileirar manualmente uma entrega failed/dead (zera as tentativas):

Janela do terminal
curl -X POST -H "Authorization: Bearer <ADMIN_TOKEN>" \
https://sua-instancia.exemplo/instance/{id}/webhooks/{webhookId}/deliveries/{deliveryId}/retry

SSRF: destinos privados são bloqueados por padrão

Seção intitulada “SSRF: destinos privados são bloqueados por padrão”

O dispatcher resolve o host do webhook uma vez e conecta no IP exato resolvido (evita reresolução DNS maliciosa entre a checagem e a entrega). Por padrão, IP privado/loopback é recusado — só é permitido com WEBHOOK_ALLOW_PRIVATE=true no backend, pensado para desenvolvimento local (o próprio ambiente de testes do zapgate usa um receptor interno, webhooksink, só alcançável porque essa flag está ligada ali).