Webhook İmza Şeması

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.

İmza header'ı

X-Restomenum-Signature: t=<unixSec>,v1=<HMAC_SHA256(webhookSecret, "<t>.<rawBody>")>
  • t — imzalama anının unix saniyesi (replay koruması).
  • v1HMAC_SHA256(webhookSecret, "<t>.<rawBody>") hex.

Doğrulama akışı

İ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.
Ham gövde (raw body) şart: imza, gövdenin tam byte hâli üzerinden hesaplanır. Önce JSON parse edip sonra yeniden stringify edersen imza tutmaz. Framework'ünde bu route için raw-body kullan (Express: express.raw). webhookSecret tenant başınadır ve asla loglanmaz/yayınlanmaz.

Diğer diller

Node örneği için /webhook alıcısı. PHP ve Python:

PHP
<?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)
# 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