Restomenum'dan gelen her webhook ve hook isteği, tenant'a özel webhookSecret ile HMAC-SHA256 imzalanır. İmzayı doğrulamak, isteğin gerçekten Restomenum'dan geldiğini ve değiştirilmediğini garanti eder — her zaman doğrula.
X-Restomenum-Signature: t=<unixSec>,v1=<HMAC_SHA256(webhookSecret, "<t>.<rawBody>")>
t — imzalama anının unix saniyesi (replay koruması).v1 — HMAC_SHA256(webhookSecret, "<t>.<rawBody>") hex.İstek geldi │ ├─ 1) header'ı ayrıştır → t, v1 ├─ 2) signedPayload = "<t>." + ham gövde (byte) ├─ 3) expected = HMAC_SHA256(webhookSecret, signedPayload) ├─ 4) timing-safe karşılaştır(expected, v1) → eşit değilse 401 └─ 5) |now - t| > 300sn → reddet (replay) ▼ Güvenli: gövdeyi JSON.parse et ve işle
// İmza doğrulama — dile bağımsız algoritma: // 1) Header'ı ayrıştır: X-Restomenum-Signature: t=<unixSec>,v1=<hex> // 2) signedPayload = "<t>" + "." + <ham gövde (byte)> // 3) expected = HMAC_SHA256(webhookSecret, signedPayload) → hex // 4) timing-safe karşılaştır: expected === v1 (eşit değilse 401) // 5) replay: |now - t| > 300sn ise reddet // // webhookSecret tenant başınadır ( /connect exchange'inden gelir ) ve ASLA yayınlanmaz.
express.raw). webhookSecret tenant başınadır ve asla loglanmaz/yayınlanmaz.Node örneği için /webhook alıcısı. PHP ve Python:
<?php
// PHP — webhook imza doğrulama
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_RESTOMENUM_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $sig), $p); // t=..&v1=..
if (abs(time() - (int)$p['t']) > 300) { http_response_code(401); exit; }
$expected = hash_hmac('sha256', $p['t'] . '.' . $raw, getenv('RESTOMENUM_WEBHOOK_SECRET'));
if (!hash_equals($expected, $p['v1'] ?? '')) { http_response_code(401); exit; }
$event = json_decode($raw, true); // güvenli
http_response_code(200);# Python (Flask) — webhook imza doğrulama
import hmac, hashlib, time
from flask import request, abort
SECRET = os.environ["RESTOMENUM_WEBHOOK_SECRET"].encode()
@app.post("/webhook")
def webhook():
raw = request.get_data() # ham byte — parse'tan ÖNCE
sig = dict(p.split("=") for p in request.headers.get("X-Restomenum-Signature","").split(","))
if abs(time.time() - int(sig.get("t", 0))) > 300: abort(401)
expected = hmac.new(SECRET, f"{sig['t']}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig.get("v1","")): abort(401)
event = request.get_json() # güvenli
return "", 200