Webhook na prática: tutorial com Python, segurança e idempotência

Entenda webhook sem promessas: crie um endpoint em Flask, valide assinatura HMAC, evite eventos duplicados, use fila e teste retentativas.

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

  1. use o evento de teste do provedor;
  2. confirme cabeçalhos e corpo bruto;
  3. mande assinatura inválida e espere rejeição;
  4. repita o mesmo ID e confirme uma única ação;
  5. simule timeout e nova entrega;
  6. envie eventos fora de ordem;
  7. 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.