WebhookToolkit
Stripe · GitHub · Shopify · Slack

Validador de assinatura de webhook

Última atualização: julho de 2026

Seu provedor envia um cabeçalho de assinatura, seu handler diz que não bate, e a documentação responde com um SDK para instalar. Isso não te diz por quê. Cole a assinatura, seu secret e o corpo bruto abaixo: recalculamos o HMAC no seu navegador e, ao falhar, testamos as mutações que costumam causar isso — um espaço a mais no secret, uma quebra de linha final, um corpo re-serializado, um timestamp vencido.

Em resumo: uma assinatura de webhook falha quase sempre por uma de cinco razões — secret errado (test vs live), um corpo re-serializado pelo seu framework, um timestamp fora da janela de 5 minutos, uma quebra de linha final ou um prefixo sha256= / v0= que sobrou. Esta ferramenta recalcula o HMAC e diz qual é.

Cole uma assinatura que falha

Tudo roda no seu navegador com a Web Crypto API. Seu secret de assinatura nunca é enviado aos nossos servidores — confira na aba Rede.

Os quatro esquemas de assinatura

Os quatro são HMAC, mas diferem no que é hasheado e em como o resultado é codificado. Um detalhe errado e toda entrega falha.

ProvedorO que é assinadoAlgoritmo
StripeHMAC de t.body; assinatura e timestamp em um cabeçalho Stripe-SignatureSHA-256 hex
GitHubHMAC do corpo bruto; assinatura em X-Hub-Signature-256SHA-256 hex
ShopifyHMAC do corpo bruto; assinatura em X-Shopify-Hmac-Sha256SHA-256 base64
SlackHMAC de v0:timestamp:body; também precisa do cabeçalho timestampSHA-256 hex

O Twilio é a exceção — ele hasheia uma URL canônica mais parâmetros ordenados, não o corpo. Use o validador de assinatura do Twilio para isso.

As cinco causas reais

Ordenadas por quanto aparecem em threads de suporte.

#CausaSintomaCorreção
1Secret erradoToda entrega falha, mesmo uma novaConfusão test vs live — cada provedor emite um secret de assinatura diferente por modo e por endpoint.
2Corpo re-serializadoFalha depois que seu framework parseou o JSONAssine os bytes brutos, não JSON.stringify(parsed): espaçamento e ordem das chaves mudam o hash.
3Timestamp vencidoPassa nos testes, falha em eventos reenviadosStripe e Slack rejeitam o que passa de 5 minutos. Não reenvie um evento capturado horas depois.
4Quebra de linha finalCorpo deslocado um byte, assinatura nunca bateUm proxy ou editor acrescentou uma quebra de linha. A ferramenta sinaliza esse caso exato.
5Prefixo comparado literalsha256= ou v0= incluído na comparaçãoCompare só a parte hex/base64, não o prefixo do esquema.

A armadilha do corpo bruto

A causa número um: você verifica contra o corpo parseado-e-re-serializado em vez dos bytes exatos. JSON.stringify(JSON.parse(body)) muda espaçamento e ordem das chaves, então o HMAC não bate mais. Leia o corpo bruto antes de qualquer coisa parseá-lo.

Express
// Express: keep the RAW bytes, do NOT let JSON re-serialise them
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const raw = req.body;                 // Buffer, exactly as received
  verify(raw, req.headers['stripe-signature'], secret);
});
Flask
# Flask: request.get_data() returns the raw body; request.json does NOT
raw = request.get_data()               # bytes, untouched
verify(raw, request.headers['X-Hub-Signature-256'], secret)
Next.js App Router
// Next.js App Router: read the text before parsing
export async function POST(req) {
  const raw = await req.text();         // sign THIS, not JSON.stringify(await req.json())
  verify(raw, req.headers.get('stripe-signature'), secret);
}

Regra prática: capture a assinatura e os bytes brutos na borda, verifique, depois parseie.

Timestamps e replays

Stripe e Slack embutem um timestamp na string assinada e rejeitam o que passa de cinco minutos — é proteção contra replay, não um bug. GitHub e Shopify não, então uma assinatura GitHub válida continua válida para sempre. Se a ferramenta diz válida mas expirada, seu secret e corpo estão bem; você só está reenviando um evento antigo. Gere uma entrega nova em vez de reenviar uma capturada.

Quando a assinatura confere

Precisa enviar um evento de teste corretamente assinado ao seu endpoint? O assinador de webhooks monta um para os seis provedores. Para capturar e inspecionar entregas reais, aponte-as para um túnel Relay.

Abrir o assinadorTwilio signature validatorRelay

Perguntas frequentes

Vocês guardam meu secret de assinatura?

Não. O HMAC é calculado no seu navegador com Web Crypto; o secret e o corpo nunca chegam aos nossos servidores. Abra a aba Rede: ao clicar em Verificar, nenhuma requisição sai.

Quais provedores são suportados?

Stripe, GitHub, Shopify e Slack — os quatro esquemas HMAC sobre o corpo. O Twilio assina uma URL canônica, então tem ferramenta própria: o validador de assinatura do Twilio.

Passa aqui mas meu servidor rejeita mesmo assim. Por quê?

Quase sempre o corpo. Seu framework parseou o JSON e você o re-serializou antes de verificar, o que muda espaçamento e ordem das chaves. Assine os bytes brutos — veja os trechos Express, Flask e Next acima.

O que significa 'válida mas expirada'?

O HMAC está correto, mas Stripe e Slack também recusam assinaturas com mais de cinco minutos para bloquear replays. Se você capturou um evento e o reenvia depois, a verificação falha no timestamp mesmo com o secret certo.

Validador de assinatura de webhook: Stripe, GitHub, Shopify, Slack · Webhook Toolkit