Skip to main content

Visão Geral

Todos os webhooks enviados pela plataforma incluem uma assinatura HMAC SHA-256 no header X-Webhook-Signature. Esta assinatura permite que você valide que o webhook é legítimo e foi enviado pela plataforma.

Por que Validar?

A validação da assinatura é essencial para:
  • Segurança: Garantir que o webhook foi enviado pela plataforma e não por um atacante
  • Integridade: Verificar que o payload não foi alterado durante a transmissão
  • Autenticação: Confirmar a identidade do remetente

Como Funciona

A assinatura é gerada usando:
  1. Algoritmo: HMAC SHA-256
  2. Chave: O token secreto configurado no webhook
  3. Mensagem: O payload JSON completo (stringificado)

Implementação

Node.js

const crypto = require('crypto');
const express = require('express');

const app = express();
app.use(express.json());

const WEBHOOK_TOKEN = 'seu-token-aqui'; // Token configurado no webhook

function validateWebhookSignature(payload, signature, token) {
  const expectedSignature = crypto
    .createHmac('sha256', token)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return signature === expectedSignature;
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  if (!signature) {
    return res.status(401).json({ error: 'Missing signature' });
  }
  
  // Validar assinatura
  const isValid = validateWebhookSignature(req.body, signature, WEBHOOK_TOKEN);
  
  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Processar webhook
  // ...
  
  res.status(200).json({ received: true });
});

Python

import hmac
import hashlib
import json
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_TOKEN = 'seu-token-aqui'  # Token configurado no webhook

def validate_webhook_signature(payload, signature, token):
    expected_signature = hmac.new(
        token.encode('utf-8'),
        json.dumps(payload, separators=(',', ':')).encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    
    if not signature:
        return jsonify({'error': 'Missing signature'}), 401
    
    # Validar assinatura
    if not validate_webhook_signature(request.json, signature, WEBHOOK_TOKEN):
        return jsonify({'error': 'Invalid signature'}), 401
    
    # Processar webhook
    # ...
    
    return jsonify({'received': True}), 200

PHP

<?php

function validateWebhookSignature($payload, $signature, $token) {
    $expectedSignature = hash_hmac(
        'sha256',
        json_encode($payload, JSON_UNESCAPED_SLASHES),
        $token
    );
    
    return hash_equals($signature, $expectedSignature);
}

$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? null;

if (!$signature) {
    http_response_code(401);
    echo json_encode(['error' => 'Missing signature']);
    exit;
}

$payload = json_decode(file_get_contents('php://input'), true);
$token = 'seu-token-aqui'; // Token configurado no webhook

if (!validateWebhookSignature($payload, $signature, $token)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Processar webhook
// ...

http_response_code(200);
echo json_encode(['received' => true]);

Pontos Importantes

1. Stringificação do Payload

O payload deve ser stringificado exatamente como recebido, preservando:
  • Ordem dos campos
  • Espaçamento (sem espaços extras)
  • Formato de números (sem alterações)
  • Encoding UTF-8

2. Comparação Segura

Use funções de comparação segura para evitar timing attacks:
  • Node.js: === (comparação direta é segura para strings hex)
  • Python: hmac.compare_digest()
  • PHP: hash_equals()

3. Encoding

Certifique-se de usar UTF-8 para:
  • Token (chave secreta)
  • Payload JSON

4. Headers Case-Insensitive

O header X-Webhook-Signature pode vir em diferentes casos. Normalize para minúsculas ao ler:
const signature = req.headers['x-webhook-signature'] || 
                  req.headers['X-Webhook-Signature'];

Tratamento de Erros

Sempre retorne um status HTTP apropriado:
  • 401 Unauthorized: Assinatura ausente ou inválida
  • 400 Bad Request: Payload malformado
  • 200 OK: Assinatura válida e webhook processado
Nunca processe um webhook sem validar a assinatura. Isso pode expor seu sistema a ataques.
O token usado para validar deve ser o mesmo configurado no webhook. Mantenha-o seguro e não o exponha em logs ou código cliente.