Pular para o conteúdo
16 min de leitura

O que é um webhook: como sistemas avisam uns aos outros sem ficar perguntando

Por Lucas Andrade ·

Webhook é uma chamada HTTP que um sistema dispara quando algo acontece, invertendo o polling; como receber, validar e processar com segurança.

Existe uma pergunta que todo sistema que depende de outro precisa responder: como eu sei que algo aconteceu do outro lado? A resposta ingênua é perguntar de novo, e de novo, e de novo, até a resposta mudar. A resposta que a maioria dos serviços sérios adotou — Stripe, GitHub, Twilio, WhatsApp Business, praticamente qualquer plataforma que expõe eventos — é a oposta: em vez de você perguntar, o outro lado avisa. Esse mecanismo de aviso tem nome, webhook, e apesar de ser conceitualmente simples — é só uma requisição HTTP —, ele carrega um conjunto de armadilhas que só aparecem em produção, sob carga, com um atacante curioso testando a URL.

Este artigo explica o que é um webhook, por que ele existe, como está estruturado por dentro, e — a parte que a maioria dos tutoriais pula — os cinco problemas que todo endpoint de webhook mal escrito acaba tendo: segurança, duplicidade, lentidão, ordem e ambiente de teste. Entender essas cinco coisas separa um endpoint de webhook que funciona na demonstração de um que aguenta produção.

O problema do polling: ficar perguntando "já aconteceu?"

A forma mais óbvia de saber se algo mudou é perguntar em intervalos. Isso se chama polling: a cada alguns segundos ou minutos, seu sistema faz uma requisição a uma API perguntando "o status do pedido 1042 mudou?", recebe a resposta, descarta se não mudou nada, e pergunta de novo daqui a pouco. É a estratégia mais simples de implementar e por isso é onde quase todo mundo começa.

O custo do polling cresce em duas direções ao mesmo tempo. Perguntar com pouca frequência significa descobrir o evento tarde — se você checa a cada cinco minutos, a notícia de que um pagamento foi aprovado pode levar quase cinco minutos para chegar, mesmo que o evento real tenha acontecido no primeiro segundo. Perguntar com muita frequência resolve a latência, mas multiplica o número de requisições: mil clientes checando o status de mil pedidos a cada segundo é um milhão de chamadas por segundo que, na esmagadora maioria das vezes, respondem "nada mudou". É desperdício de rede, de CPU do lado que responde, e de dinheiro em qualquer API cobrada por chamada.

Polling também é um jogo de adivinhação de frequência. Não existe intervalo certo, porque a frequência ideal depende de quando o evento acontece de verdade, e isso é justamente o que você não sabe de antemão. Ajustar o intervalo é sempre um compromisso entre gastar recursos à toa e demorar para reagir — um trade-off que some completamente quando o problema é resolvido de outro jeito.

A inversão: não me ligue, eu ligo para você

O webhook resolve o problema invertendo quem inicia a conversa. Em vez de você perguntar repetidamente se algo aconteceu, você registra uma vez uma URL sua junto ao provedor, e é o provedor quem faz a chamada — exatamente no momento em que o evento ocorre, nem antes nem depois. Isso costuma ser descrito com uma frase do mundo do cinema: don't call us, we'll call you — não nos ligue, nós ligamos para você. Em engenharia de software esse padrão é conhecido como Hollywood principle, e é a mesma ideia por trás de callbacks e de arquiteturas orientadas a eventos: o controle de "quando algo acontece" migra de quem consome para quem produz.

Tecnicamente, um webhook é só uma requisição HTTP comum, só que na direção contrária ao que a maioria dos desenvolvedores está acostumado. Você está habituado a escrever código que faz chamadas HTTP para uma API — seu servidor é o cliente, a API é o servidor. No webhook, seu servidor vira o lado que recebe a chamada: ele expõe um endpoint HTTP público, e é o provedor de eventos quem age como cliente, disparando um POST para esse endereço sempre que algo relevante acontece.

A consequência direta é latência quase nula e zero desperdício. Não existe intervalo de checagem porque não existe checagem — o aviso chega no instante em que o evento ocorre, e nenhuma requisição é feita quando nada muda, porque nesse caso simplesmente nenhuma requisição é enviada. É a troca de um problema de frequência por um problema de disponibilidade: agora seu endpoint precisa estar de pé para receber a chamada quando ela vier, o que introduz uma nova classe de cuidados, que é o assunto da segunda metade deste artigo.

Anatomia de um webhook

Toda integração via webhook tem três peças fixas. Primeiro, uma URL de destino, que você cadastra no painel do provedor ou via API — algo como https://seusite.com/webhooks/pagamento. Segundo, o evento em si, entregue como um POST HTTP cujo corpo é um payload, quase sempre JSON, descrevendo o que aconteceu. Terceiro, um ou mais headers de assinatura, que permitem provar que aquele POST realmente veio do provedor esperado e não foi forjado por qualquer pessoa que descobriu a URL.

Um payload típico carrega o tipo do evento, um identificador único e os dados relevantes. Um evento de pagamento aprovado de uma plataforma de cobrança, por exemplo, chega mais ou menos assim:

``json { "id": "evt_1PabcXYZ", "type": "payment.succeeded", "created": "2026-08-14T13:05:22Z", "data": { "payment_id": "pay_9f2c", "amount": 4990, "currency": "brl", "customer_id": "cus_88a1" } } ``

O id do evento é o detalhe mais importante do payload, e é o que menos chama atenção à primeira vista. Ele existe para permitir que quem recebe o evento distinga "este é um evento novo" de "este eu já processei antes" — uma necessidade que fica clara na seção sobre entrega duplicada, mais adiante.

O header de assinatura acompanha o POST por fora do corpo, normalmente com um nome como X-Signature ou Stripe-Signature, e carrega um hash calculado sobre o payload usando um segredo que só você e o provedor conhecem. É esse header que transforma "qualquer POST que chega nessa URL" em "um POST que eu confio que veio de quem eu esperava" — sem ele, o endpoint aceita qualquer coisa que qualquer um mandar.

Exemplos do mundo real

Pagamento aprovado é o exemplo mais citado, e por um bom motivo. Quando um cliente paga uma cobrança, a plataforma de pagamento — Stripe, PagSeguro, Mercado Pago — dispara um webhook payment.succeeded para o backend do lojista, que então libera o pedido, envia o e-mail de confirmação e atualiza o estoque. Sem o webhook, o lojista precisaria perguntar repetidamente "esse pagamento já foi aprovado?" para cada cobrança pendente, o que é exatamente o cenário de polling descrito acima.

Push para um repositório é outro caso canônico. Quando alguém dá git push num repositório no GitHub, o GitHub pode disparar um webhook push para uma URL configurada — é assim que pipelines de CI/CD sabem que devem começar a rodar, sem que nenhum servidor de CI precise ficar consultando "tem código novo?" a cada poucos segundos.

Mensagem recebida em uma plataforma de mensageria segue o mesmo padrão. Um número de WhatsApp Business ou uma integração de chat dispara um webhook para o backend da empresa sempre que um cliente manda uma mensagem, permitindo que um chatbot ou um atendente humano responda quase em tempo real, sem manter uma conexão de polling aberta o tempo todo.

Webhook vs API tradicional: quem liga para quem

A diferença entre webhook e uma API tradicional não está no protocolo — os dois usam HTTP — mas em quem inicia a chamada. Numa API tradicional, você é sempre o cliente: seu código decide quando perguntar, monta a requisição e espera a resposta. No webhook, os papéis se invertem para aquele fluxo específico: o provedor de eventos é quem assume o papel de cliente HTTP, e o seu sistema passa a ser o servidor que responde.

Essa inversão explica por que webhook precisa de infraestrutura pública e API tradicional não precisa. Para consumir uma API você só precisa conseguir sair — fazer requisições para fora, o que qualquer máquina atrás de NAT ou firewall corporativo consegue fazer sem configuração extra. Para receber um webhook, você precisa conseguir ser alcançado — um endpoint com endereço público, HTTPS válido, e sem bloqueio de firewall na direção de entrada. É por isso que testar webhook em localhost exige um passo extra, tratado mais adiante.

Na prática, os dois modelos convivem na mesma integração. Você usa a API tradicional para consultar dados sob demanda ou para acionar ações — criar uma cobrança, disparar uma mensagem — e usa webhook para ser avisado quando o resultado dessa ação, ou de uma ação de terceiros, muda de estado. Um não substitui o outro; eles resolvem direções opostas do mesmo problema de comunicação entre sistemas.

Os desafios que todo mundo subestima

Até aqui, um webhook parece trivial: registre uma URL, receba um POST, leia o JSON. É exatamente essa aparência de simplicidade que faz tanta gente subestimar os problemas reais, que só aparecem quando o endpoint está exposto na internet e recebendo tráfego de verdade.

Segurança: sua URL é pública, então verifique a assinatura

O endpoint de webhook é, por definição, uma URL pública que aceita POST de fora. Isso significa que qualquer pessoa que descubra ou adivinhe essa URL pode mandar um payload forjado — um falso "pagamento aprovado" para liberar um pedido sem pagar, por exemplo. Confiar cegamente no conteúdo de um payload só porque ele "parece" vir do provedor certo é o erro de segurança mais comum em integrações de webhook.

A defesa é verificar a assinatura HMAC que vem no header, sempre, antes de processar qualquer coisa. O provedor calcula um hash do corpo da requisição usando um segredo compartilhado, previamente combinado com você, e manda esse hash no header. Do seu lado, você recalcula o mesmo hash sobre os mesmos bytes recebidos, usando o mesmo segredo, e compara com o que veio no header. Se os dois hashes não baterem, o payload foi alterado ou não veio de quem diz ter vindo, e a requisição deve ser rejeitada sem processar nada. Um exemplo de verificação em Node.js:

```javascript const crypto = require("crypto");

function verificarAssinatura(payloadBruto, assinaturaRecebida, segredo) { const hashCalculado = crypto .createHmac("sha256", segredo) .update(payloadBruto) // bytes crus, antes do JSON.parse .digest("hex");

// comparação em tempo constante: evita vazar o hash por timing return crypto.timingSafeEqual( Buffer.from(hashCalculado), Buffer.from(assinaturaRecebida) ); } ```

Dois detalhes desse trecho não são acidente. A verificação usa o corpo bruto da requisição, antes de qualquer JSON.parse, porque a assinatura foi calculada sobre os bytes exatos que o provedor enviou — reformatar o JSON, mesmo sem mudar o conteúdo lógico, muda os bytes e quebra a comparação. E a comparação usa timingSafeEqual em vez de um simples ===, porque comparar strings caractere a caractere com early-exit vaza, por tempo de resposta, quantos caracteres iniciais bateram — uma diferença medível que um atacante paciente pode explorar para descobrir a assinatura correta aos poucos.

Entrega não confiável: o mesmo evento pode chegar mais de uma vez

Provedores de webhook não têm garantia de que sua chamada chegou e foi processada com sucesso. Se o seu endpoint não responder com um status de sucesso (2xx) dentro de um tempo curto — timeout de rede, seu servidor caiu, seu servidor demorou demais —, o provedor assume que a entrega falhou e tenta de novo, geralmente várias vezes, com espaçamento crescente entre tentativas.

Isso cria uma possibilidade que muita gente não antecipa: o mesmo evento chegar mais de uma vez no seu endpoint. Pode acontecer de o seu processamento ter, na verdade, funcionado perfeitamente da primeira vez, mas a resposta 200 ter se perdido na rede antes de voltar ao provedor — do ponto de vista dele, a tentativa falhou, então ele reenvia um evento que você já processou. Se o seu código trata cada webhook recebido como um evento novo e o executa de novo sem checar nada, um pagamento aprovado pode liberar dois pedidos, ou enviar dois e-mails de confirmação, ou debitar duas vezes um saldo interno.

A solução é fazer do processamento uma operação idempotente, usando o id do evento para deduplicar. Antes de executar qualquer efeito colateral, o endpoint verifica se aquele id de evento já foi processado — uma tabela simples com o id como chave única resolve isso — e, se já foi, responde 200 sem repetir o trabalho. É o mesmo princípio de idempotência que se aplica a qualquer operação que pode ser tentada mais de uma vez pela mesma causa: a segunda execução com a mesma entrada não deve produzir um efeito adicional.

Responder rápido: valide, enfileire, processe depois

O provedor espera uma resposta rápida, e "rápido" costuma significar poucos segundos. Se o seu endpoint segura a conexão aberta fazendo trabalho pesado — chamando outros serviços, gerando um PDF, mandando um e-mail de forma síncrona — antes de responder, corre o risco real de estourar o timeout do provedor. E um timeout, como visto acima, é tratado como falha de entrega, o que dispara um reenvio de um evento que, na verdade, ainda está sendo processado — abrindo caminho para duplicidade e para uma fila de retries crescendo sobre um endpoint que já está sobrecarregado.

A prática correta é separar recebimento de processamento. O handler do webhook faz o mínimo indispensável de forma síncrona — verificar a assinatura, validar o formato básico do payload, checar duplicidade pelo id do evento — e, assim que confirma que o evento é legítimo e novo, coloca o trabalho pesado numa fila (Redis, SQS, RabbitMQ, o que já existir no seu stack) e responde 200 imediatamente. Um worker separado consome essa fila no seu próprio ritmo, sem pressão de timeout externo. O endpoint de webhook vira, na prática, uma porta de entrada fina, não o lugar onde o trabalho de verdade acontece.

Ordem não garantida

Eventos de webhook podem chegar fora da ordem em que aconteceram. Retries, múltiplos workers processando em paralelo do lado do provedor, e variação de latência de rede significam que um evento "pedido cancelado" pode, em casos raros, chegar ao seu endpoint antes de um "pedido criado" referente ao mesmo pedido, mesmo que a criação tenha ocorrido primeiro na realidade.

Depender da ordem de chegada para inferir a ordem real dos fatos é um erro sutil e comum. A correção é usar um timestamp ou um número de sequência que vem dentro do próprio payload — nunca a ordem de chegada na rede — para decidir qual é o estado mais recente. Um processamento que sempre sobrescreve o estado com "o que chegou por último" sem checar esse timestamp corre o risco de deixar um pedido marcado como criado depois de já ter sido cancelado.

Testar em ambiente de desenvolvimento local

Um webhook exige uma URL pública, e localhost não é público — esse é o obstáculo prático número um de quem está integrando pela primeira vez. O provedor de eventos vive na internet e não tem como alcançar http://localhost:3000 rodando na sua máquina, então o fluxo natural de "recebi o evento, vejo no meu debugger" simplesmente não funciona sem um passo a mais.

A solução mais comum é um túnel que expõe a porta local com uma URL pública temporária, como ngrok ou Cloudflare Tunnel: a ferramenta cria um endereço público, do tipo https://abc123.ngrok.io, que encaminha todo tráfego recebido de volta para a porta local do seu servidor de desenvolvimento. Você cadastra esse endereço temporário no provedor enquanto desenvolve, e troca pela URL de produção real antes de publicar.

Muitos provedores também oferecem ferramentas de teste e replay direto no próprio painel, que permitem disparar manualmente um evento de exemplo para uma URL registrada, ou reenviar um evento real que já ocorreu, sem precisar esperar que a condição de negócio aconteça de novo. Vale a pena conhecer essa ferramenta específica de cada provedor antes de depender só de túnel — ela costuma ser mais rápida para testar casos específicos, como um payload malformado ou um tipo de evento raro.

Boas práticas de quem recebe

  • Endpoint dedicado por tipo de integração — não misture o webhook de pagamento com o de outro provedor no mesmo handler; cada integração tem seu próprio segredo e formato.
  • Responda rápido, sempre 2xx para eventos aceitos — valide, enfileire, responda; trabalho pesado vai para um worker assíncrono.
  • Verifique a assinatura em toda requisição, usando o corpo bruto e comparação em tempo constante, antes de tocar no conteúdo do payload.
  • Deduplique pelo id do evento — trate cada processamento como idempotente, porque reenvio é esperado, não exceção.
  • Registre logs com o id do evento em cada etapa — recebido, validado, enfileirado, processado — para reconstruir o que aconteceu quando algo falhar na investigação.

Boas práticas de quem envia

  • Assine todo payload com um segredo por integração, e documente claramente o algoritmo e o formato do header para quem consome.
  • Retries com backoff crescente, não imediato e infinito — um espaçamento que aumenta a cada tentativa evita bombardear um endpoint que já está com problema.
  • Timeout curto na chamada de saída — não vale a pena segurar a conexão esperando um consumidor lento; é melhor falhar rápido e deixar o mecanismo de retry cuidar disso.
  • Um painel de reenvio manual para o cliente da integração, permitindo disparar de novo um evento específico sem depender de suporte técnico.

Fechando

Um webhook é, por baixo de tudo, apenas um POST HTTP — mas é um POST que inverte quem inicia a conversa: em vez de você perguntar repetidamente se algo mudou, o provedor avisa no instante em que muda, eliminando o desperdício e a latência do polling. Essa simplicidade de superfície esconde cinco problemas que só aparecem em produção: a URL pública exige verificação de assinatura antes de confiar em qualquer payload; a entrega não é garantida, então o mesmo evento pode chegar duas vezes e o processamento precisa ser idempotente; o provedor espera resposta rápida, então trabalho pesado vai para uma fila, não para dentro do handler; a ordem de chegada não reflete a ordem real dos fatos; e testar em desenvolvimento exige expor a máquina local através de um túnel. Nenhum desses cinco pontos é opcional — cada um, ignorado, vira um incidente específico e reconhecível: pedido duplicado, dado desatualizado sobrescrevendo um mais novo, ou uma URL forjada liberando algo que nunca foi pago. Tratar o webhook com o mesmo rigor de qualquer outra fronteira pública do seu sistema é o que separa uma integração que funciona na demonstração de uma que aguenta produção.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly