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, environment, 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)

Header'lar

Her teslimde şu header'lar gelir:

AlanTipZorunluAçıklama
X-Restomenum-Signaturestringt=<unixSec>,v1=<hex> — HMAC imzası. Her zaman doğrula (ham gövde).
X-Restomenum-EventstringEvent tipi (örn. packet.created) — gövdedeki type ile aynı.
X-Restomenum-DeliverystringTeslim başına bir debug id (log/destek için). Idempotency anahtarı DEĞİLDİR — dedup için gövdedeki id'yi kullan.
X-Restomenum-Environment"sandbox" | "production"Gövdedeki environment'ın kolaylık kopyası — değeri gövdeden okunur, ikisi ayrışamaz. Header imzaya dahil değildir ve gövdeyi kuyruğa atıp sonra işleyen tasarımlarda kaybolur → gövdedeki alanı kullan.

Zarf (envelope)

Gövde tüm event'lerde aynı zarftır; event'e özel veri yalnız data içinde:

AlanTipZorunluAçıklama
idstringIdempotency anahtarı. Çağıran verdiyse o, yoksa evt_<uuid>. Retry'da aynı id tekrar gelebilir — bir kez işle.
typestringEvent tipi — X-Restomenum-Event ile aynı.
versionstringZarf şema sürümü — string "1" (sayı değil). Kurulum başına sabitlenir; versiyonlama.
environment"sandbox" | "production"Teslimin ortamı — imzalı gövdede (test/dev mağazası = "sandbox", gerçek mağaza = "production"). Tüm imzalı gövdelerde bulunur: event webhook'ları, lifecycle, hook/action/capability.
tenantIdstringTenant kimliği; kurulumla ve event'leri bununla eşle.
occurredAtnumberEvent anı — epoch milisaniye. (Dikkat: imzadaki t= unix saniye — farklı birim.)
dataobjectEvent'e özel gövde — event kataloğu.

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.