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:
- Algoritmo: HMAC SHA-256
- Chave: O token secreto configurado no webhook
- 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
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.