Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Webhook’lar

Bu sayfa Open Play’in oyun olaylarını kendi sunucunuza HTTP POST ile nasıl ilettiğini anlatır: uç nokta kaydı, olay türleri, imza doğrulaması, yeniden deneme ve tekrar eden teslimatla başa çıkma. Karakter, mağaza ya da ödül olayında kendi arka uç sisteminizi tetiklemek istiyorsanız okuyun.

Ne işe yarar

Open Play bir olay oluştuğunda (ör. level_up, store_purchase_completed, reward_granted) kayıtlı uç noktalarınıza imzalı bir POST gönderir. Teslimat en az bir kez (at-least-once) yapılır: aynı olay ağ hataları yüzünden birden çok gelebilir, bu yüzden alıcınız tekrarı X-Crx-Event-Id ile elemelidir.

Modül kapalıysa uçlar 404 ve {"code":"module_disabled","module":"webhooks"} döner. Modülü Oyun ayarları › Modüller sayfasından açın.

Panelde nerede

Ne yapacaksınızPanel sayfası
Kayıtlı uç noktaları, teslimat denemelerini, başarısız (DLQ) teslimatları ve olay kuyruğunu görmekWebhook ve olay kuyruğu
Modülü açmakOyun ayarları › Modüller

Panel bu sayfada salt okunurdur: uç nokta kaydı REST ile yapılır; panelde yeniden gönderme ya da başarısız teslimatı kurtarma düğmesi yoktur. Panel sırları, ham gövdeyi ve ham hata metnini göstermez.

Veri modeli (düz dille)

  • Uç nokta (endpoint): url, isteğe bağlı event_types süzgeci, enabled ve created_at.
  • Sır (secret): Sunucu üretir (whsec_ + 64 küçük harf hex), yalnız kayıt yanıtında bir kez döner ve şifreli saklanır. Sonra hiçbir uç onu vermez. Sır değiştirme ucu yoktur: yenilemek için uç noktayı silip yeniden kaydedin.
  • Olay zarfı: Gönderilen JSON gövdesi. Her zarfta event_id, type, environment_id ve data bulunur.
  • Teslimat (delivery): Bir olayın bir uç noktaya gönderimi. Durumları bekleyen, teslim edilen ve başarısız (DLQ).

Seçilebilir olay türleri

event_types boşsa uç nokta bütün olayları alır (aşağıdaki not). Doluysa yalnız şu türlerden seçebilirsiniz:

AlanTürler
Karakterflag_set, attribute_set, resource_changed, modifier_added, modifiers_removed, xp_added, level_up, ability_unlocked, event_emitted, character_rolled_back
Mağazastore_page_created, store_page_updated, store_page_deleted, store_pages_reordered, store_listing_created, store_listing_updated, store_listing_deleted, store_listings_reordered, store_purchase_initiated, store_payment_started, store_purchase_completed, store_reservation_cancelled
Ödülreward_granted

Bilinmeyen bir tür 400 döner. Tam liste ve veri alanları: Webhook olayları.

Uyarı

Süzgeci boş bırakırsanız uç nokta yukarıdaki listeyle sınırlı kalmaz: outbox’a yazılan bütün olayları (eşya, cüzdan, klan, parti, skor, bildirim kuyruğu gibi) alır. Bu ek olayların zarf biçimi belgelenmiş bir sözleşme değildir ve haber verilmeden değişebilir. Üretimde event_types süzgecini her zaman açıkça verin.

Zarf biçimi

Karakter olayları:

{
  "event_id": "0192f500-1111-7222-8333-944455556666",
  "type": "level_up",
  "character_id": "0192f0f2-5b1a-7c3e-9d11-4a5b6c7d8e9f",
  "environment_id": "0192f000-0000-7000-8000-00000000e001",
  "seq": 12,
  "data": { "...": "olaya özgü alanlar" }
}

Mağaza olaylarında character_id yoktur; seq ile birlikte now_ms (olayın işlendiği an) ve actor_api_key_id bulunur. reward_granted olayında player_id ve data içinde source_kind, table_key, table_version ile verilenler yer alır; çekiliş tohumu hiçbir olayda yoktur.

Oyunun yaptığı çağrılar

Bütün yönetim uçları sunucu anahtarıyla çağrılır; istemci anahtarı 403 alır:

Authorization: Bearer crx_live_<önek>_<gizli>
Yöntem ve yolNe yapar
POST /v1/webhooksUç nokta kaydeder (201; sır yalnız burada)
GET /v1/webhooksUç noktaları listeler (cursor, limit 1–200, varsayılan 50)
GET /v1/webhooks/{id}Tek uç noktayı okur (sırsız)
DELETE /v1/webhooks/{id}Uç noktayı siler (yumuşak silme, 204)

TypeScript SDK’sı bu dört ucu tipli sunar: createWebhook, listWebhooks, getWebhook, deleteWebhook (bkz. TypeScript SDK).

Uç nokta kaydetmek

POST /v1/webhooks
Authorization: Bearer crx_live_<önek>_<gizli>
Content-Type: application/json

{
  "url": "https://hooks.ornek-oyun.com/open-play",
  "event_types": ["level_up", "store_purchase_completed"]
}
{
  "id": "0192f600-…",
  "url": "https://hooks.ornek-oyun.com/open-play",
  "event_types": ["level_up", "store_purchase_completed"],
  "enabled": true,
  "created_at": "2026-11-03T09:00:00Z",
  "secret": "whsec_<64 hex karakter>"
}

secret değerini hemen güvenli bir yere (gizli yönetim servisine) yazın; bir daha okunamaz.

Kayıt kuralları:

  • URL https olmalı; kullanıcı bilgisi (user:parola@) taşımamalı; harf dışı kontrol karakteri ve boşluk içermemeli. Loopback, özel ve link-local IP adresleri kayıtta reddedilir (400).
  • Ayrıca her gönderimde adın çözümlendiği IP yeniden denetlenir; özel ya da loopback adrese çözümlenen ad gönderilemez. Yönlendirme (redirect) takip edilmez.
  • Aynı URL aktifken ikinci kayıt 409.

Teslimat

Her olay için uç noktanıza şu istek gelir:

POST /open-play
Content-Type: application/json
X-Crx-Signature: t=1790000000,v1=9c1f…e2
X-Crx-Event-Id: 0192f500-1111-7222-8333-944455556666
X-Crx-Event-Type: level_up

{ …zarf… }
BaşlıkAnlamı
X-Crx-Signaturet=<unix saniye>,v1=<hex HMAC-SHA256>
X-Crx-Event-IdOlay kimliği; tekrar eleme anahtarınız
X-Crx-Event-TypeZarftaki type değeri

İmzayı doğrulamak

İmza, "{t}.{ham gövde}" metninin, kayıtta dönen sırrın tamamı (whsec_… önekiyle) anahtar alınarak hesaplanan HMAC-SHA256 özetidir (küçük harf hex). Doğrulama adımları:

  1. X-Crx-Signature başlığından t ve v1 değerlerini ayırın.
  2. Ham gövde baytlarını alın. JSON’u ayrıştırıp yeniden serileştirmeyin; imza tel üzerindeki birebir baytlar içindir.
  3. HMAC-SHA256(secret, t + "." + ham_gövde) hesaplayın ve v1 ile sabit zamanlı karşılaştırın.
  4. t değerini kendi saatinizle karşılaştırın; çok eski (örneğin 5 dakikadan eski) istekleri reddedin. Her yeniden denemede t yenilenir, bu yüzden gerçek bir yeniden deneme bu pencerede kalır.
  5. X-Crx-Event-Id daha önce işlendiyse işlemi tekrarlamadan 2xx dönün.

Yanıt ve yeniden deneme kuralları

  • Yalnız 2xx başarıdır. Başka her yanıt, zaman aşımı ve bağlantı hatası yeniden denemeyi tetikler.
  • Bağlantı için 5 saniye, DNS dahil toplam 10 saniye beklenir.
  • Gövde 64 KB’ı aşarsa gönderilmez ve hata sayılır.
  • Gecikme min(1 sn × 2^deneme, 5 dk)’dır ve ±%20 rastgelelik içerir. En çok 10 deneme yapılır; 10. başarısız denemeden sonra teslimat başarısız (DLQ) durumuna alınır ve otomatik tekrar edilmez.
  • Uç noktayı sildiğinizde bekleyen teslimatlar da başarısız duruma alınır.
  • Sıra garantisi yoktur: olaylar karakter için seq ile sıralanabilir, ama teslimat sırası değişebilir.
  • Uç nokta yokken oluşan olaylar sonradan gönderilmez; geriye dönük teslimat yoktur.

Sürümleme ve tekrar güvenliği

  • Alıcı tarafı: X-Crx-Event-Id ile tekrar eleme zorunludur. Aynı olay yeniden denemede aynı event_id ile gelir; imza zamanı (t) yenilenir.
  • Kayıt ucunda Idempotency-Key yoktur; aynı URL’yi ikinci kez kaydetmek 409 döner.
  • Zarfta bir olay sürümü alanı yoktur; tanımadığınız alanları yok sayın ve gövdeyi sıkı bir şemayla reddetmeyin.

Sınırlar

  • Gövde 64 KB; toplam bekleme 10 saniye; 10 deneme.
  • Liste ucunda limit 1–200.
  • Redirect takip edilmez; yalnız https.

Sık görülen hatalar

BelirtiNedenÇözüm
Kayıtta 400URL https değil, IP adresi, kullanıcı bilgisi ya da bilinmeyen olay türüURL ve event_types değerlerini düzeltin
Kayıtta 403İstemci anahtarıSunucu anahtarı kullanın
Kayıtta 409Aynı URL zaten aktifVar olanı silin ya da kullanın
İmza doğrulanmıyorGövde yeniden serileştirildi; sırrın whsec_ öneki atıldı; saat farkıHam baytlarla, sırrın tamamıyla hesaplayın
Aynı olay iki kez geliyorAt-least-once teslimatX-Crx-Event-Id ile eleyin
Teslimatlar durdu10 deneme doldu (DLQ) ya da uç nokta silindiAlıcıyı düzeltin; kaçan olaylar için kendi kaynağınızdan okuyun
Teslimat hiç gelmiyorUç adı özel/loopback IP’ye çözümleniyor ya da yanıt 10 saniyeyi aşıyorGenel bir adres kullanın; yanıtı hızlı verin

Çalışan örnek: Node ile alıcı

Aşağıdaki sunucu imzayı doğrular, tekrar eden olayı eler ve 2xx döner. Sır ortam değişkeninden gelir; üretimde kalıcı bir depoyla tekrar elemesi yapın.

/** Webhook alıcısı (Node 22): imzayı doğrular, tekrar eden teslimatı `X-Crx-Event-Id` ile eler. */
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";

const SECRET = process.env["OPEN_PLAY_WEBHOOK_SECRET"] ?? ""; // uç noktayı kaydederken bir kez dönen sır
const TOLERANCE_SECONDS = 300; // eski teslimatı (replay) reddetme penceresi: alıcının kendi politikası
const seen = new Set<string>(); // örnek amaçlı bellek; üretimde kalıcı depo kullanın

/** `X-Crx-Signature: t=<unix-sn>,v1=<hex>` değerini ham gövdeye karşı doğrular. */
export function verifySignature(secret: string, header: string, rawBody: Buffer, nowSeconds: number): boolean {
  const parts = new Map(header.split(",").map((part) => part.split("=", 2) as [string, string]));
  const t = parts.get("t");
  const v1 = parts.get("v1");
  if (t === undefined || v1 === undefined) return false;
  if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;
  const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(v1, "utf8");
  return a.length === b.length && timingSafeEqual(a, b);
}

createServer((req, res) => {
  const chunks: Buffer[] = [];
  req.on("data", (chunk: Buffer) => chunks.push(chunk));
  req.on("end", () => {
    const rawBody = Buffer.concat(chunks); // imza ham bayt üzerindendir: JSON'u yeniden serileştirmeyin
    const signature = req.headers["x-crx-signature"];
    if (typeof signature !== "string" || !verifySignature(SECRET, signature, rawBody, Math.floor(Date.now() / 1000))) {
      res.writeHead(401).end();
      return;
    }
    const eventId = String(req.headers["x-crx-event-id"] ?? "");
    if (!seen.has(eventId)) {
      seen.add(eventId);
      const event = JSON.parse(rawBody.toString("utf8")) as { type: string; data: unknown };
      console.log("olay", event.type, event.data);
    }
    res.writeHead(200).end(); // yalnız 2xx başarıdır; başka kod yeniden denemeyi tetikler
  });
}).listen(8080);

İlgili sayfalar