Pular para o conteúdo principal
Abrir no ChatGPTAbrir no Claude

Receber webhook e validar a assinatura

Em vez de consultar o job, deixe a RMF avisar o seu servidor. As entregas seguem o padrão Standard Webhooks e são assinadas com HMAC-SHA256. O corpo leva só o identificador, o tipo do evento e o estado: nunca dado fiscal. Para ler o resultado, chame GET /v1/arquivos/{id} ou GET /v1/relatorios/{id}.

Escopo webhooks:gerenciar

1. Crie o webhook​

POST /v1/webhooks, com Idempotency-Key:

Corpo da requisição
{
"url": "https://integracao.exemplo.com.br/rmf/eventos",
"eventos": ["arquivo.lido", "relatorio.concluido", "relatorio.falhou"],
"descricao": "Integração com o ERP"
}

A resposta traz o segredo whsec_... uma única vez: guarde-o no cofre do seu sistema. A URL precisa ser HTTPS na porta 443, com nome público; ela é conferida de novo a cada entrega e a entrega é descartada se o nome passar a resolver para endereço interno. URL fora da regra: 422 url_invalida.

EventoQuando
arquivo.lidoA leitura terminou, em lido ou em periodo_ja_lido (o estado do corpo diz qual)
arquivo.rejeitadoA leitura foi recusada; o motivo está em GET /v1/arquivos/{id}
relatorio.concluidoO relatório terminou
relatorio.falhouO relatório falhou
Corpo de uma entrega
{
"id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
"tipo": "arquivo.lido",
"criado_em": "2026-10-29T11:41:12-03:00",
"recurso": { "tipo": "arquivo", "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP" },
"estado": "lido"
}

2. Valide a assinatura​

Cada entrega traz três cabeçalhos: webhook-id, webhook-timestamp e webhook-signature (v1,<base64>; pode vir mais de uma, separadas por espaço, durante a troca de segredo).

  1. Monte o texto webhook-id + . + webhook-timestamp + . + o corpo bruto (os bytes exatos recebidos, antes de interpretar o JSON).
  2. Calcule o HMAC-SHA256 desse texto usando como chave os bytes do segredo (a parte depois de whsec_, decodificada de Base64).
  3. Compare em tempo constante com cada assinatura v1,... do cabeçalho.
  4. Recuse entregas com webhook-timestamp muito distante do seu relógio (os exemplos usam 5 minutos).

Os exemplos rodam offline, com um vetor fixo de teste, e provam as duas pontas: a assinatura certa é aceita e um corpo adulterado é recusado.

webhook.sh
#!/bin/sh
# Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.
# Requer openssl. Em produção, leia o corpo BRUTO recebido e o segredo do seu cofre.
set -eu

SEGREDO='whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8='
WEBHOOK_ID='evt_01JC8F8D7S2T5W8Y1B4E7GHKMN'
WEBHOOK_TIMESTAMP='1793284930'
WEBHOOK_SIGNATURE='v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg='
CORPO='{"id":"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN","tipo":"arquivo.lido","criado_em":"2026-10-29T11:41:12-03:00","recurso":{"tipo":"arquivo","id":"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"},"estado":"lido"}'
AGORA=1793284990 # no seu servidor: $(date +%s)

verificar() { # verificar <segredo> <id> <timestamp> <assinaturas> <corpo> <agora>
# recusa horário distante do seu relógio (5 minutos)
atraso=$(($6 - $3))
[ "${atraso#-}" -le 300 ] || return 1
# a chave são os bytes do segredo depois de "whsec_", decodificados de Base64
chave_hex=$(printf '%s' "${1#whsec_}" | base64 -d | od -An -v -tx1 | tr -d ' \n')
esperada=$(printf '%s.%s.%s' "$2" "$3" "$5" |
openssl dgst -sha256 -mac HMAC -macopt "hexkey:$chave_hex" -binary | base64)
# o cabeçalho pode trazer várias assinaturas "v1,<base64>" separadas por espaço
for item in $4; do
[ "${item%%,*}" = v1 ] && [ "${item#v1,}" = "$esperada" ] && return 0
done
return 1
}

if verificar "$SEGREDO" "$WEBHOOK_ID" "$WEBHOOK_TIMESTAMP" "$WEBHOOK_SIGNATURE" "$CORPO" "$AGORA"; then
echo "assinatura=valida"
else
echo "assinatura=invalida" >&2
exit 1
fi
if verificar "$SEGREDO" "$WEBHOOK_ID" "$WEBHOOK_TIMESTAMP" "$WEBHOOK_SIGNATURE" "${CORPO}x" "$AGORA"; then
echo "corpo adulterado foi aceito" >&2
exit 1
fi
echo "adulterado=recusado"
Exemplo testado no CI, offline, com vetor fixo

3. Responda e trate repetição​

  • Responda com qualquer 2xx para confirmar. Demore pouco: a entrega espera até 10 segundos no total.
  • Entrega perdida é repetida com espera exponencial e pode ser repetida à mão com POST /v1/eventos/{id}/reenviar (qualquer evento dos últimos 30 dias, até 100 por dia).
  • Trate webhook-id como chave de deduplicação: a mesma entrega pode chegar mais de uma vez.
  • POST /v1/webhooks/{id}/teste manda um evento de teste para a sua URL.
  • No sandbox há até 10 entregas por dia.