Bezpieczeństwo webhooków
Każde żądanie webhooka od ConsentForge zawiera podpis HMAC-SHA256. Weryfikuj ten podpis przed przetworzeniem ładunku, aby upewnić się, że żądanie jest autentyczne i nie zostało zmanipulowane.
Nagłówki żądania
| Nagłówek | Opis |
|---|---|
X-ConsentForge-Signature | Skrót szesnastkowy HMAC-SHA256 z {timestamp}.{body} |
X-ConsentForge-Timestamp | Znacznik czasu Unix (sekundy) wysłania zdarzenia |
X-ConsentForge-Delivery-ID | Unikalny identyfikator dostarczenia (używaj do idempotencji) |
Algorytm weryfikacji
- Odczytaj
X-ConsentForge-Timestampz nagłówka żądania - Odczytaj
X-ConsentForge-Signaturez nagłówka żądania - Zbuduj ciąg podpisywania:
{timestamp}.{raw_request_body} - Oblicz HMAC-SHA256 ciągu podpisywania używając swojego sekretu webhooka
- Porównaj (bezpieczne czasowo) z otrzymanym podpisem
- Odrzuć, jeśli znacznik czasu jest starszy niż 5 minut (ochrona przed powtórzeniem)
Przykłady kodu
- PHP
- Node.js
- Python
- Ruby
function verifyConsentForgeWebhook(
string $rawBody,
string $signature,
string $timestamp,
string $secret
): bool {
// Odrzuć jeśli zbyt stary (5 minut)
if (abs(time() - (int)$timestamp) > 300) {
return false;
}
$signingString = $timestamp . '.' . $rawBody;
$expected = hash_hmac('sha256', $signingString, $secret);
return hash_equals($expected, $signature);
}
// Użycie w kontrolerze Laravel:
$rawBody = $request->getContent();
$signature = $request->header('X-ConsentForge-Signature');
$timestamp = $request->header('X-ConsentForge-Timestamp');
$secret = config('services.consentforge.webhook_secret');
if (!verifyConsentForgeWebhook($rawBody, $signature, $timestamp, $secret)) {
return response('Unauthorized', 401);
}
$payload = $request->json()->all();
const crypto = require('crypto');
function verifyConsentForgeWebhook(rawBody, signature, timestamp, secret) {
// Odrzuć jeśli zbyt stary (5 minut)
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return false;
}
const signingString = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signingString)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
}
// Użycie w Express:
app.post('/webhooks/consentforge', express.raw({ type: '*/*' }), (req, res) => {
const valid = verifyConsentForgeWebhook(
req.body.toString(),
req.headers['x-consentforge-signature'],
req.headers['x-consentforge-timestamp'],
process.env.CONSENTFORGE_WEBHOOK_SECRET
);
if (!valid) return res.status(401).send('Unauthorized');
const payload = JSON.parse(req.body);
// obsłuż ładunek...
res.json({ received: true });
});
import hmac
import hashlib
import time
def verify_consentforge_webhook(raw_body, signature, timestamp, secret):
# Odrzuć jeśli zbyt stary (5 minut)
if abs(time.time() - int(timestamp)) > 300:
return False
signing_string = f"{timestamp}.{raw_body}"
expected = hmac.new(
secret.encode('utf-8'),
signing_string.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# Użycie we Flask:
from flask import request, abort
import os
@app.route('/webhooks/consentforge', methods=['POST'])
def handle_webhook():
raw_body = request.get_data(as_text=True)
valid = verify_consentforge_webhook(
raw_body,
request.headers.get('X-ConsentForge-Signature'),
request.headers.get('X-ConsentForge-Timestamp'),
os.environ['CONSENTFORGE_WEBHOOK_SECRET']
)
if not valid:
abort(401)
payload = request.get_json()
# obsłuż ładunek...
return {'received': True}
require 'openssl'
require 'rack/utils'
def verify_consentforge_webhook(raw_body, signature, timestamp, secret)
# Odrzuć jeśli zbyt stary (5 minut)
return false if (Time.now.to_i - timestamp.to_i).abs > 300
signing_string = "#{timestamp}.#{raw_body}"
expected = OpenSSL::HMAC.hexdigest('SHA256', secret, signing_string)
Rack::Utils.secure_compare(expected, signature)
end
# Użycie w Rails:
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def consentforge
raw_body = request.body.read
valid = verify_consentforge_webhook(
raw_body,
request.headers['X-ConsentForge-Signature'],
request.headers['X-ConsentForge-Timestamp'],
ENV['CONSENTFORGE_WEBHOOK_SECRET']
)
return head :unauthorized unless valid
payload = JSON.parse(raw_body)
# obsłuż ładunek...
head :ok
end
end
Idempotencja
Użyj X-ConsentForge-Delivery-ID do deduplikacji ponowionych dostarczeń. Przechowuj przetworzone identyfikatory dostarczeń i pomijaj duplikaty.
Rotacja sekretu
Aby zmienić sekret webhooka:
- Wygeneruj nowy sekret w Pulpicie (stary sekret nadal aktywny przez 24h)
- Zaktualizuj swój serwer, aby używał nowego sekretu
- Po 24h stary sekret traci ważność