Pular para o conteúdo

↑↓ navegar ↵ abrir Ctrl↵ nova aba esc fechar

No formato v2 a Rapid assina cada webhook com HMAC-SHA256 usando a sua chave de assinatura (o secret). A Rapid gera essa chave quando você cadastra o webhook e a mostra uma única vez no painel, em Configurações > Webhook; guarde na hora. Não há como recuperar a chave depois: se perder, peça uma nova ao suporte da Rapid (a anterior deixa de valer no mesmo instante). Sua aplicação deve validar essa assinatura antes de processar o payload, é assim que você confirma que o POST veio da Rapid.

Contas no formato legacy (migradas do sistema anterior) não recebem assinatura: a autenticação é feita pelos headers clientid/clientkey, como antes. Veja qual é o seu formato em Payload.

X-Webhook-Signature: sha256=<hmac_hex>

Onde <hmac_hex> é o HMAC-SHA256, em hexadecimal, da string ${timestamp}.${corpo_bruto}, ou seja o valor do header X-Webhook-Timestamp (segundos desde a epoch), um ponto, e o corpo bruto da requisição (o JSON recebido, byte a byte), usando a sua chave de assinatura.

O timestamp entra na assinatura para dar proteção contra replay: sem ele, um POST válido capturado poderia ser reenviado indefinidamente com assinatura correta. Rejeite requisições cujo X-Webhook-Timestamp esteja fora de uma janela de tolerância (recomendamos 5 minutos).


  1. Leia o corpo bruto da requisição. Não parse como JSON antes de validar: a assinatura é calculada sobre os bytes exatos recebidos.
  2. Monte signed = timestamp + "." + raw_body, calcule HMAC-SHA256(secret, signed) e codifique em hexadecimal
  3. Compare com o valor em X-Webhook-Signature (após o prefixo sha256=)
  4. Use comparação timing-safe para evitar timing attacks

Um endpoint completo em cada linguagem: lê o corpo bruto, valida a assinatura e a janela de tempo, e só então lê o JSON. Responde 401 para qualquer entrega que não passe, inclusive sem os headers. A chave vem da variável de ambiente RAPID_WEBHOOK_SECRET.

import crypto from 'node:crypto'
import express from 'express'
const TOLERANCIA_SEGUNDOS = 5 * 60
function validaAssinatura(corpoBruto, assinatura, timestamp, secret) {
// 1. Janela de tolerância (anti-replay). Sem isto, um POST antigo capturado
// continua passando pra sempre.
const idade = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp))
if (!Number.isFinite(idade) || idade > TOLERANCIA_SEGUNDOS) return false
// 2. Assina `timestamp.corpo_bruto` (o corpo recebido, sem reserializar).
const esperada = 'sha256=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${corpoBruto}`).digest('hex')
const a = Buffer.from(esperada)
const b = Buffer.from(assinatura ?? '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
const app = express()
// express.raw, e não express.json: a assinatura é calculada sobre o corpo bruto.
app.post('/webhooks/rapid', express.raw({ type: 'application/json' }), (req, res) => {
const corpoBruto = req.body.toString('utf8')
const ok = validaAssinatura(
corpoBruto,
req.get('X-Webhook-Signature'),
req.get('X-Webhook-Timestamp'),
process.env.RAPID_WEBHOOK_SECRET,
)
if (!ok) return res.status(401).end()
const evento = JSON.parse(corpoBruto)
// Processe o `evento` (de preferência numa fila) e responda rápido.
res.status(200).end()
})
app.listen(process.env.PORT ?? 3000)

A rotação é feita pelo suporte da Rapid, a pedido. A partir dela os webhooks novos já saem assinados com a chave nova, sem período de overlap: quem validar com a anterior passa a recusar. Combine a troca nos seus servidores e a rotação em sequência, de preferência em janela de baixo tráfego.

Salvar uma URL nova não mexe na chave.


No formato legacy, a Rapid envia os headers clientid e clientkey e não envia assinatura, exatamente como o sistema anterior.

clientid: <client_id>
clientkey: <client_secret>

No formato v2 esses headers não existem: validar por clientid/clientkey significaria receber a credencial de acesso à API em cada request (menos seguro que assinar o payload), então a autenticação da entrega é só X-Webhook-Signature. Quem está em legacy continua recebendo os headers por tempo indeterminado, para não quebrar integrações existentes.