Técnica · 7 de novembro de 2026 · 6 min de leitura
Webhooks explicados de forma simples: assinatura, repetição, segurança
O que é um webhook, como difere da consulta à API e como tratar assinatura, repetição e duplicados. Com curl e Node.
Um webhook é uma chamada no sentido contrário. Em vez de o seu sistema perguntar a um serviço de poucos em poucos minutos se aconteceu algo, o serviço chama um endereço seu assim que acontece. Isso poupa consultas e entrega novidades mais depressa. Em contrapartida, tem de disponibilizar e proteger um recetor. Este artigo explica ambas as coisas.
Webhook ou consulta à API
| Consulta à API (polling) | Webhook | |
|---|---|---|
| Quem começa? | O seu sistema | O remetente |
| Quando? | Segundo um horário, por exemplo de hora a hora | Assim que ocorre um evento |
| Esforço | Pedidos mesmo sem novidades | É necessário um recetor acessível |
| Bom para | Relatórios, reconciliação, recarregamento | Notificação, reencaminhamento |
| Risco | Atraso até à consulta seguinte | A mensagem perde-se se o recetor não responder |
Na prática, muitos combinam ambos: o webhook comunica o evento, a API vai buscar os pormenores quando necessário ou fecha lacunas após uma falha.
Como decorre uma entrega
- Ocorre um evento, por exemplo „pagamento recebido“.
- O remetente envia um POST com JSON para o seu endereço registado (HTTPS).
- O seu recetor verifica a assinatura, memoriza a mensagem e responde rapidamente com um código da gama 2xx.
- O trabalho propriamente dito é feito depois pelo seu sistema.
O passo 3 é importante. A Stripe recomenda na documentação de webhooks devolver imediatamente um código 2xx antes de executar lógica trabalhosa que possa provocar um timeout. Quem primeiro lança a fatura e depois responde arrisca repetições desnecessárias.
Assinatura: como verificar a origem
O seu endereço é acessível publicamente. Qualquer pessoa poderia, portanto, enviar-lhe mensagens inventadas. Por isso, remetentes sérios assinam cada mensagem com um segredo partilhado. É habitual um HMAC com SHA-256 sobre o conteúdo, muitas vezes juntamente com uma marca temporal. O GitHub descreve o procedimento assim: HMAC-SHA256 sobre o conteúdo, representado em hexadecimal, com o cabeçalho X-Hub-Signature-256. A comparação deve ser feita com uma função de tempo de execução constante, para que os atacantes nada possam deduzir dos tempos de resposta.
Pontos importantes para o seu recetor:
- Usar o conteúdo em bruto. Calcule a assinatura sobre os bytes recebidos, não sobre o JSON serializado de novo. Basta uma quebra de linha diferente para alterar a assinatura.
- Verificar a marca temporal. A Stripe usa uma marca temporal na assinatura e uma tolerância predefinida de cinco minutos para impedir ataques de repetição. Uma tolerância de 0 desativa a verificação.
- Manter o segredo secreto. Não no código, não no repositório, mas numa variável de ambiente ou num cofre.
Diretrizes de código aberto para remetentes e recetores são reunidas pela iniciativa Standard Webhooks. Como cada fornecedor forma concretamente a sua assinatura consta da respetiva documentação.
Repetições e entrega duplicada
Se o seu recetor não responder ou responder com um código de erro, o remetente tenta de novo. A Stripe, por exemplo, repete segundo a própria documentação com intervalos crescentes até três dias. Outros fornecedores têm outras regras, alguns desativam o webhook após demasiadas falhas. Daqui resulta:
- Conte com duplicados. O mesmo evento pode chegar várias vezes, mesmo que o seu recetor funcione corretamente. A Stripe chama expressamente a atenção para isso.
- Torne o tratamento idempotente. Isto significa: quem trata a mesma mensagem duas vezes não gera um segundo resultado. Guarde o identificador do evento e salte o que já é conhecido.
- Não confie na ordem. Os eventos podem chegar trocados. Em caso de dúvida, verifique o estado atual através da API.
Segurança de ambos os lados
Para recetores: apenas HTTPS, verificar assinatura e idade, limitar o tamanho da mensagem, não executar comandos a partir do conteúdo. Trate o conteúdo como qualquer entrada vinda do exterior.
Para remetentes que chamam endereços de clientes: quem oferece webhooks chama endereços que outros registaram. É um caminho clássico para Server-Side Request Forgery. O atacante regista um endereço interno. O servidor chama-o a partir da rede protegida. A descrição da OWASP indica como alvos, por exemplo, serviços internos e endereços de metadados de fornecedores de cloud. Medidas de proteção razoáveis:
- permitir apenas
https://, - resolver o endereço ao registá-lo e antes de cada chamada e rejeitar intervalos privados, locais e reservados,
- não seguir redirecionamentos às cegas,
- definir limites de tempo curtos.
Exemplo: enviar uma mensagem de teste com curl
Com curl e openssl pode testar o seu recetor sem o verdadeiro remetente. O script envia uma mensagem assinada, como a enviaria um remetente com marca temporal:
GEHEIMNIS="test-geheimnis"
ZEIT=$(date +%s)
INHALT='{"id":"evt_1","typ":"besuch.begonnen","firma":"beispiel-firma.example"}'
SIGNATUR="sha256=$(printf '%s.%s' "$ZEIT" "$INHALT" | openssl dgst -sha256 -hmac "$GEHEIMNIS" | sed 's/^.* //')"
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "X-Zeit: $ZEIT" \
-H "X-Signatur: $SIGNATUR" \
-d "$INHALT"
Exemplo: um pequeno recetor em Node.js
const http = require('http')
const crypto = require('crypto')
const GEHEIMNIS = process.env.WEBHOOK_GEHEIMNIS || 'test-geheimnis'
const gesehen = new Set() // na realidade: base de dados
http.createServer((req, res) => {
if (req.method !== 'POST' || req.url !== '/webhook') { res.writeHead(404); return res.end() }
const teile = []
req.on('data', t => teile.push(t))
req.on('end', () => {
const roh = Buffer.concat(teile).toString('utf8')
const zeit = req.headers['x-zeit'] || ''
const erwartet = 'sha256=' + crypto.createHmac('sha256', GEHEIMNIS).update(zeit + '.' + roh).digest('hex')
const erhalten = String(req.headers['x-signatur'] || '')
const a = Buffer.from(erwartet), b = Buffer.from(erhalten)
const echt = a.length === b.length && crypto.timingSafeEqual(a, b)
const frisch = Math.abs(Date.now() / 1000 - Number(zeit)) < 300
if (!echt || !frisch) { res.writeHead(401); return res.end() }
const ereignis = JSON.parse(roh)
if (!gesehen.has(ereignis.id)) { gesehen.add(ereignis.id); console.log('neu:', ereignis.typ) }
res.writeHead(200); res.end('ok')
})
}).listen(3000)
O recetor verifica assinatura e idade, salta identificadores conhecidos e responde de imediato. Para a operação precisa ainda de HTTPS, um limite de tamanho e uma fila de espera para o processamento posterior.
Onde os webhooks são comuns
Serviços de pagamento, plataformas de código e ferramentas de formulários comunicam eventos por webhook. Também o reconhecimento de visitantes o pode fazer: se o sistema comunica uma empresa que visita a sua página, esta chega diretamente a uma ferramenta à sua escolha. Como isso leva a um sistema de vendas mostra-o Levar visitas do website para o CRM. A alternativa por consulta descreve-a Que empresas visitam o meu website?. Que dados fluem aí explica-o Como funciona o reconhecimento de empresas?.
Perguntas frequentes
Qual é a diferença entre webhook e API? Na API, o seu sistema pergunta, no webhook o remetente comunica por si. Ambos se podem combinar.
O que acontece se o meu recetor falhar? A maioria dos remetentes repete a entrega durante um certo período. Durante quanto tempo e quantas vezes consta da documentação do fornecedor. Para lacunas, deve prever uma reconciliação por API.
Basta verificar o endereço do remetente? Não. Os endereços de remetente podem ser falsificados e mudam. Só é fiável a assinatura com um segredo que apenas você e o remetente conhecem.