Configuração e segurança
Como proteger seu endpoint de webhook: validação da assinatura HMAC-SHA256 (X-IORQ-Signature), defesa contra replay, headers custom como camada adicional, TLS e o que responder a cada entrega.
1. Assinatura HMAC das entregas
Toda entrega de webhook é assinada com HMAC-SHA256 usando o signing_secret do webhook. Dois headers acompanham cada POST:
| Header | Conteúdo |
|---|---|
X-IORQ-Signature | t=<unix_ts>,v1=<hex> — o digest cobre "{timestamp}.{corpo_bruto}" |
X-IORQ-Timestamp | O mesmo unix_ts, para conveniência |
O signing_secret (prefixo whsec_) é retornado uma única vez, na resposta do POST /webhook — guarde-o em cofre imediatamente. GET, PATCH e listagem não o retornam.
Webhooks criados antes da assinaturaWebhooks registrados antes da disponibilização da assinatura não têm
signing_secrete seguem sendo entregues sem os headers acima (nada muda para eles). Para passar a receber entregas assinadas, registre um novo webhook.
Como validar do seu lado
- Extraia
tev1do headerX-IORQ-Signature - Concatene:
signed_payload = t + "." + corpo_bruto(use os bytes exatos do body recebido, antes de qualquer parse) - Calcule HMAC-SHA256(
signing_secret,signed_payload) - Compare com
v1em tempo constante (hmac.compare_digestem Python ou equivalente) - Rejeite se divergir, ou se
testiver a mais de 5 minutos do relógio do seu servidor
Implementações
# Python
import hmac, hashlib, time
def verify(payload_bytes, signature_header, secret, tolerance=300):
parts = dict(p.split("=", 1) for p in signature_header.split(","))
ts, sig = parts["t"], parts["v1"]
if abs(time.time() - int(ts)) > tolerance:
return False
signed = f"{ts}.{payload_bytes.decode()}"
expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)// Node.js
const crypto = require('crypto');
function verify(payloadBytes, signatureHeader, secret, tolerance = 300) {
const parts = Object.fromEntries(
signatureHeader.split(',').map(p => p.split('='))
);
const { t: ts, v1: sig } = parts;
if (Math.abs(Date.now() / 1000 - parseInt(ts)) > tolerance) return false;
const signed = `${ts}.${payloadBytes.toString()}`;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(sig);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}2. Defesa contra replay
- Tolerância de tempo — recuse entregas cujo
X-IORQ-Timestampesteja a mais de 5 minutos do horário do servidor (já incluída no exemplo acima). - Deduplicação — a entrega é at-least-once e o payload não traz um
event_id; deduplique pelo conteúdo dedata(ex.:entity_id+status+nosso_numero). Ver Idempotência e reentrega.
3. Headers custom (camada adicional)
Os headers registrados no webhook continuam sendo reenviados em cada entrega. Use-os como segunda barreira (ex.: um header secreto validado no seu lado), mas trate a assinatura HMAC como a defesa autoritativa — headers custom não cobrem integridade do corpo nem replay.
4. TLS e certificado
- A URL registrada precisa ser HTTPS — URLs
http://são rejeitadas na criação e na atualização do webhook. - Certificado válido emitido por CA pública. Mínimo TLS 1.2; TLS 1.3 recomendado.
5. Rotação do signing_secret
signing_secretAinda não há endpoint de rotação de secret. Em caso de suspeita de comprometimento, registre um novo webhook (que recebe um secret novo) e desative o antigo via PATCH com {"status": "INACTIVE"} — ou contate a IORQ.
6. O que responder no webhook
| HTTP | Quando devolver | Comportamento IORQ |
|---|---|---|
2xx | Recebido/processado | Marca como entregue, não reenvia |
4xx / 5xx / timeout | Falha no seu lado | Reenvia com backoff (até o limite de tentativas — ver Idempotência) |
