Saltar para o conteúdo

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.

Dois caminhos em comparação: na consulta à API, o seu sistema pergunta uma e outra vez, no webhook o remetente chama o seu endereço uma única vez, assim que algo acontece
Gráfico: Whovisit · Ver o gráfico em tamanho grande

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 sistemaO remetente
Quando?Segundo um horário, por exemplo de hora a horaAssim que ocorre um evento
EsforçoPedidos mesmo sem novidadesÉ necessário um recetor acessível
Bom paraRelatórios, reconciliação, recarregamentoNotificação, reencaminhamento
RiscoAtraso até à consulta seguinteA 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

  1. Ocorre um evento, por exemplo „pagamento recebido“.
  2. O remetente envia um POST com JSON para o seu endereço registado (HTTPS).
  3. O seu recetor verifica a assinatura, memoriza a mensagem e responde rapidamente com um código da gama 2xx.
  4. 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.

Continuar a ler