Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.teceo.co/llms.txt

Use this file to discover all available pages before exploring further.

se você configurou um signing secret no webhook, cada notificação chega assinada. nós usamos essa chave para criar um hash HMAC-SHA256 pra você validar que o evento veio realmente da gente.

como funciona

  1. você configura um secret e salva em algum lugar no seu sistema onde você possa acessar pra validar as requisições
  2. a gente calcula um hash HMAC-SHA256 e enviamos no header X-Webhook-Signature
  3. você recalcula o mesmo hash com o secret que você tem salvo
  4. você compara os dois hashes e se bater, é porque a requisição é legítima

headers que recebe

X-Webhook-Signature
string
required
hash HMAC-SHA256 do payload assinado, no formato sha256= seguido do valor em hexadecimal (ex: sha256=abc123def456...).
X-Webhook-Timestamp
string
required
timestamp ISO 8601 do evento, usado para evitar ataques de replay.

como calcular o hash

o hash é calculado com a concatenação de timestamp.body:

o valor assinado é construído assim

Typescript
const payload = `${timestamp}.${body}`;
const hash = HMAC_SHA256({ secret: "sua-secret", payload });
por que timestamp + body? isso evita ataques de replay — o timestamp tem validade limitada, então mesmo que alguém capture uma requisição, não consegue reenviá-la depois.

validar a assinatura

const crypto = require('crypto');
function validateSignature(body, timestamp, signature, secret) {
  // cria a string assinada: timestamp.body
  const payload = timestamp + '.' + JSON.stringify(body);
  // calcula HMAC-SHA256
  const hash = crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');
  // compara com o header recebido
  const expected = 'sha256=' + hash;
  return signature === expected;
}
app.post('/webhooks', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  // valida a assinatura
  if (!validateSignature(req.body, timestamp, signature, 'seu-secret')) {
  return res.status(401).json({ error: 'invalid signature' });
  }
  // processa webhook
  res.status(200).json({ ok: true });
});

importante

  • use o corpo bruto (raw): se o seu framework parseia o JSON automaticamente (ex: Express com express.json()), reconverta com JSON.stringify(body) antes de calcular o hash. se você tiver o raw body como string, use diretamente sem stringify
  • a ordem importa: é sempre timestamp + . + body, nessa sequência
  • timestamp vem do header: sempre use X-Webhook-Timestamp, nunca tente adivinhar ou usar a hora do seu servidor

segurança

  • sempre valide: mesmo se parecer desnecessário
  • nunca commite o secret no controle de versão
  • guarde em variável de ambiente ou serviço de secrets
  • sem validação = risco: qualquer um pode se passar pela plataforma