Pular para o conteúdo principal

Segurança

Duas camadas protegem a comunicação: a assinatura HMAC (prova que o payload veio do Dunning e não foi adulterado) e a autenticação do request (credenciais que o Dunning apresenta ao seu endpoint). A primeira vai sempre; a segunda é opcional.

Assinatura HMAC

Toda entrega leva o header:

X-Webhook-Signature: t=<unix>,v1=<hex>
  • t é o timestamp (unix, segundos) do momento do envio — cada retry é re-assinado com um t novo.
  • v1 é o HMAC-SHA256 de "{t}.{body}" (timestamp, um ponto e o body cru da requisição), calculado com o secret do endpoint (aquele whsec_... exibido uma única vez no cadastro), em hexadecimal minúsculo.

Vincular o timestamp à assinatura fecha o replay: um atacante que capture uma entrega não consegue reapresentá-la mais tarde, porque o t fica fora da janela de tolerância — e não consegue trocar o t sem invalidar o HMAC. Tolerância recomendada: 5 minutos.

Verificando a assinatura

Extraia t e v1, rejeite timestamps fora da tolerância e compare o HMAC de "{t}.{body}" em tempo constante (sempre sobre o body bruto, antes de qualquer parse de JSON). O algoritmo é o mesmo em qualquer linguagem — o mesmo verificador em Node.js, Python, PHP e Ruby:

const crypto = require('crypto');

const TOLERANCE_SECONDS = 300; // 5 minutos

function verifySignature(rawBody, signatureHeader, secret) {
const t = /(?:^|,)t=(\d+)(?:,|$)/.exec(signatureHeader || '')?.[1];
const v1 = /(?:^|,)v1=([0-9a-f]+)(?:,|$)/i.exec(signatureHeader || '')?.[1];
if (!t || !v1) return false;

// Anti-replay: rejeita assinaturas fora da janela de tolerância
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(t)) > TOLERANCE_SECONDS) return false;

const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(v1.toLowerCase(), 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Exemplo com Express (repare no bodyParser cru):
app.post('/webhooks/dunning', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verifySignature(
req.body, // Buffer com o body cru
req.get('X-Webhook-Signature'),
process.env.DUNNING_WEBHOOK_SECRET
);
if (!ok) return res.status(401).end();

const event = JSON.parse(req.body);
// enfileire e responda rápido
res.status(200).end();
});

Armadilhas clássicas:

  • Não re-serialize o JSON para calcular o HMAC: qualquer diferença de espaço ou ordem de chaves muda a assinatura. Use o body exatamente como chegou.
  • Concatene na ordem certa: o HMAC cobre t, um ponto (.) e o body — nessa ordem.
  • Use comparação em tempo constante (crypto.timingSafeEqual), nunca ===: comparação simples vaza informação por timing.
  • Relógio dessincronizado no seu servidor derruba verificações legítimas: mantenha NTP em dia antes de reduzir a tolerância.
  • Rejeite requisições sem o header ou com assinatura inválida com 401, sem detalhar o motivo.

Um retry da mesma entrega chega com t (e assinatura) diferentes — a janela de tolerância vale para qualquer tentativa. Para deduplicar entregas, use o header X-Webhook-Delivery-Id, não a assinatura.

Autenticação do request

Se o seu endpoint fica atrás de um gateway que exige credencial, configure a autenticação no cadastro:

TipoO que o Dunning envia
Nenhuma (none)Só a assinatura HMAC
Basic (basic)Authorization: Basic <usuário:senha>
Bearer (bearer)Authorization: Bearer <token>
Header (header)Um header custom com nome e valor definidos por você

As credenciais são cifradas em repouso (AES-256-GCM) e nunca aparecem na trilha de entregas: na captura de request/response, a assinatura e os headers de autenticação são gravados como [REDACTED].

Rotação de secret

O botão de rotação (na linha do endpoint) gera um secret novo, exibido uma única vez, e invalida o anterior imediatamente (também disponível via POST /api/v1/webhook-endpoints/{id}/rotate-secret). Rotacione se houver suspeita de vazamento ou como higiene periódica; atualize o consumidor no mesmo momento, pois entregas assinadas com o secret antigo deixarão de validar.

Proteções do lado do Dunning

  • Só HTTPS: URLs http:// são recusadas no cadastro.
  • Anti-SSRF: a URL é validada no cadastro e revalidada a cada entrega (DNS re-resolvido, IP fixado, redes internas bloqueadas). Redirects não são seguidos. Um destino que passa a resolver para rede interna é bloqueado permanentemente (SSRF blocked, sem retry).
  • Respostas do seu endpoint são capturadas com headers sensíveis (Set-Cookie, Authorization...) redigidos.