Configuração e segurança

1. Validação HMAC

Todo webhook é assinado com HMAC-SHA256 usando o signing_secret retornado no registro (Configurando webhooks). A assinatura vai no header X-IORQ-Signature:

X-IORQ-Signature: t=1715601234,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

Formato: t=<timestamp>,v1=<hash>. O timestamp também aparece em X-IORQ-Timestamp (redundante para conveniência).

Como calcular do seu lado

  1. Extraia o timestamp e o v1 do header
  2. Concatene: signed_payload = timestamp + "." + body_raw
  3. Calcule HMAC-SHA256(signing_secret, signed_payload)
  4. Compare em tempo constante com v1 (use hmac.compare_digest em Python ou equivalente)
  5. Rejeite se diferente, ou se o timestamp tem mais de 5 minutos de diferença do agora

Implementações

# Python
import hmac, hashlib, time

def verify(payload_bytes, signature_header, secret, tolerance=300):
    parts = dict(p.split('=') 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');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

2. Defesa contra replay

Sem proteção contra replay, alguém pode capturar uma chamada legítima e reenviar depois — mesmo sem conhecer o secret. Duas defesas:

  • Tolerância de tempo — recuse eventos cujo X-IORQ-Timestamp esteja mais de 5 minutos do horário do servidor. Já incluído no exemplo de verificação acima.
  • Deduplicação por X-IORQ-Event-Id — guarde os IDs de eventos processados nos últimos N minutos; rejeite duplicados.

3. TLS e certificado

  • A URL registrada precisa ser HTTPS com certificado válido emitido por CA pública. A IORQ verifica a cadeia — self-signed é rejeitado.
  • Mínimo TLS 1.2. TLS 1.3 recomendado.
  • Não use certificate pinning sem aviso prévio — a IORQ pode rotacionar certificados sem aviso conforme política de renovação.

4. IPs de origem

Para defesa em profundidade, você pode restringir seu endpoint a aceitar chamadas dos IPs de saída da IORQ. A lista é pública e estável (mudanças com 30 dias de aviso):

AmbienteIPs CIDR
Sandbox52.67.0.0/24
Produção54.232.0.0/24
📘

Cuidado

Restrição por IP não substitui validação HMAC. Trate como segunda barreira, não primeira. A validação HMAC é a defesa autoritativa.

5. Rotação do signing_secret

Em caso de suspeita de vazamento, rotacione imediatamente:

curl -X POST 'https://hs-api.iorq.com.br/webhook/wh_2A8f1b3c4d5e/rotate-secret' \
  -H 'Authorization: Bearer YOUR_TOKEN'
{
  "webhook_id": "wh_2A8f1b3c4d5e",
  "signing_secret": "whsec_NEW_SECRET",
  "rotated_at": "2026-05-13T12:30:00Z",
  "old_secret_valid_until": "2026-05-13T13:00:00Z"
}

O secret antigo continua válido por 30 minutos após a rotação, para você atualizar seus serviços sem janela de invalidação. Após esse prazo, apenas o novo aceita.

6. O que responder no webhook

HTTPQuando devolverComportamento IORQ
200, 201, 204Recebido e enfileirado/processadoMarca como entregue, não reenvia
4xxErro de assinatura, evento desconhecido, payload corrompidoMarca como falha permanente; não reenvia
5xxErro temporário do seu ladoReenvia com backoff exponencial (ver Idempotência)
Timeout (acima de 10s)Não respondeu a tempoReenvia como se fosse 5xx

7. Próximos passos