Webhook é uma requisição HTTP enviada por um sistema quando um evento acontece. Em vez de consultar uma API repetidamente para descobrir se existe novidade, sua aplicação expõe um endpoint e recebe a notificação. Pagamentos, mensagens, formulários e CRMs usam esse padrão.
O webhook não garante processamento exatamente uma vez. O provedor pode repetir a entrega, enviar fora de ordem ou desistir após várias falhas. Segurança, idempotência e observabilidade fazem parte da implementação.
Fluxo básico
Sistema de origem → POST HTTPS → validação → persistência → fila → processamento
↘ resposta 2xx rápida
O endpoint recebe, valida e registra. A tarefa lenta — chamar IA, enviar mensagem ou atualizar CRM — deve continuar em uma fila. Isso reduz timeout e facilita retentativa.
Exemplo mínimo em Flask
from flask import Flask, abort, request
app = Flask(__name__)
@app.post("/webhooks/pedidos")
def pedidos():
if not request.is_json:
abort(415)
evento = request.get_json()
event_id = evento.get("id")
if not event_id:
abort(400)
# 1. validar assinatura antes de confiar no corpo
# 2. inserir event_id com restrição UNIQUE
# 3. publicar o trabalho numa fila
return {"received": True}, 202
Esse código ilustra o fluxo, mas ainda não valida origem nem persiste o evento. Não publique assim em produção.
Valide assinatura HMAC
Muitos provedores enviam uma assinatura calculada sobre o corpo bruto. Seu servidor recalcula com o segredo compartilhado e compara em tempo constante:
import hashlib
import hmac
def assinatura_valida(raw_body: bytes, recebida: str, segredo: str) -> bool:
calculada = hmac.new(
segredo.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(calculada, recebida)
Use exatamente o formato documentado pelo provedor: nome do cabeçalho, prefixo, algoritmo e composição podem ser diferentes. Valide também timestamp quando a especificação oferece proteção contra replay.
Evite processar o mesmo evento duas vezes
Crie uma chave única com o identificador do evento. Antes de executar a ação, tente registrar essa chave no banco. Se ela já existir, responda sucesso sem repetir cobrança, mensagem ou atualização.
Não derive a chave apenas do horário. Dois eventos legítimos podem ocorrer no mesmo instante. Quando o provedor não oferece ID, combine campos estáveis e documente a limitação.
Não presuma ordem de chegada
“Pedido criado” pode chegar depois de “pagamento aprovado”. Guarde versão, horário do evento e estado atual. A regra de atualização deve impedir que um evento antigo reverta um estado mais novo.
Responda rápido e processe depois
- valide tamanho, conteúdo e assinatura;
- persista o corpo necessário e o ID;
- publique em fila;
- responda 2xx;
- processe com retentativa e fila de erro.
Se você devolver erro após concluir a ação, o provedor pode repetir e causar duplicidade. Por isso a idempotência é necessária mesmo quando o código “normalmente funciona”.
Como testar
- use o evento de teste do provedor;
- confirme cabeçalhos e corpo bruto;
- mande assinatura inválida e espere rejeição;
- repita o mesmo ID e confirme uma única ação;
- simule timeout e nova entrega;
- envie eventos fora de ordem;
- verifique se logs não expõem segredo ou dado sensível.
Checklist de produção
- HTTPS com certificado válido;
- rota específica por provedor e ambiente;
- segredo em gerenciador seguro;
- assinatura e timestamp validados;
- limite de corpo e rate limit;
- ID único e idempotência;
- fila, retentativa e fila de mensagens mortas;
- métricas de recebidos, rejeitados, duplicados e falhos;
- alerta para ausência inesperada de eventos;
- procedimento para rotacionar segredo.
Fontes técnicas
Compare sempre com o provedor específico. Referências úteis: boas práticas de webhooks do GitHub, documentação de webhooks da Stripe e guia oficial do Flask. Este exemplo não substitui a especificação de assinatura do serviço integrado.