Webhook-Überprüfung
Wie Sie überprüfen, dass Webhooks authentisch sind und nicht manipuliert wurden.
Warum überprüfen
Jeder, der die URL Ihres Webhooks kennt, könnte versuchen, gefälschte Ereignisse zu senden. Um dies zu verhindern, enthält jeder Webhook eine kryptografische Signatur, die Sie überprüfen müssen.
Verarbeiten Sie niemals einen Webhook, ohne seine Signatur zu überprüfen, besonders wenn er wichtige Aktionen wie das Erstellen von Bestellungen oder das Ändern von Daten ausführt.
Wie es funktioniert
- Beim Erstellen eines Webhooks wird ein secret eindeutiges
- Wenn wir einen Webhook senden, berechnen wir einen HMAC-SHA256-Hash des Payloads mit Ihrem Secret
- Wir fügen diesen Hash im Header ein
X-SalonBookIt-Signature - Ihr Server berechnet den gleichen Hash und vergleicht ihn
Signaturformat
X-SalonBookIt-Signature: sha256=abc123def456...
Die Signatur besteht aus:
sha256=- Präfix, das den Algorithmus angibt- Gefolgt vom HMAC-SHA256-Hash in hexadezimal
Ihr Secret erhalten
- Gehen Sie zu Konfiguration → Integrationen
- Klicken Sie im Webhook-Bereich auf den Webhook, den Sie überprüfen möchten
- Kopieren Sie den Wert des Feldes Secret
Verwenden Sie Umgebungsvariablen oder einen Secret-Manager. Fügen Sie es niemals in den Quellcode ein.
Die Signatur überprüfen
Node.js / JavaScript
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
// Hash der Signatur extrahieren
const sigHash = signature.replace('sha256=', '');
// Erwarteten Hash berechnen
const expectedHash = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
// Sicher vergleichen (vermeidet Timing-Angriffe)
return crypto.timingSafeEqual(
Buffer.from(sigHash, 'hex'),
Buffer.from(expectedHash, 'hex')
);
}
// In deinem Endpunkt
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-salonbookit-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
console.error('Ungültige Signatur');
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log('Gültiges Ereignis:', event.type);
// Ereignis verarbeiten...
res.json({ received: true });
});
Python
import hmac
import hashlib
import os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Überprüft die HMAC-Signatur des Webhooks."""
if not signature.startswith('sha256='):
return False
sig_hash = signature[7:] # 'sha256=' entfernen
expected_hash = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
# Sicherer Vergleich gegen Timing-Angriffe
return hmac.compare_digest(sig_hash, expected_hash)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-SalonBookIt-Signature', '')
payload = request.get_data()
if not verify_signature(payload, signature, WEBHOOK_SECRET):
abort(401, 'Invalid signature')
event = request.get_json()
print(f"Gültiges Ereignis: {event['type']}")
# Ereignis verarbeiten...
return {'received': True}
PHP
<?php
$webhookSecret = getenv('WEBHOOK_SECRET');
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SALONBOOKIT_SIGNATURE'] ?? '';
function verifySignature(string $payload, string $signature, string $secret): bool
{
if (strpos($signature, 'sha256=') !== 0) {
return false;
}
$sigHash = substr($signature, 7);
$expectedHash = hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedHash, $sigHash);
}
if (!verifySignature($payload, $signature, $webhookSecret)) {
http_response_code(401);
die('Invalid signature');
}
$event = json_decode($payload, true);
error_log('Gültiges Ereignis: ' . $event['type']);
// Ereignis verarbeiten...
http_response_code(200);
echo json_encode(['received' => true]);
Ruby
require 'openssl'
require 'sinatra'
WEBHOOK_SECRET = ENV['WEBHOOK_SECRET']
def verify_signature(payload, signature, secret)
return false unless signature.start_with?('sha256=')
sig_hash = signature[7..]
expected_hash = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
Rack::Utils.secure_compare(expected_hash, sig_hash)
end
post '/webhook' do
payload = request.body.read
signature = request.env['HTTP_X_SALONBOOKIT_SIGNATURE'] || ''
unless verify_signature(payload, signature, WEBHOOK_SECRET)
halt 401, 'Invalid signature'
end
event = JSON.parse(payload)
puts "Gültiges Ereignis: #{event['type']}"
# Ereignis verarbeiten...
content_type :json
{ received: true }.to_json
end
Timestamp überprüfen (optional)
Für mehr Sicherheit können Sie überprüfen, dass der Webhook nicht zu alt ist, indem Sie den Header verwenden X-SalonBookIt-Timestamp:
const timestamp = parseInt(req.headers['x-salonbookit-timestamp']);
const now = Math.floor(Date.now() / 1000);
const tolerance = 300; // 5 Minuten
if (Math.abs(now - timestamp) > tolerance) {
console.error('Webhook zu alt');
return res.status(401).send('Timestamp expired');
}
Dies verhindert Replay-Angriffe, bei denen jemand versucht, einen zuvor erfassten Webhook erneut zu senden.
Häufige Fehler
Signatur stimmt nicht überein
- Überprüfe, dass du das richtige Secret verwendest
- Stelle sicher, dass du den Payload verwendest raw (nicht geparst)
- Ändere den Payload nicht vor der Überprüfung
Der Payload wurde geändert
Wenn dein Framework das JSON automatisch parst, hole den rohen Body vorher. In Express:
app.use(express.raw({ type: 'application/json' }))
Encoding-Probleme
Stelle sicher, dass der Payload bei der Hash-Berechnung als UTF-8 behandelt wird.