Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Entender o que é um webhook e como ele difere de consultar uma API
- Receber um webhook e verificar a assinatura HMAC em Node.js
- Evitar os erros típicos: resposta lenta, evento duplicado e URL sem proteção
Imagine que você vende um produto e quer liberar a conta do cliente assim que ele pagar. Você pode perguntar ao gateway de pagamento a cada dez segundos se o pagamento já caiu, ou pode pedir para ele avisar quando acontecer. Essa segunda opção é um webhook.
Webhooks estão em quase tudo: pagamentos (inclusive confirmação de Pix), notificações do GitHub, mensagens de bots do Telegram, formulários, alertas de monitoramento. Entender como eles funcionam poupa requisições inúteis e evita alguns problemas de segurança.
Como um webhook funciona?#
O fluxo tem três passos:
- Você cadastra uma URL no serviço emissor (por exemplo
https://seuapp.com/webhooks/pagamentos) e escolhe quais eventos quer receber. - Quando um desses eventos acontece, o serviço envia uma requisição HTTP
POSTpara a sua URL. O corpo costuma ser um JSON com o tipo do evento e os dados. - O seu servidor responde com um código
2xxpara confirmar que recebeu.
Se você não responder com 2xx, o emissor considera que a entrega falhou. O Stripe, por exemplo, também trata redirecionamentos 3xx como falha.
Um evento típico se parece com isto:
{
"id": "evt_123",
"type": "payment.succeeded",
"data": { "order_id": "ped_42", "amount": 4990 }
}Os nomes dos campos mudam de um serviço para outro. O que se mantém é a ideia: um POST com um identificador do evento, um tipo e os dados.
Qual a diferença entre webhook e polling?#
No polling, o seu app pergunta de tempos em tempos se há algo novo. É simples, mas a maioria das respostas é "nada novo": você gasta requisições e descobre cada evento com um atraso de até um intervalo inteiro.
No webhook, o aviso chega quando o evento acontece, e só nessa hora. Há menos tráfego e menos latência. Em troca, o seu servidor precisa estar acessível pela internet e pronto para receber requisições de terceiros.
Na prática, os dois se combinam: o webhook avisa e, se você desconfiar que perdeu algum, uma tarefa periódica consulta a API REST para reconciliar.
Como receber um webhook em Node.js?#
Este exemplo usa Express 5 com ES modules ("type": "module" no package.json) e verifica a assinatura do evento. O esquema é o que o GitHub usa: um HMAC-SHA256 do corpo, calculado com um segredo compartilhado e enviado no cabeçalho X-Hub-Signature-256 com o prefixo sha256=. Outros serviços usam nomes de cabeçalho e formatos diferentes (o Stripe usa Stripe-Signature, que também inclui um timestamp), então confira sempre a documentação deles.
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.WEBHOOK_SECRET;
function assinaturaValida(corpo, cabecalho) {
// se o Content-Type não bater, o express.raw deixa req.body indefinido
if (!cabecalho || !Buffer.isBuffer(corpo)) return false;
const esperada = Buffer.from(
"sha256=" + createHmac("sha256", SECRET).update(corpo).digest("hex")
);
const recebida = Buffer.from(cabecalho);
// timingSafeEqual lança um erro se os tamanhos forem diferentes
return recebida.length === esperada.length && timingSafeEqual(recebida, esperada);
}
// express.raw guarda o corpo exato em bytes, necessário para a assinatura
app.post("/webhooks/pagamentos", express.raw({ type: "application/json" }), (req, res) => {
if (!assinaturaValida(req.body, req.get("X-Hub-Signature-256"))) {
return res.sendStatus(401);
}
const evento = JSON.parse(req.body.toString("utf8"));
console.log("Evento recebido:", evento.type);
res.sendStatus(200); // responde primeiro, processa depois
});
app.listen(process.env.PORT ?? 3000);Para testar sem depender de um serviço externo, calcule a assinatura você mesmo:
CORPO='{"id":"evt_123","type":"payment.succeeded"}'
ASSINATURA="sha256=$(printf '%s' "$CORPO" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $NF}')"
curl -i -X POST http://localhost:3000/webhooks/pagamentos \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: $ASSINATURA" \
-d "$CORPO"Se você mudar uma única letra do corpo sem recalcular a assinatura, vai receber 401.
Por que verificar a assinatura?#
A sua URL é pública. Sem verificação, qualquer pessoa que a conheça pode mandar um evento falso de "pagamento aprovado" e fazer o seu sistema liberar uma conta sem cobrar. A documentação do Stripe alerta exatamente para isso.
Três regras para verificar a assinatura:
- Use o corpo cru. Se um middleware converte o JSON em objeto e serializa de novo, os bytes mudam e a assinatura deixa de bater. O Stripe aponta isso como causa frequente de falha na verificação.
- Compare em tempo constante. O GitHub recomenda
crypto.timingSafeEqualem vez de==para evitar ataques de temporização. - Guarde o segredo numa variável de ambiente, nunca no código nem no repositório.
Alguns emissores incluem um timestamp dentro do conteúdo assinado. O Stripe faz isso, e as bibliotecas dele rejeitam por padrão eventos com mais de cinco minutos de diferença, o que barra ataques de repetição. Como camada extra, alguns emissores publicam uma lista de IPs de origem que você pode liberar no firewall.
Quais erros são comuns com webhooks?#
Demorar demais para responder. Se você faz o trabalho pesado antes de responder, o emissor pode dar a entrega como falha por timeout e reenviar. O Stripe recomenda devolver o 2xx antes de qualquer lógica complexa e processar numa fila assíncrona.
Processar o mesmo evento duas vezes. Os emissores podem entregar um evento mais de uma vez, principalmente quando tentam de novo. Guarde o identificador de cada evento processado e descarte os repetidos. Assim o seu handler fica idempotente.
Supor uma ordem. O Stripe diz explicitamente que não garante entregar os eventos na ordem em que foram gerados. Se você precisa do estado atual, consulte a API em vez de confiar na ordem de chegada.
Aceitar todos os tipos de evento. Assine só os que você usa. Menos eventos, menos carga e menos superfície para erro.
Testar só em produção. Use um túnel como o ngrok ou a CLI do emissor, e acompanhe o painel de entregas, onde costumam aparecer o código de status e o horário da próxima tentativa.
Quando não vale usar webhook?#
Um webhook precisa de um servidor acessível e sempre disponível. Se o seu cliente é uma página web ou um app de celular que precisa de dados ao vivo, uma conexão persistente como um WebSocket encaixa melhor. Se o emissor não oferece webhooks, ou se você precisa ler o estado completo de tempos em tempos, o polling continua válido.
Quando perder um evento sai caro, não dependa só do webhook: combine com uma reconciliação periódica pela API.
Cuidado também com o nome. Os webhooks do Discord são uma URL para publicar mensagens num canal: ali você envia, enquanto aqui você recebe.
Como fazer isso na Square Cloud#
No fundo, um webhook é uma rota HTTP da sua aplicação web com uma URL pública e HTTPS. Uma aplicação web na Square Cloud ganha um subdomínio *.squareweb.app com HTTPS em qualquer plano, então você pode cadastrar https://seu-app.squareweb.app/webhooks/pagamentos no emissor sem montar proxy nem certificado.
Passos para o exemplo acima:
- Faça o deploy da aplicação Node.js pelo painel, pela CLI, pelo GitHub ou pela extensão do VS Code. O arquivo de configuração
squarecloud.appé opcional: o painel o gera durante o deploy, ou você pode escrevê-lo na raiz do projeto. - Defina
WEBHOOK_SECRETnas variáveis de ambiente da aplicação. A mudança vale no próximo restart, sem rebuild. - Cadastre a URL pública no emissor e registre um log a cada entrega no seu código: os logs em tempo real do painel mostram o que a sua aplicação imprime.
As aplicações ficam sempre ligadas, sem modo de suspensão. Isso importa porque um emissor que não recebe resposta marca a entrega como falha. Bots do Telegram também podem receber atualizações por webhook em vez de long polling, usando um subdomínio HTTPS.
Veja a página de hospedagem de APIs para conhecer runtimes, planos e limites.
Hospedagem de APIs
APIs em containers sempre ligados, sem cold start
Perguntas frequentes
Qual a diferença entre webhook e API?
Numa API, você faz a requisição quando quer os dados. No webhook, é o outro serviço que chama você quando algo acontece. Os dois se completam: o webhook avisa e a API serve para buscar os detalhes.
Webhook precisa de HTTPS?
Em produção, sim. O Stripe, por exemplo, exige uma URL HTTPS pública para os endpoints de produção. Além disso, o HTTPS impede que o conteúdo do evento seja lido no caminho.
O que acontece se meu servidor estiver fora do ar quando o webhook chegar?
Depende do emissor. Muitos tentam de novo: o Stripe repete por até três dias com espera exponencial em produção. Outros não repetem, então vale ler a documentação deles e conseguir reconciliar pela API.
Como testar um webhook no meu computador?
Você precisa de uma URL pública que chegue à sua máquina. Dá para usar um túnel como o ngrok, ou a ferramenta do emissor, como o `stripe listen`, que encaminha os eventos para o localhost.
Webhook e WebSocket são a mesma coisa?
Não. Um webhook é uma requisição HTTP pontual do emissor para o seu servidor. Um WebSocket é uma conexão persistente e bidirecional que você abre entre cliente e servidor.
Mais em APIs
Ver todos os 15 artigos- Como hospedar uma APIHospedar uma API é rodar o servidor HTTP 24 horas num lugar com IP público e HTTPS. Veja o passo a passo com Node.js e Python, testes e erros comuns.
- Como hospedar uma aplicação Node.jsPara hospedar uma aplicação Node.js, prepare o package.json, leia porta e segredos do ambiente, escolha uma versão LTS e rode com node. Veja o passo a passo.
- Como hospedar FastAPI, Flask ou DjangoPara hospedar FastAPI, Flask ou Django, rode a aplicação com um servidor de produção (Uvicorn ou Gunicorn) em 0.0.0.0. Veja comandos, ajustes do Django e erros.
- Como hospedar um script ou API em PythonPara hospedar um script ou API em Python, fixe a versão, isole dependências em um venv, leia segredos do ambiente e trate o SIGTERM. Passo a passo com código.