Webhooks
Registar um endpoint, verificar a assinatura, e o que faz com que o desliguemos.
Em vez de perguntares periodicamente o que aconteceu, dizes-nos para onde
empurrar. Cada evento sai num POST, assinado, com repetições.
Registar
No painel, em Webhooks, ou pela API:
curl -X POST https://app.c-connect.pt/v1/webhooks \
-H "Authorization: Bearer $CCONNECT_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.aempresa.pt/cconnect",
"events": ["delivered", "bounced", "complained"]
}'
Só https, e com certificado válido: a carga leva endereços de destinatários.
Sem events, enviamos todos. Um endpoint que não recebe nada é pior do que um
que recebe a mais, por isso a omissão é generosa e não restritiva.
A resposta traz o segredo de assinatura, uma vez e só uma. Guarda-o.
Verificar a assinatura
Cada entrega traz este cabeçalho:
X-CConnect-Signature: t=1758182462,v1=8f3c...9ab
O t é a hora em segundos. O v1 é um HMAC SHA-256, com o teu segredo, sobre a
string "<t>.<corpo em bruto>".
O corpo em bruto, byte a byte como chegou. Se descodificares o JSON e voltares a codificá-lo, a assinatura deixa de bater, porque a ordem das chaves e os espaços mudam.
$timestamp = ...; // o t do cabeçalho
$expected = hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
if (! hash_equals($expected, $received)) {
abort(400);
}
Usa sempre uma comparação de tempo constante, o hash_equals em PHP. Um ===
sobre strings devolve mais depressa quando os primeiros caracteres não batem,
e isso chega para um atacante adivinhar a assinatura byte a byte.
Verifica também que o t é recente. Aceitamos cinco minutos de tolerância do
nosso lado; faz o mesmo, e recusa uma entrega mais velha do que isso. É o que
impede que alguém capture um pedido e o repita amanhã.
O que responder
200, e depressa. Se demorares mais de dez segundos consideramos a tentativa
falhada.
O padrão que funciona é: verificar a assinatura, gravar o evento numa fila
tua, responder 200, e processar depois. Um endpoint que faz trabalho pesado
antes de responder acaba por parecer avariado num dia de volume.
Qualquer resposta fora dos 2xx conta como falha, incluindo um 301 para outro
URL: não seguimos redireccionamentos, porque um redireccionamento é uma forma de
enviar a carga para um sítio que não registaste.
As repetições
Sete tentativas, ao longo de cerca de 33 horas:
| Tentativa | Depois de |
|---|---|
| 1 | imediatamente |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
| 6 | 6 horas |
| 7 | 24 horas |
Depois disso, aquela entrega é dada como falhada e não volta a ser tentada.
Quando desligamos um endpoint
Ao fim de dez falhas seguidas, desactivamos o endpoint. No painel aparece
como Desactivado por falhas seguidas, e pela API o campo disabled_at fica
preenchido.
É dito em vez de ficar em silêncio de propósito: um endpoint que deixou de receber sem ninguém saber é a razão pela qual alguém descobre um mês depois que perdeu os eventos todos.
Uma entrega com sucesso põe o contador a zero. Para voltar a ligar, o botão Reativar no painel, ou:
curl -X PATCH https://app.c-connect.pt/v1/webhooks/{id} \
-H "Authorization: Bearer $CCONNECT_KEY" \
-d '{"enabled": true}'
Rodar o segredo
Também no painel. O segredo novo aparece uma vez, e as entregas que já estavam a caminho foram assinadas com o anterior: o teu sistema vai recusá-las se já tiver mudado. Se isso te incomodar, aceita as duas assinaturas durante alguns minutos antes de largares a antiga.