الأمان وتوقيع HMAC
يُوقّع كل طلب webhook يرسله Savebase باستخدام HMAC-SHA256، لتتمكن من التأكد من أنه صدر فعلًا عن Savebase ولم يُعبث به أثناء النقل. يجب عليك دائمًا التحقق من هذا التوقيع قبل معالجة حدث webhook.
كيف يعمل التوقيع
يحسب Savebase التوقيع بتطبيق HMAC-SHA256 على جسم الطلب الخام باستخدام قيمة الويب هوك التالية: سر التوقيع كمفتاح. تُرسل النتيجة في X-Savebase-Signature الترويسة بصيغة sha256=<hex-digest>.
استخدم الجسم الخام
احسب HMAC دائمًا على البايتات الأصلية غير المعدلة لمتن الطلب — وليس كائن JSON أعيد تسلسله. قد يؤدي تحليل JSON وإعادة تحويله إلى نص إلى تغيير المسافات وترتيب المفاتيح، فيفشل التحقق من التوقيع.
| ترويسة | صيغة القيمة |
|---|---|
X-Savebase-Signature | sha256=abcdef0123... |
X-Savebase-Event | post.saved |
User-Agent | Savebase-Webhook/1.0 |
Node.js / Express
bash
const crypto = require('crypto');
function verifySignature(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'utf8'),
Buffer.from(signatureHeader, 'utf8')
);
}
app.post(
'/savebase-webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['x-savebase-signature'];
if (!sig || !verifySignature(req.body, sig, process.env.SAVEBASE_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Event:', event.event, event.data.id);
res.sendStatus(200);
}
);Python / Flask
bash
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['SAVEBASE_WEBHOOK_SECRET']
@app.route('/savebase-webhook', methods=['POST'])
def handle_webhook():
sig_header = request.headers.get('X-Savebase-Signature', '')
raw_body = request.get_data()
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, sig_header):
abort(401)
payload = request.get_json()
print('Received event:', payload['event'])
return '', 200PHP
bash
<?php
$secret = getenv('SAVEBASE_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_X_SAVEBASE_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $sigHeader)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true);
error_log('Received: ' . $event['event']);
http_response_code(200);أفضل ممارسات الأمان
- استجب بسرعة. إرجاع
200 OKخلال 10 ثوانٍ. ينهي Savebase المهلة بعد 10 ثوانٍ ويسجل فشل التسليم. انقل العمل الثقيل إلى مهمة في الخلفية. - لا تسجّل مفتاح التوقيع السري أبدًا. عامله ككلمة مرور. غيّره في
Settings → Webhooksإذا اعتقدت أنه انكشف لغير المصرح لهم. - استخدم HTTPS فقط. يرفض Savebase وجهات HTTP غير المشفرة.
- استخدم مقارنة آمنة زمنيًا. استخدم دائمًا
crypto.timingSafeEqual(Node)،hmac.compare_digest(Python)، أوhash_equals(PHP) — ولا تستخدم أبدًا مقارنة مباشرة مثل===. - عالج حالات الفشل بنفسك. يسجل Savebase نتيجة التسليم، لكن التنفيذ الحالي لا يعيد المحاولة أو تشغيل الطلبات الفاشلة تلقائيًا.