# 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.

Quelle: https://whovisit.app/pt/blog/webhooks-explicados-assinatura-seguranca
Veröffentlicht: 2026-11-07
Herausgeber: Whovisit (ARAVILO, Innsbruck)

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

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](https://docs.stripe.com/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](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) 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](https://www.standardwebhooks.com/). 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](https://community.owasp.org/attacks/Server_Side_Request_Forgery) 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:

```bash
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

```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](https://whovisit.app/blog/website-besuche-ins-crm). A alternativa por consulta descreve-a [Que empresas visitam o meu website?](https://whovisit.app/blog/welche-firmen-besuchen-meine-website). Que dados fluem aí explica-o [Como funciona o reconhecimento de empresas?](https://whovisit.app/blog/firmenerkennung-website-technik).

## 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.
