3 · /webhook Alıcısı

Abone olduğun event'ler, imzalı POST olarak webhook endpoint'ine teslim edilir (asenkron, fire-and-forget). İmzayı doğrula, 2xx dön ve işle. 2xx dönmezsen Restomenum yeniden dener (retry).

Akış

Bir event oluşur (örn. packet.created)
   │
   ▼
Restomenum  ──imzalı POST──►  https://<senin-origin>/webhook
   │   header: X-Restomenum-Signature: t=..,v1=..
   │   body:   { id, type, version, tenantId, occurredAt, data }
   ▼
Senin /webhook:
   1) imzayı doğrula (raw body)        → 401 değilse
   2) 200 dön (hızlıca)
   3) işle (idempotent — id ile tekrarı yut)

Referans — /webhook (Node)

// /webhook — Restomenum event alıcısı (Node + Express)
// Ham gövde ŞART: imza ham byte'lar üzerinden doğrulanır (JSON.parse'tan ÖNCE).
import express from 'express';
import crypto from 'node:crypto';

const app = express();
const WEBHOOK_SECRET = process.env.RESTOMENUM_WEBHOOK_SECRET; // /connect exchange'inden geldi

// Bu route'ta JSON parser DEĞİL, raw body kullan:
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.header('X-Restomenum-Signature') || '';     // "t=1730..,v1=<hex>"
  const parts = Object.fromEntries(sig.split(',').map((p) => p.split('=')));
  const t = parts.t, v1 = parts.v1;

  // 1) replay koruması: timestamp 5 dk toleransı
  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(401);

  // 2) imza: HMAC_SHA256(secret, "<t>.<rawBody>")
  const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
    .update(`${t}.${req.body.toString('utf8')}`)
    .digest('hex');
  const ok = v1 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
  if (!ok) return res.sendStatus(401);

  // 3) artık güvenli: parse et ve işle
  const event = JSON.parse(req.body.toString('utf8'));
  // event.type, event.data, event.tenantId ...
  res.sendStatus(200); // 2xx dönmezsen Restomenum retry eder
});

Önemli noktalar

  • İmza: her zaman doğrula (raw body).
  • Idempotency: aynı id retry'da tekrar gelebilir — bir kez işle.
  • Hızlı 2xx: ağır işi kuyruğa al; senkron uzun işlem retry tetikler.
  • Payload: event'e özel data için event kataloğu.
  • Lifecycle: aynı uç app.installed / subscription.* / app.uninstalled da alır — type ile dallan (Lifecycle Webhook’ları).
Event almak için manifest'te events:subscribe scope'u ve events[] listesi gerekir. Geliştirme sırasında portaldaki “Test Event” aracıyla webhook'una sandbox payload gönderebilirsin.