Proteja seus endpoints de webhook com verificação de assinatura e melhores práticas — com eventos webhook gu1 para integração downstream em tempo real.
Proteger seus endpoints de webhook é crítico para garantir que as solicitações de webhook venham do Gu1 e não de atores maliciosos. Este guia cobre como verificar assinaturas de webhook, implementar melhores práticas de segurança e evitar erros de segurança comuns.
Gu1 assina todas as solicitações de webhook com uma assinatura HMAC SHA-256 usando seu secret de webhook. A assinatura é enviada no header X-Webhook-Signature, permitindo que você verifique se a solicitação é autêntica.
Gu1 gera uma assinatura: Ao enviar um webhook, Gu1 cria um hash HMAC SHA-256 do corpo raw da solicitação usando seu secret de webhook
Assinatura é enviada no header: A assinatura é incluída no header X-Webhook-Signature
Seu servidor recalcula: Seu endpoint recalcula a assinatura usando o mesmo secret e corpo raw
Comparar assinaturas: Se as assinaturas corresponderem, o webhook é autêntico
Sempre verifique assinaturas de webhook em produção. Sem verificação, qualquer pessoa pode enviar webhooks falsos para seu endpoint e potencialmente comprometer seu sistema.
Crítico: Você deve verificar a assinatura usando o corpo raw da solicitação antes de ser analisado como JSON. Se você verificar contra o corpo JSON analisado (por exemplo, JSON.stringify(req.body)), a assinatura não corresponderá porque a formatação JSON pode diferir.
from flask import Flask, request, jsonifyimport hmacimport hashlibimport osimport loggingapp = Flask(__name__)@app.route('/webhooks/gu1', methods=['POST'])def gu1_webhook(): try: # Obter assinatura do header signature = request.headers.get('X-Webhook-Signature') webhook_secret = os.getenv('GU1_WEBHOOK_SECRET') # Obter corpo raw para verificação de assinatura raw_body = request.get_data(as_text=True) # Verificar assinatura if not verify_signature(raw_body, signature, webhook_secret): logging.error('Invalid webhook signature') return jsonify({'error': 'Invalid signature'}), 401 # Analisar payload payload = request.json event = payload.get('event') data = payload.get('payload') logging.info(f'Received webhook: {event}') # Processar o webhook handle_webhook(event, data) return jsonify({ 'success': True, 'message': 'Webhook received' }), 200 except Exception as e: logging.error(f'Webhook error: {e}') return jsonify({ 'success': False, 'error': str(e) }), 500def verify_signature(raw_body: str, signature: str, secret: str) -> bool: """Verificar assinatura HMAC SHA-256""" expected_signature = hmac.new( secret.encode('utf-8'), raw_body.encode('utf-8'), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_signature)def handle_webhook(event: str, data: dict): # Processar webhook baseado no tipo de evento passif __name__ == '__main__': app.run(port=3000)
Use hmac.compare_digest() em vez de == para comparar assinaturas em Python. Esta função realiza uma comparação segura contra timing que previne ataques de timing.
Um erro comum é verificar a assinatura usando o objeto JSON analisado em vez do corpo raw da solicitação. Isso sempre falhará porque a formatação JSON pode diferir.
// ERRADO: Verificando corpo analisadoapp.post('/webhooks', express.json(), (req, res) => { const signature = req.headers['x-webhook-signature']; // Isso NÃO funcionará - JSON.stringify pode formatar diferentemente const body = JSON.stringify(req.body); const expectedSignature = crypto .createHmac('sha256', secret) .update(body) .digest('hex'); if (signature === expectedSignature) { // Sempre falhará }});
O monitor de webhooks exibe o payload para debug e suporte. Essa visualização não é a string byte a byte que foi assinada no envio. Pretty-print, copiar da UI ou JSON.stringify() em um objeto parseado pode gerar uma string diferente e fazer uma entrega correta parecer inválida.Para depurar uma falha, compare o header X-Webhook-Signature recebido pelo seu endpoint com o HMAC calculado sobre o raw body daquele mesmo request HTTP. Não re-verifique apenas com o JSON do dashboard.
Se a verificação funciona em alguns eventos mas em outros retorna 401 Invalid signaturecom o mesmo secret e endpoint, as causas mais comuns são:
JSON parseado e re-serializado — JSON.stringify(req.body) após express.json() (ou equivalente) não reproduz de forma confiável os bytes do body da Gu1. Payloads diferentes (ordem de chaves, forma aninhada, formatação numérica) podem falhar só às vezes.
Middleware que altera o body — deduplicar arrays, remover campos null, ordenar chaves ou normalizar strings antes de verificar muda o input assinado.
Secret incorreto — secret regenerado no dashboard enquanto o valor antigo permanece no seu ambiente.
Header de assinatura ausente — se o webhook não tiver secret configurado, a Gu1 pode omitir X-Webhook-Signature; tratar header ausente como assinatura inválida é esperado.
A verificação de assinatura é opcional, mas recomendada. A Gu1 ainda entrega webhooks se você não verificar; o 401 é retornado pelo seu servidor quando sua lógica de verificação rejeita o request.
Webhooks podem ser entregues mais de uma vez devido a problemas de rede, timeouts ou tentativas. Implemente idempotência para garantir que você processe cada webhook apenas uma vez.
// NÃO FAÇA ISSO - Sem verificação de assinaturaapp.post('/webhooks', (req, res) => { // Processando webhook sem verificação handleWebhook(req.body); res.status(200).send('OK');});
Risco: Qualquer pessoa pode enviar webhooks falsos para seu endpoint.
// NÃO FAÇA ISSO - Processando duplicadosapp.post('/webhooks', (req, res) => { // Sem verificação de duplicados await createUser(req.body.entity); res.status(200).send('OK');});
// NÃO FAÇA ISSO - Processando eventos desconhecidosapp.post('/webhooks', (req, res) => { // Aceitando qualquer tipo de evento handleAnyEvent(req.body.event, req.body.payload);});
Risco: Atacantes podem enviar tipos de eventos arbitrários.