Operatör entegrasyon rehberi · v1

Chaos Lane'i sitenize bağlayın

Bu belge, bir casino veya bahis sitesinin Chaos Lane oyununu kendi oyuncularına açması için gereken her şeyi anlatır: istekleri imzalamak, oyunu açmak, iframe ile gömmek, oyuncu bakiyesini kendi cüzdanınızda tutmak ve olayları webhook ile almak.

REST + JSONEd25519 veya HMAC imzalı isteklerSeamless wallet (HMAC)Webhook (at-least-once)
Durum: taslak (v1)Bu API yalnızca kendi test cüzdanımıza (mock partner) karşı uçtan uca test edildi. Henüz hiçbir operatör veya aggregator tarafından sertifikalanmadı. Gerçek para ile oyun açılmadan önce ortak kabul testi yapılacak. Sandbox adresi: https://api.urbanrats.com. İngilizce entegrasyon rehberi: api.urbanrats.com/integration.

01 Genel bakış

Chaos Lane, gerçek trafik kameralarında belirli bir çizgiyi ok yönünde geçen araçların sayısı üzerine oynanan kısa turlu bir oyundur. Sonuç rastgele sayı üretecinden değil, kamera görüntüsünde sayılan gerçek geçişlerden gelir.

Siz ne yaparsınız?

Oyuncunuz için bir oturum açarsınız, dönen linki sitenizde iframe içinde gösterirsiniz ve cüzdan çağrılarımıza cevap verirsiniz.

Biz ne yaparız?

Turları yönetir, kamerayı sayar, bahisleri kabul eder, sonucu hesaplar ve bakiye değişikliklerini cüzdanınıza bildiririz.

Para kimde?

Her zaman sizde. Oyuncu bakiyesi sizin cüzdanınızdadır; biz para tutmayız, sadece borç/alacak talimatı göndeririz.

İki yönlü iletişim

YönNe içinGüvenlik
Siz → UrbanRats
Partner API
Oyun listesi, oturum açma, bahis/tur sorgulama, promosyon, webhook yönetimiEd25519 imza (özel anahtar sizde, biz yalnızca açık anahtarı saklarız) veya HMAC-SHA256 (paylaşılan sır)
UrbanRats → Siz
Wallet callback
Bakiye sorma, bahis tutarını düşme, kazancı yatırma, iadeHMAC-SHA256 imza, iki tarafın cevabı da imzalı
UrbanRats → Siz
Webhook
Bahis kabul / sonuç / iade, promosyon olaylarıEd25519 imza (açık anahtarımız GET /v1/keys)

02 Entegrasyon akışı

Bir oyuncunun oyuna girip bahis yapması ve sonucun cüzdanına yansıması dört adımdan oluşur.

1Oturum aç

Sunucunuz imzalı POST /v1/launch-sessions çağırır, launchUrl alır.

2Göster

Sayfanızda launchUrl'i iframe ile açarsınız (60 sn içinde, tek kullanımlık).

3Bahis

Oyuncu bahis yapar; biz cüzdanınıza /bet gönderir, tutarı düşmenizi isteriz.

4Sonuç

Tur bitince /win (kayıpta 0) veya iptalde /rollback gelir; webhook da gönderilir.

Oyuncu Sizin siteniz UrbanRats API Sizin cüzdanınız │ "Oyna" ─────▶ │ │ │ │ │── POST /v1/launch-sessions (imzalı) ─▶│ │ │ │◀──────── { launchUrl } ────│ │ │◀── iframe(launchUrl) ─│ │ │ │ bahis ───────────────────────────────────▶ │── POST /bet (HMAC) ─────▶│ │ │ │◀──── OK + bakiye (imzalı) ─│ │ … kamera sayar, tur kapanır … │ │ │ │ │── POST /win (HMAC) ─────▶│ │ │◀── webhook bet.settled ──│ │

03 Ortamlar ve anahtarlar

OrtamAPI adresiNot
Sandboxhttps://api.urbanrats.comDemo para. Test oyuncunuz ve test cüzdanınızla deneme yaparsınız. Gerçek para site bazında açılır (bölüm 05d).
Canlı (production)Ayrıca bildirilecekOrtak kabul ve onaydan sonra açılır.

Makine tarafından okunabilir şemalar: GET /v1/openapi.json (Partner API, tüm hata kodları x-error-codes altında) ve wallet callback'ler için ayrı bir OpenAPI dosyası (talep üzerine).

Bizim tarafımızda yapılan kurulum

  1. Operatör kaydınız ve markalarınız (brand) oluşturulur: her markanın cüzdan para birimi, izinli ülkeleri, gösterim para birimleri ve oyunu gömeceğiniz site adresleri (origin).
  2. Markalarınıza Chaos Lane (gameId: traffic-pulse) yetkisi verilir.
  3. Cüzdan ve webhook için kullanacağınız sunucu alan adlarınız (tam DNS adı) kaydedilir. Kayıtlı olmayan bir adrese asla çağrı yapmayız.
  4. Cüzdan imzası için iki taraflı HMAC sırları güvenli bir kanalla paylaşılır.

API anahtarınız

Sandbox anahtarı

Self-servis portal: partner hesabınızla (ops.urbanrats.com, şifre + doğrulama uygulaması) kendi Ed25519 açık anahtarınızı kaydeder, sandbox anahtarlarını açar/döndürür, cüzdan imza sırlarınızı kendiniz üretirsiniz (bir kez, yalnızca size gösterilir), cüzdan adresinizi, wire scale'leri ve callback/webhook alan adlarınızı talep edersiniz (onayımızdan sonra uygulanır), webhook'ları yönetir ve uyumluluk testini çalıştırırsınız. Önerilen: Ed25519 anahtar çiftini siz üretip yalnızca açık anahtarı kaydedin; hiçbir sır el değiştirmez. Alternatif: geliştirici portalında “Sandbox anahtarı oluştur” ile Ed25519 anahtar çifti üretilir. Özel anahtar bir kez gösterilir, biz saklamayız. Portalda döndürülebilir; eskisi 1 saat daha geçerli kalır.

HMAC anahtarı (alternatif)

Paylaşılan sır tercih ederseniz anahtar HMAC-SHA256 olarak açılır. Sırrı biz üretiriz, bir kez gösterilir; bizde şifreli saklanır. Döndürmede yeni sır yine bir kez gösterilir. Ayrıntı bölüm 4'te.

Canlı anahtar

Anahtar çiftini siz üretirsiniz, bize yalnızca açık anahtarı gönderirsiniz. İki farklı yetkilimizin onayıyla aktif olur; diğer canlı anahtarlarınız 24 saat sonra geçersizleşir.

Anahtarın yetkileri (scope): games:read launch:create bets:read rounds:read rewards:write webhooks:manage. İsteğe bağlı olarak bir IP izin listesi (CIDR) eklenebilir. İptal edilen anahtar anında geçersiz olur.

Ed25519 anahtar çifti üretmek için:

openssl genpkey -algorithm ed25519 -out urbanrats-private.pem
openssl pkey -in urbanrats-private.pem -pubout -out urbanrats-public.pem   # bunu bize gönderin
Özel anahtar yalnızca sunucunuzda kalır.Tarayıcıya, mobil uygulamaya veya kaynak koduna asla koymayın. Partner API çağrılarının hepsi sizin sunucunuzdan yapılır.

04 İstek imzalama (Ed25519 veya HMAC)

Partner API'ye giden her istek dört başlık taşır. Bu başlıklar olmadan istek 401 ile reddedilir.

BaşlıkDeğer
x-ur-key-idAnahtar kimliğiniz
x-ur-timestampUnix zamanı, milisaniye. Saatimizden en fazla ±60 sn farklı olabilir (NTP kullanın).
x-ur-nonce16–64 karakter, [A-Za-z0-9_-]. Her istekte yeni; aynı nonce ikinci kez kabul edilmez.
x-ur-signatureAşağıdaki kanonik metnin Ed25519 imzası, base64.

Kanonik metin

Satırlar \n ile ayrılır, sonda satır sonu yoktur, UTF-8:

UR1-ED25519-REQUEST
POST                                  ← metot, büyük harf
/v1/launch-sessions                   ← path, sorgu (query) olmadan
                                      ← kanonik query (yoksa boş satır)
3f10f49bbecdb60e…ccc79e280            ← gövdenin (body) sha256 hex'i; boş gövde = e3b0c442…b855
1790000000000                         ← x-ur-timestamp
n0nce-0000000000001                   ← x-ur-nonce
sbx-test-vector                       ← x-ur-key-id

Kanonik query: sorgu dizesini çözün (+ = boşluk), her çifti encodeURIComponent(anahtar)=encodeURIComponent(değer) olarak yazın, önce anahtara sonra değere göre sıralayın, & ile birleştirin. Tekrarlanan anahtarlar korunur.

Gövdeyi tam olarak imzaladığınız haliyle gönderin.Hash, gönderilen ham baytlar üzerinden hesaplanır. JSON'u imzaladıktan sonra yeniden biçimlendirmeyin.

Örnek kod

import { createHash, createPrivateKey, randomBytes, sign } from 'node:crypto';

const sha256 = (s) => createHash('sha256').update(s, 'utf8').digest('hex');

function canonicalQuery(search) {
  return [...new URLSearchParams(search)]
    .map(([k, v]) => [encodeURIComponent(k), encodeURIComponent(v)])
    .sort(([a, x], [b, y]) => (a < b ? -1 : a > b ? 1 : x < y ? -1 : x > y ? 1 : 0))
    .map(([k, v]) => `${k}=${v}`).join('&');
}

export async function urbanrats(method, url, bodyObj, { keyId, privateKeyPem }) {
  const body = bodyObj ? JSON.stringify(bodyObj) : '';
  const u = new URL(url);
  const timestampMs = Date.now();
  const nonce = randomBytes(18).toString('base64url');   // 24 karakter
  const canonical = ['UR1-ED25519-REQUEST', method.toUpperCase(), u.pathname, canonicalQuery(u.search),
    sha256(body), String(timestampMs), nonce, keyId].join('\n');
  const signature = sign(null, Buffer.from(canonical, 'utf8'), createPrivateKey(privateKeyPem)).toString('base64');
  const res = await fetch(u, {
    method,
    headers: { 'content-type': 'application/json', 'x-ur-key-id': keyId, 'x-ur-timestamp': String(timestampMs),
               'x-ur-nonce': nonce, 'x-ur-signature': signature },
    body: body || undefined,
  });
  const data = await res.json();
  if (!res.ok) throw Object.assign(new Error(data.message), { code: data.code, status: res.status });
  return data;
}

Node.js örneği test vektörleriyle doğrulandı. Python örneğinin kanonik query kısmı doğrulandı; Python ve PHP imza kısmını kendi ortamınızda aşağıdaki test vektörüyle kontrol edin.

Test vektörü (imzanızı doğrulayın)

Aşağıdaki yalnızca test amaçlı anahtarla, verilen girdilerle tam olarak bu imzayı üretmelisiniz. Bu anahtarı başka hiçbir yerde kullanmayın.

# Test özel anahtarı (herkese açık tohum 0x01..0x20'den türetildi)
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8g
-----END PRIVATE KEY-----

method     POST
path       /v1/launch-sessions
body       {"brandId":"alpha-eu","externalPlayerId":"p-1","gameId":"traffic-pulse","country":"DE"}
timestamp  1790000000000
nonce      n0nce-0000000000001
keyId      sbx-test-vector

sha256(body) = 3f10f49bbecdb60e6f7bca6f95ad2343a832d1671ab1d84e25e34a1ccc79e280
signature    = OOWcqLvAtoYJKU/Yeas+zkGcRxvhbZUbv9g0Ytd7Izf7nhhjIDFSNSdVWY1hubCgjW/T3ux672f49ZEWzlGwBw==
# Sorgulu GET (sıralama + kodlama testi)
GET /v1/bets?state=SETTLED&externalPlayerId=p+1%2F%C3%A4&limit=10
timestamp 1790000000500 · nonce n0nce-0000000000002 · keyId sbx-test-vector

kanonik query = externalPlayerId=p%201%2F%C3%A4&limit=10&state=SETTLED
signature     = vNxyJO/2jBDsZIIeqcWfzWfu8fjR1BbnfvB2AjRtD3gSz7JKDd9fUYI7HKUepekKbs6bJHjhDPXXRa+fE+KwCQ==

Tekrar deneme kuralı

Aynı nonce ile gelen istek NONCE_REPLAY ile reddedilir. Bir işlemi tekrar denemek için yeni nonce ve aynı iş kimlikleri (ör. operatorRewardId) ile yeniden gönderin; ilk sonucu geri alırsınız.

HMAC-SHA256 ile imzalama

Başlıklar, nonce, zaman ve kanonik metin Ed25519 ile aynıdır; yalnızca kanonik metnin ilk satırı UR1-HMAC-REQUEST olur ve imza x-ur-signature = base64(HMAC-SHA256(sır, kanonik)) şeklinde hesaplanır. Yetkiler, IP izin listesi ve döndürme kuralları aynıdır.

import { createHmac } from 'node:crypto';
const canonical = ['UR1-HMAC-REQUEST', method.toUpperCase(), u.pathname,
  canonicalQuery(u.search), sha256(body ?? ''), ts, nonce, keyId].join('\n');
const signature = createHmac('sha256', secret).update(canonical).digest('base64');
# HMAC test vektörü — TEST sırrı, gerçek hiçbir şeyde kullanmayın
sır     ur-test-hmac-secret-do-not-use
POST /v1/launch-sessions
gövde   {"brandId":"alpha-eu","externalPlayerId":"p-1","gameId":"traffic-pulse","country":"DE"}
ts      1790000000000   nonce n0nce-0000000000001   keyId sbx-test-hmac
sha256(gövde) 3f10f49bbecdb60e6f7bca6f95ad2343a832d1671ab1d84e25e34a1ccc79e280
imza          2dDwMfMk4py9J1CdXEQpPhwa/yQXRSGLm0NyAEALnMQ=

05 Oyunu açma (launch)

Oyuncu “Oyna”ya bastığında sunucunuz bir oturum açar ve dönen linki oyuncuya gösterir.

POST/v1/launch-sessionslaunch:create

Gövde katı (strict) şemadır: bilinmeyen bir alan gönderirseniz 400 BAD_REQUEST alırsınız.

AlanTipAçıklama
brandId *stringKurulumda tanımlanan marka kimliğiniz
externalPlayerId *stringSizin oyuncu kimliğiniz. Kişisel veri (ad, e-posta) göndermeyin; sabit, anlamsız bir kimlik yeterli.
gameId *stringtraffic-pulse (Chaos Lane). Sabit oyun listesi kullanmayın; GET /v1/games kataloğunu kullanın — emekli oyunlar GAME_RETIRED döner.
country *ISO-3166 alpha-2Bu oyuncunun yetki alanı (ör. DE, TR); her açılışta oyuncuya göre gönderilir, sabit bir ayar değildir. KYC sorumluluğu sizdedir.
currencystringİsteğe bağlı doğrulama. Markanın cüzdan para biriminden farklıysa CURRENCY_MISMATCH.
displayCurrencystringYalnızca gösterim için, marka için açık olmalı
languagexx / xx-XXArayüz dili, ör. tr, en-GB
allowedOriginoriginOyunu gömeceğiniz sayfanın origin'i (ör. https://casino.example). Markanın izin listesinde olmalı.
operatorTokenstring (önerilir)Kendi cüzdan oturum anahtarınız (≤ 512 karakter). Oturumla saklanır ve her cüzdan çağrısında (/balance dahil) size geri gönderilir; kapattığınız oturumdan gelen bahsi reddedebilir, bahsin hangi oturumunuzdan geldiğini izleyebilirsiniz.
devicedesktop / mobileCihaz türü; biz tahmin etmeyiz.
lobbyUrl, depositUrl, returnUrlURLMutlak https adresleri (≤ 2048, kullanıcı adı/şifre yok). Oturum bitince oyun “Siteye dön” ile lobbyUrl'e yönlendirir. Gönderilmezse markanın kurulumdaki varsayılanları kullanılır.
// İstek
{
  "brandId": "alpha-eu",
  "externalPlayerId": "p-10293",
  "gameId": "traffic-pulse",
  "country": "DE",
  "language": "de",
  "allowedOrigin": "https://casino.example",
  "operatorToken": "wallet-session-8f2c…",
  "device": "mobile",
  "lobbyUrl": "https://casino.example/lobby"
}

// 200 Cevap
{
  "launchCode": "lc_…",
  "launchUrl": "https://play.urbanrats.com/?launch=lc_…",
  "expiresInS": 60,
  "currency": "EUR",
  "displayCurrency": "EUR"
}

Tek kullanımlık, 60 saniye

Link bir kez açılabilir ve 60 sn içinde kullanılmalıdır. Linki önceden üretip saklamayın; oyuncu “Oyna”ya bastığı anda üretin.

Olası hatalar

GAME_NOT_ENTITLED, COUNTRY_NOT_ALLOWED, CURRENCY_MISMATCH, DISPLAY_CURRENCY_NOT_ALLOWED, ORIGIN_NOT_ALLOWED, NAVIGATION_URL_INVALID, OPERATOR_SUSPENDED, BRAND_SUSPENDED, DEMO_PLAYER_RESERVED; gerçek para sitesinde cüzdan bağlı değilse 503 REAL_MONEY_UNAVAILABLE. Bunlar yapılandırma reddidir; aynı isteği tekrarlamak işe yaramaz.

Oturum süresi

Oyun oturumu kullanıldıkça uzar (4 saat boşta kalma sınırı) ve en geç açılıştan 24 saat sonra biter; sonra yeni bir launch açın.

Oyun açıldıktan sonra launch kodu adres çubuğundan ve tarayıcı geçmişinden silinir; cevaplarımız Referrer-Policy: no-referrer taşır. IP konumuna göre ülke belirlemeyiz.

05b Eğlence modu (demo) ile oyun açma

“Ücretsiz oyna” düğmesi veya site tanıtımı için. Oyuncu kimliği göndermezsiniz; biz anonim bir demo:… oyuncusu açarız ve oyun parası bizim demo cüzdanımızdan gelir. Markanız seamless wallet kullanıyor olsa bile demo oturum için cüzdanınıza hiçbir çağrı yapılmaz. Oyunda “EĞLENCE MODU” etiketi görünür.

POST/v1/demo-sessionslaunch:create

Alanlar: brandId *, gameId *, country *, language, allowedOrigin, device, lobbyUrl. Marka, oyun yetkisi, ülke ve origin kontrolleri gerçek launch ile aynıdır. Başka alan (ör. externalPlayerId) gönderirseniz 400 BAD_REQUEST. Cevap launch ile aynı biçimdedir (launchCode, launchUrl, expiresInS).

{ "brandId": "alpha-eu", "gameId": "traffic-pulse", "country": "DE", "language": "de" }
Gerçek oyundan ayrı tutulur.Demo oyuncular istatistiklere, sıralamalara, ödüllere, ödül sandığına ve kampanyalara girmez; oyun parası gerçek paraya dönüşmez. demo: ile başlayan oyuncu kimlikleri ayrılmıştır: normal launch'ta kullanılırsa DEMO_PLAYER_RESERVED döner.

05c Aggregator modu: çok site, tek entegrasyon

Kendi müşterilerinin çok sayıda sitesine oyunu dağıtan aggregator ve oyun platformları için: tek API anahtarı, tek cüzdan bağlantısı, tek webhook. Sitelerinizi bize tek tek kaydettirmezsiniz.

  • Her açılışta brandId olarak kendi site kimliğinizi, ayrıca oyuncunun cüzdan para birimini (currency) ve isteğe bağlı siteName gönderin.
  • Bir site bir para biriminde ilk kez açıldığında, aggregator şablonunuzdan (izinli para birimleri, oyuncu ülkeleri, oyunlar, sayfa adresi politikası) otomatik oluşturulur. Her site + para birimi bizde bir markadır: {siteId}.{para birimi} (ör. site-42.eur), cevapta brandId olarak döner.
  • country yine her oyuncunun kendi yetki alanıdır ve şablondaki ülkelere göre kontrol edilir.
  • externalPlayerId tüm siteleriniz arasında benzersiz olmalı (ör. site kimliğiyle önekleyin); cüzdan çağrılarında size aynen geri gelir.
  • Varsayılan DECLARED politikasında beyan ettiğiniz her https origin o site için kabul edilir.
  • Raporlar, limitler, sorumlu oyun ayarları ve askıya alma site bazında çalışır; tek bir sitenizi (tüm para birimleriyle) diğerlerine dokunmadan durdurabiliriz. Ödül ve eğlence modu çağrıları da site kimliğinizi kabul eder.
  • Hatalar: CURRENCY_REQUIRED, CURRENCY_NOT_ALLOWED, SITE_LIMIT.
{ "brandId": "site-42", "siteName": "Casino 42", "currency": "EUR",
  "externalPlayerId": "site-42:p-10293", "gameId": "traffic-pulse", "country": "DE" }
// → 200 { "launchUrl": "…", "currency": "EUR", "brandId": "site-42.eur", … }

05d Gerçek para ve demo para (site bazında)

Her site (marka) ya demo parayla (oyun parası veya test cüzdanınız) ya da gerçek parayla çalışır. Gerçek para sizin şans oyunu lisansınız altında, sizin seamless cüzdanınız üzerinden yürür. Oyunculara ikisini birden sunabilirsiniz: normal açılış sitenin modunda oynar, eğlence modu (POST /v1/demo-sessions) her zaman oyun parasıdır.

  • Gerçek paraya geçiş: lisans bilgilerinizi (yetki alanı, lisans numarası, lisans sahibi şirket) bize iletin. Siteyi UrbanRats açar; ikinci bir UrbanRats yöneticisi onaylar. Talep, onay ve lisans bilgileriniz kayda geçer. Demoya dönüş anında yapılır. Durumu partner portalında (Operators → Real money) görürsünüz.
  • Şartlar: seamless cüzdanınız bağlı olmalı (bölüm 08) ve sitenin cüzdan para birimi gerçek paraya açık olmalı.
  • Para birimleri: gerçek para için EUR, USD, TRY (scale 2). BTC, ETH, USDT yalnızca oyun parasıdır.
  • Oturum: oturum açıldığı modda kalır; site sonradan değişse de çalışan demo oturumu gerçek paraya dönmez (tersi de). GET /v1/player/session içinde moneyMode: REAL, DEMO veya FUN.
  • Katalog: GET /v1/games içinde site başına entitlements[].moneyMode; sitelerinizden en az biri gerçek paradaysa realMoneyEnabled: true.
  • Promosyonlar: gerçek para sitesinde bizim ödüllerimiz, drop sandıkları ve kampanyalar kapalıdır. Kendi freebet'leriniz (POST /v1/rewards) sizin kararınız ve sizin maliyetinizdir.
  • Ülkeler: beyan edilen country geçerli bir ISO 3166-1 alpha-2 kodu olmalı ve site için izinli olmalı. Lisansınızın hangi ülkeleri kapsadığına siz karar verirsiniz; biz yalnızca sizinle anlaştığımız listeyi uygularız.
  • Cüzdan bağlı değilse: gerçek para sitesinde açılış 503 REAL_MONEY_UNAVAILABLE döner; hiçbir şey oyun parasına kaymaz.
İddia etmediklerimiz.UrbanRats'ın şans oyunu lisansı, bağımsız laboratuvar sertifikası, ölçülmüş yakalama zamanı sınırı veya doğrulanmış fiyatlama modeli henüz yok; oranlar bizim belirlediğimiz sabit fiyatlardır. Gerçek para sizin lisansınız ve oyuncularınıza / düzenleyicinize karşı sizin sorumluluğunuz altında çalışır.

06 Sitenize gömme (iframe)

En basit yol: launchUrl'i bir iframe'e koymak. Oyun ile sayfanız arasında mesajlaşmak isterseniz küçük bir istemci kütüphanesi (embed SDK) sağlıyoruz.

En basit hali

<iframe
  src="LAUNCH_URL"
  title="Chaos Lane"
  allow="autoplay; fullscreen"
  referrerpolicy="no-referrer"
  style="width:100%; aspect-ratio:16/9; min-height:560px; border:0">
</iframe>

Oyun mobil, tablet ve masaüstüne uyumludur. Mobilde iframe'e ekranın tamamını vermenizi öneririz.

Embed SDK ile (isteğe bağlı)

import { UrbanRatsEmbed } from '@urbanrats/embed-sdk';

const game = new UrbanRatsEmbed({
  gameOrigin: 'https://play.urbanrats.com',     // tam origin, joker yok
  launchUrl,                                   // sunucunuzdan gelen link
  container: document.getElementById('game'),
});

game.on('ready', (d) => console.log('hazır', d.walletCurrency));
game.on('balance.changed', (d) => headerBalance.update(d.balance));   // sadece gösterim
game.on('session-expired', () => relaunch());

await game.command('refresh-balance');
await game.command('mute', { muted: true });
Komutlar (siz → oyun)Olaylar (oyun → siz)
refresh-balance, mute, volume, open-history, close-panels, get-stateready, error, session-expired, round.phase, bet.accepted, bet.rejected, bet.settled, balance.changed
Kural onayı (sizin yapacağınız bir şey yok).Oyuncu ilk oyununa girmeden önce oyun, sayım kurallarını resimli kısa bir özetle gösterir ve onay ister: yalnızca okların yönü sayılır, araç ortası çizgiyi geçince sayılır, her araç (otomobil, otobüs, kamyon, motosiklet) 1 sayılır, yalnızca kameranın gördüğü sayılır, yaya ve bisiklet sayılmaz, sayılan dakika ve görüntü bozulursa iade. Onay oyuncu ve kural sürümü bazında bizde saklanır (tüm cihazlarda geçerli; kurallar değişirse yeniden sorulur). Oyuncu bir daha göstermemeyi seçebilir.
Sayfa mesajları para hareketi yapmaz.iframe mesajları yalnızca arayüz içindir. Bakiye değişikliğinin tek doğru kaynağı cüzdan çağrılarıdır (bölüm 8). Mesajlar her iki yönde tam origin ve kaynak pencere kontrolüyle kabul edilir; joker (*) hedef kullanılmaz.

07 Diğer API rotaları

Tüm rotalar imzalıdır (bölüm 4). Kiracı (tenant) her zaman imzalayan anahtardan belirlenir; başka bir operatörün kimlikleri 404 döner.

GET/v1/gamesgames:read

Yetkili olduğunuz oyunlar ve her markanın izinli ülkeleri. İmzasız çağrıda herkese açık katalog döner.

GET/v1/bets · /v1/bets/{betId}bets:read

Filtreler: externalPlayerId, gameRoundId, state, since, limit (en fazla 500). Mutabakat ve oyuncu destek ekranlarınız için.

{
  "betId": "…", "gameId": "traffic-pulse", "gameRoundId": "…", "partnerWalletRoundId": "…",
  "selection": "OVER",
  "stake":     { "amountAtomic": "500", "currency": "EUR", "scale": 2 },
  "funding": "CASH",
  "state": "SETTLED",
  "payoutGross": { "amountAtomic": "1150", "currency": "EUR", "scale": 2 },   // toplam dönüş (bahis dahil)
  "payoutNet":   { "amountAtomic": "650",  "currency": "EUR", "scale": 2 },   // net kazanç
  "submittedAt": "2026-10-03T12:00:01.204Z", "updatedAt": "…"
}

Bahis durumları: SUBMITTED → DEBIT_PENDING → ACCEPTED | REJECTED → RESULT_WON / RESULT_LOST → CREDIT_PENDING → SETTLED. İptal yolu: REFUND_PENDING → VOIDED. Geç düşülen bahis: COMPENSATION_PENDING → COMPENSATED.

GET/v1/rounds/{id} · /v1/rounds/{id}/evidencerounds:read

Turun kuralları, sonucu ve imzalı kanıt paketi (manifest, sayım, hash'ler). Bir oyuncu itirazında turun nasıl sonuçlandığını göstermek için kullanılır. manifest.line sayım çizgisidir (a→b ve countedDirection); isteğe bağlı line.zone, oyuncunun çizginin önünde gördüğü sarı yaklaşma bölgesinin ön kenarıdır — yalnızca görseldir, sayımda kullanılmaz.

POST/v1/rewards · /v1/rewards/{id}/cancel · GET/v1/rewardsrewards:write

Oyuncuya freebet veya kazanç artırımı (boost) tanımlayın. operatorRewardId ile idempotenttir; aynı kimlikle tekrar gönderim ilk sonucu döner. Tutar markanın cüzdan para biriminde olmalı. İptal yalnızca kullanılmamış ödüller içindir.

{
  "brandId": "alpha-eu",
  "externalPlayerId": "p-10293",
  "grant": {
    "operatorRewardId": "welcome-p-10293",
    "kind": "FREEBET",                        // veya "BOOST"
    "faceValue": { "amountAtomic": "500", "currency": "EUR", "scale": 2 },
    "freebetPolicy": "PROFIT_ONLY",
    "boostBps": null,                         // BOOST için 1–10000 (baz puan)
    "games": ["traffic-pulse"],
    "expiresAt": "2026-10-31T23:59:59.000Z"
  }
}
POST/v1/webhooks · GET/v1/webhookswebhooks:manage

Webhook adresi kaydı: {"url":"https://hooks.casino.example/urbanrats","events":["bet.settled","bet.refunded"]}. Ek rotalar: POST /v1/webhooks/{id}/disable, POST /v1/webhooks/{id}/test, GET /v1/webhooks/deliveries, POST /v1/webhooks/deliveries/{id}/replay.

GET/v1/keysherkese açık

Açık anahtarlarımız (MANIFEST, RESULT, PLAYBACK, WEBHOOK) ve iptal zamanları. Webhook imzalarını bu listedeki WEBHOOK anahtarıyla doğrularsınız.

07b Oyuncu istatistikleri ve sıralamalar

GET/v1/players/{externalPlayerId}/statsbets:read

Oyuncunun oyundaki “İstatistiklerim” özetinin aynısı: tüm oturumlardaki toplamlar (rounds, bets, wins, exactHits, bestMultiplier, playMinutes), son 30 gün, para birimi başına tutarlar (stake, payout, net, biggestWin, atomik dize + scale; para birimleri asla toplanmaz), favori seçim ve son 15 sonuçlanmış bahis (turun nihai sayısı ve L/H eşikleriyle). Yalnızca istatistik; video tekrar yok. Sonuçlanmadan sonra yaklaşık bir dakika içinde güncellenir. Başka operatörün oyuncusu 404.

{ "alias": "Lynx-4821", "firstPlayed": "2026-10-02",
  "totals": { "rounds": 58, "bets": 74, "wins": 29, "exactHits": 3, "bestMultiplier": 12.5, "playMinutes": 96 },
  "last30Days": { "rounds": 58, "bets": 74, "wins": 29 },
  "money": [{ "currency": "EUR", "scale": 2, "stake": "7400", "payout": "6930", "net": "-470", "biggestWin": "1250" }],
  "favouriteSelection": "OVER", "recent": [ /* … */ ] }
GET/v1/leaderboards · /v1/leaderboards/{id}games:read

Markalarınızın katıldığı sıralamalar (global ve size özel), güncel dönem ve tek bir sıralama için ilk 100 (yalnızca takma ad ve ülke). yours: true sizin oyuncunuzu işaretler. Sıralama ve ödül sandığı ödülleri oyuncunuza freebet/boost olarak verilir ve size reward.granted webhook'u ile origin (LEADERBOARD, DROP, CAMPAIGN) bilgisiyle gelir. Sıralama, ödül ve sandık; operatör, site ve ülke bazında kapatılabilir.

08 Seamless wallet: sizin yazacağınız uç noktalar

Oyuncu bakiyesi sizin sisteminizde kalır. Biz, kayıtlı alan adınızdaki bir temel adrese (ör. https://wallet.casino.example/urbanrats) aşağıdaki beş çağrıyı yaparız. Hepsi POST, JSON, imzalıdır.

Uç noktaNe zamanSizin yapacağınızİdempotent anahtar
/balanceOyun açılırken, gerektiğindeBakiyeyi döndürün (değiştirmeyin)—
/betOyuncu bahis yapıncaTutarı düşüntransactionId
/winTur sonuçlanıncaTutarı yatırın; kayıpta tutar 0 gelir ve cüzdan turunu kapatırtransactionId
/rollbackTur iptali, reddedilen bahis, geç gelen borçDüşülen tutarı iade edinoriginalTransactionId + reversalType
/statusCevabı kaybolan işlemde (isteğe bağlı)İşlemin durumunu söyleyin—

Altın kurallar

1 · Aynı istek, aynı sonuç

Aynı transactionId ve aynı tutar ikinci kez gelirse bakiyeye dokunmayın; ilk sonucu alreadyProcessed: true, applied: false ile döndürün. Tekrar denemelerde requestId ve nonce değişir, transactionId değişmez.

2 · Aynı kimlik, farklı tutar = reddet

Aynı transactionId farklı tutar veya oyuncu ile gelirse REJECTED / IDEMPOTENCY_CONFLICT döndürün.

3 · Bir bahis = bir sonuç

Her bahis ya bir /win ya da bir /rollback ile biter, ikisi birden asla. Başarılı /win sonrası rollback gelmez.

4 · Önce rollback gelirse

Asıl borcu hiç görmediyseniz OK ve originalFound: false dönün ve bir “mezar taşı” kaydı tutun. Sonra aynı kimlikle gelen /bet'i DEBIT_CANCELLED ile reddedin.

5 · İş reddi = HTTP 200

Yetersiz bakiye gibi iş kararları imzalı 200 + "status":"REJECTED" ile döner. 5xx veya zaman aşımı “bilinmeyen sonuç” sayılır.

7 · Kapattığınız oturum

Kapattığınız bir oturumdan gelen bahsi /bet'e REJECTED / SESSION_EXPIRED ile reddedebilirsiniz; oyuncuya sitenize dönmesi söylenir. /win veya /rollback'i asla bu nedenle reddetmeyin: bahis kabul edilmişti, sonucu veya iadesi uygulanmalı.

6 · Zaman aşımı başarısızlık değildir

Cevap gelmezse aynı transactionId ile tekrar deneriz. Uç noktanız idempotent değilse /status sunmanız önerilir; yoksa işlem elle mutabakata düşer.

İstek alanları

AlanHangi çağrılardaAçıklama
protocolVersionhepsiHer zaman 1
requestIdhepsiTaşıma kimliği; her denemede yeni
playerIdbalance, bet, win, rollbackLaunch'ta verdiğiniz externalPlayerId
gameIdbalance, bet, win, rollbacktraffic-pulse
currencybalanceCüzdan para birimi
transactionIdbet, win, rollback, statusEkonomik kimlik; tekrar denemelerde sabit
betId, gameRoundId, walletRoundIdbet, win, rollbackBahis, oyun turu ve cüzdan turu kimlikleri (v1: her bahis için bir cüzdan turu)
operatorTokenhepsi (launch'ta verildiyse)Launch'ta verdiğiniz oturum anahtarınız; verilmediyse alan yoktur
debitTransactionIdwinBu sonucun ait olduğu borç işlemi (freebet bahsinde null)
funding, promotionIdwin (freebet)Freebet bahisleri (sizin freebet'iniz veya bizim ödülümüz) için /bet gönderilmez; /win içinde funding: "FREEBET" ve promotionId gelir. Tutarı yatırın (yalnızca kâr; kayıpta 0).
roundClosedwinHer zaman true
originalTransactionId, reversalTyperollbackBET_REJECTED, ROUND_VOID veya LATE_DEBIT_COMPENSATION
amountbet, win, rollback{amount, currency, scale}: tam sayı dizesi, sizin “wire scale”inizde (bölüm 11)

Örnek: /bet

// UrbanRats → POST https://wallet.casino.example/urbanrats/bet
{
  "protocolVersion": 1,
  "requestId": "req-00000001",
  "playerId": "ext-42",
  "gameId": "traffic-pulse",
  "transactionId": "debit:demo-r000001-b0001",
  "betId": "demo-r000001-b0001",
  "gameRoundId": "demo-r000001",
  "walletRoundId": "demo-r000001:demo-r000001-b0001",
  "amount": { "amount": "100000", "currency": "EUR", "scale": 5 }      // 1,00000 EUR
}

// Sizin cevabınız (200, imzalı)
{
  "protocolVersion": 1,
  "requestId": "req-00000001",
  "status": "OK",
  "code": null,
  "transaction": {
    "transactionId": "debit:demo-r000001-b0001",
    "partnerTransactionId": "op-tx-981",
    "applied": true,                // bakiye bu denemede değişti
    "alreadyProcessed": false,
    "originalFound": null           // sadece rollback'te dolu
  },
  "balance": { "amount": "99900000", "currency": "EUR", "scale": 5 },
  "balanceVersion": "7"
}

transaction işlemin ilk sonucunu (tekrar denemelerde sabit), balance ise cevap anındaki bakiyeyi anlatır. İkisini birleştirmeyin.

Örnek: kayıp (/win, tutar 0)

{"protocolVersion":1,"requestId":"credit:demo-r000001-b0001#1","playerId":"ext-42","gameId":"traffic-pulse",
 "transactionId":"credit:demo-r000001-b0001","betId":"demo-r000001-b0001","gameRoundId":"demo-r000001",
 "walletRoundId":"demo-r000001:demo-r000001-b0001","debitTransactionId":"debit:demo-r000001-b0001",
 "amount":{"amount":"0","currency":"EUR","scale":5},"roundClosed":true}

Örnek: aynı kimlik, farklı tutar

{"protocolVersion":1,"requestId":"…#2","status":"REJECTED","code":"IDEMPOTENCY_CONFLICT",
 "transaction":{"transactionId":"debit:…","partnerTransactionId":null,"applied":false,"alreadyProcessed":false,"originalFound":null},
 "balance":{"amount":"99900000","currency":"EUR","scale":5},"balanceVersion":"7"}

/status (isteğe bağlı)

// istek  {"protocolVersion":1,"requestId":"st-0001-aaaa","transactionId":"debit:demo-r000001-b0001"}
// cevap  {"protocolVersion":1,"requestId":"st-0001-aaaa","transactionId":"debit:demo-r000001-b0001","state":"APPLIED","code":null}
         // state: APPLIED | REJECTED | REVERSED | NOT_FOUND

Cüzdan hata kodları (code alanı)

INSUFFICIENT_FUNDS · UNKNOWN_PLAYER · IDEMPOTENCY_CONFLICT · DEBIT_CANCELLED · ROUND_CLOSED · CURRENCY_MISMATCH · INVALID_SIGNATURE · REPLAYED_REQUEST · LIMIT_EXCEEDED · INTERNAL

Bizim tarafımızdaki süreler

Operatör başına üç süre anlaşılır: bahis borcu için deneme başına zaman aşımı (borç bahisler kapanmadan çözülmezse iade edilir), sonuç çağrıları için zaman aşımı ve tekrar deneme ufku (kazanç/iade çağrıları bu süre boyunca artan aralıklarla tekrar denenir, sonra alarmla elle mutabakata düşer). Art arda 5 bağlantı hatasında 5 sn boyunca çağrı göndermeyiz; bu sırada yeni bahisler sıraya alınmaz, WALLET_UNAVAILABLE ile reddedilir.

09 Wallet imzası (HMAC-SHA256)

Cüzdan çağrılarında iki taraf da imzalar: biz isteği, siz cevabı. İmzasız veya hatalı imzalı bir cevabı “bilinmeyen sonuç” sayarız.

Bizim isteğimiz

Başlıklar: x-ur-key-id, x-ur-timestamp (ms), x-ur-nonce, x-ur-signature.
Kanonik: METHOD\npath\ncanonicalQuery\ntimestampMs\nnonce\nsha256hex(body)
path = yalnızca işlem yolu (/bet), temel adresinizin yolu dahil değil; query şu an hep boş.
İmza: hex(HMAC-SHA256(bizimSır, kanonik))

Sizin cevabınız

Başlıklar: x-ur-timestamp, x-ur-signature.
Kanonik: RESPONSE\nistekNonce\ncevapTimestampMs\nsha256hex(cevapBody)
İmza: hex(HMAC-SHA256(sizinSır, kanonik))

±60 sn dışındaki zaman damgalarını ve daha önce gördüğünüz nonce'ları reddedin (INVALID_SIGNATURE / REPLAYED_REQUEST). Karşılaştırmayı sabit zamanlı yapın.

Örnek sunucu (Node.js / Express)

import express from 'express';
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';

const UR_SECRET = process.env.UR_WALLET_SECRET;         // bizim imzaladığımız sır
const MY_SECRET = process.env.OPERATOR_WALLET_SECRET;   // sizin cevap sırrınız
const sha256 = (s) => createHash('sha256').update(s, 'utf8').digest('hex');
const hmac = (k, s) => createHmac('sha256', k).update(s, 'utf8').digest('hex');
const seen = new Set();   // üretimde: TTL'li paylaşılan depo (Redis vb.)

const app = express();
app.use('/urbanrats', express.text({ type: '*/*' }));   // ham gövde lazım

app.post('/urbanrats/:op', async (req, res) => {
  const raw = req.body ?? '';
  const ts = req.get('x-ur-timestamp'), nonce = req.get('x-ur-nonce'), sig = req.get('x-ur-signature') ?? '';
  // İmzalanan path temel adresiniz değil, sadece işlem yoludur: /bet, /win …
  const canonical = ['POST', '/' + req.params.op, '', ts, nonce, sha256(raw)].join('\n');
  const expected = Buffer.from(hmac(UR_SECRET, canonical));
  const okSig = sig.length === expected.length && timingSafeEqual(Buffer.from(sig), expected);
  const fresh = Math.abs(Date.now() - Number(ts)) <= 60_000 && !seen.has(nonce);

  const body = JSON.parse(raw || '{}');
  let out;
  if (!okSig || !fresh) {
    out = { protocolVersion: 1, requestId: body.requestId ?? '', status: 'REJECTED',
            code: okSig ? 'REPLAYED_REQUEST' : 'INVALID_SIGNATURE', transaction: null, balance: null, balanceVersion: null };
  } else {
    seen.add(nonce);
    out = await handle(req.params.op, body);   // sizin cüzdan mantığınız (idempotent!)
  }

  const outRaw = JSON.stringify(out);
  const rts = String(Date.now());
  res.set('content-type', 'application/json')
     .set('x-ur-timestamp', rts)
     .set('x-ur-signature', hmac(MY_SECRET, ['RESPONSE', nonce ?? '', rts, sha256(outRaw)].join('\n')))
     .status(200).send(outRaw);
});

Test vektörleri

# İstek (bizim imzamız)
sır         ur-test-secret-do-not-use
POST /bet   timestamp 1790000000000   nonce n-00000001
body        {"amount":{"amount":"100000","currency":"EUR","scale":5},"betId":"demo-r000001-b0001","gameId":"traffic-pulse","gameRoundId":"demo-r000001","playerId":"ext-42","protocolVersion":1,"requestId":"req-00000001","transactionId":"debit:demo-r000001-b0001","walletRoundId":"demo-r000001:demo-r000001-b0001"}
sha256      d000b4ae22736bbf93baf7fe8d8054f55912f333230d7ad5d31886e0538d59b1
imza        d2c63cdcc0d149bdf36efe8c980288c9ac6b335c6ab0277470005970af898b86

# Cevap (sizin imzanız)
sır         partner-test-secret-do-not-use
istek nonce n-00000001   cevap timestamp 1790000000123
body        {"balance":{"amount":"99900000","currency":"EUR","scale":5},"balanceVersion":"7","code":null,"protocolVersion":1,"requestId":"req-00000001","status":"OK","transaction":{"alreadyProcessed":false,"applied":true,"originalFound":null,"partnerTransactionId":"op-tx-981","transactionId":"debit:demo-r000001-b0001"}}
sha256      76ef6b7890b64045171aa2d24478f2b1db95be38ddcd09759f4efd62bff98bff
imza        7e5067970199e7e077e7c74ecb5e9cfbde2f39d6b2b1fb4741ff3eb5a7e39c4c
Sık yapılan hata: reddi HTTP 5xx ile göndermek.Bir ret (yetersiz bakiye, kapalı oturum, kendi doğrulamanız) HTTP 200 ve tam imzalı gövdeyle dönmelidir: protocolVersion, aynen geri gönderilen requestId, status: "REJECTED", code, transaction: null ve x-ur-timestamp / x-ur-signature başlıkları. HTTP 5xx, zaman aşımı, imzasız ya da bozuk cevap "tutarı düşüp düşmediğinizi bilmiyoruz" demektir: bahis asla kabul edilmez, aynı transactionId ile tekrar denenir ve bahis kapanınca reversalType: LATE_DEBIT_COMPENSATION ile /rollback gönderilir — hiç düşmediyseniz OK ve originalFound: false dönün. Geliştirici portalındaki teslim kaydı sebebi gösterir (HTTP_5XX, UNSIGNED_RESPONSE, INVALID_RESPONSE, TIMEOUT).

Cüzdan adresiniz için kurallar

Yalnızca kayıtlı alan adlarına, https üzerinden, herkese açık IP'lere çağrı yaparız. Yönlendirmeleri (3xx) takip etmeyiz; adres içinde kullanıcı adı/şifre veya özel ağ adresi kabul edilmez. Çözülen IP bağlantı boyunca sabitlenir, TLS sertifikası alan adına göre doğrulanır.

10 Webhook'lar

Bahis ve promosyon olaylarını sunucunuza bildiririz. Cüzdan çağrılarının yerine geçmez; raporlama, CRM ve bildirimler içindir.

OlayAnlamı
bet.accepted / bet.rejectedBahis kabul edildi / reddedildi
bet.settledSonuç kesinleşti (kazanç veya kayıp)
bet.refundedTur iptal edildi, bahis iade edildi
bet.compensatedGeç düşülen bahis telafi edildi
reward.granted / cancelled / consumed / returnedPromosyon yaşam döngüsü
webhook.testTest gönderimi
{
  "apiVersion": "1",
  "eventId": "evt-…",
  "type": "bet.settled",
  "occurredAt": "2026-10-03T12:01:12.410Z",
  "operatorId": "op-…",
  "data": { "bet": { /* bölüm 7'deki bahis nesnesi */ } }
}

Teslim kuralları

  • En az bir kez (at-least-once): aynı olay birden fazla gelebilir. eventId ile tekilleştirin.
  • 2xx = teslim edildi. Diğer her şey (3xx dahil) 2, 4, 8… sn aralıklarla tekrar denenir; 8 denemeden sonra durdurulur. Portaldan veya API'den yeniden gönderebilirsiniz (aynı eventId ile).
  • Olaylar durum değişikliğiyle aynı veritabanı işleminde yazılır; kaybolmaz.
  • Hızlı cevap verin (işi kuyruğa atın, sonra işleyin).

İmza doğrulama

Başlıklar: x-ur-key-id, x-ur-timestamp, x-ur-event-id, x-ur-signature. Kanonik metin:

UR1-ED25519-WEBHOOK
<eventId>
<timestampMs>
<sha256hex(body)>
<keyId>
import { createHash, createPublicKey, verify } from 'node:crypto';

// publicKeyPem: GET /v1/keys içindeki WEBHOOK anahtarı (x-ur-key-id ile eşleşen)
export function verifyWebhook(rawBody, headers, publicKeyPem) {
  const ts = Number(headers['x-ur-timestamp']);
  if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > 60_000) return false;
  const canonical = ['UR1-ED25519-WEBHOOK', headers['x-ur-event-id'], String(ts),
    createHash('sha256').update(rawBody, 'utf8').digest('hex'), headers['x-ur-key-id']].join('\n');
  return verify(null, Buffer.from(canonical, 'utf8'), createPublicKey(publicKeyPem),
    Buffer.from(headers['x-ur-signature'], 'base64'));
}

Test vektörü: eventId evt-1, timestamp 1790000001000, keyId webhook-test-vector, gövde sha256 68348a6d1e720874635176e6d6128ae206d95ed3519d1c8b2665134630de3e93, imza kT+IgHMSGDzMwbd1uK9QBBzPYOryuqDROFTX9Oh34273VILwfRCTis6WYn56RdUdPwMwdlMczYRrDNVv8SfMDg== (bölüm 4'teki test anahtarının açık anahtarıyla doğrulanır).

11 Para birimi ve tutarlar

Asla ondalıklı sayı yok

Tutarlar her zaman tam sayı dizesi + scale olarak gelir. {"amount":"100000","scale":5} = 1,00000. Float kullanmayın; BigInt veya decimal kütüphanesi kullanın.

Cüzdan para birimi markanındır

Dönüştürme yapmayız. Bahis, kazanç ve promosyon hep markanın cüzdan para biriminde. Gösterim para birimi yalnızca ekrandaki yaklaşık değerdir.

Wire scale

Cüzdan çağrılarındaki ölçek (ör. EUR için 5) entegrasyonda sizinle anlaşılır. Bahis tutarı bu ölçekte tam ifade edilemiyorsa bahis reddedilir; kazanç bu ölçeğe aşağı yuvarlanır ve kayda o tutar geçer.

Kazanç = toplam dönüş

/win tutarı bahis dahil toplam dönüştür (ör. 5,00 bahis, 2,30 oran → 11,50). Kayıpta 0 gelir.

Kripto para birimleri için varlık kimliği (assetId, network) para birimi kataloğumuzdan gelir; sembolden ayrıdır. Biz saklama (custody) yapmayız.

12 Partner API hata kodları

Her hata gövdesi {"code": "...", "message": "...", "correlationId": "..."} şeklindedir; her cevap aynı kimliği x-correlation-id başlığında taşır — destek talebinde bunu iletin. “Tekrar dene” sütunu ne yapmanız gerektiğini söyler.

GÜVENLİ aynı iş kimlikleri + yeni nonce ile tekrar deneyin · DÜZELT önce sorunu giderin · HAYIR tekrar denemek sonucu değiştirmez

KodHTTPTekrarAnlamı
BAD_REQUEST400HAYIRGövde/sorgu şemaya uymuyor (bilinmeyen alanlar reddedilir)
SIGNATURE_MISSING401DÜZELTDört imza başlığı da zorunlu
NONCE_INVALID401DÜZELTNonce 16–64 karakter, [A-Za-z0-9_-]
TIMESTAMP_SKEW401DÜZELTSaatiniz 60 sn'den fazla kaymış (NTP)
KEY_UNKNOWN401DÜZELTBilinmeyen anahtar kimliği
KEY_REVOKED401DÜZELTAnahtar iptal edilmiş; güncel anahtarınızı kullanın
KEY_EXPIRED401DÜZELTDöndürme geçiş süresi bitti
SIGNATURE_INVALID401DÜZELTİmza kanonik metinle eşleşmiyor
NONCE_REPLAY401GÜVENLİNonce daha önce kullanılmış; yeni nonce ile tekrar gönderin
IP_NOT_ALLOWED403DÜZELTKaynak IP anahtarın izin listesinde değil
SCOPE_MISSING403DÜZELTAnahtarın bu rota için yetkisi yok
NOT_FOUND404HAYIRKiracınızda bulunamadı
GAME_NOT_ENTITLED403HAYIRMarkanın bu oyuna yetkisi yok
OPERATOR_SUSPENDED / BRAND_SUSPENDED403DÜZELTOperatör veya site askıya alınmış; launch reddedilir
NAVIGATION_URL_INVALID400HAYIRlobbyUrl / depositUrl / returnUrl mutlak https olmalı, kullanıcı bilgisi içermemeli
GAME_RETIRED403HAYIROyun artık sunulmuyor; kataloğu kullanın
CURRENCY_REQUIRED / CURRENCY_NOT_ALLOWED400 / 409DÜZELTAggregator: para birimi gönderin / şablonunuzda açık değil
SITE_LIMIT403DÜZELTAggregator: anlaşılan site sayısı doldu
DEMO_PLAYER_RESERVED400DÜZELTdemo: ile başlayan kimlikler eğlence moduna ayrılmış; POST /v1/demo-sessions kullanın
COUNTRY_NOT_ALLOWED403HAYIRBeyan edilen ülke bu marka/oyun için izinli değil
REAL_MONEY_UNAVAILABLE503DÜZELTSite gerçek para modunda ama seamless cüzdanınız bağlı değil; bağlanınca açılışlar devam eder
CURRENCY_MISMATCH409HAYIRPara birimi markanın cüzdan para biriminden farklı
DISPLAY_CURRENCY_NOT_ALLOWED400HAYIRGösterim para birimi marka için açık değil
ORIGIN_NOT_ALLOWED400HAYIRGömme origin'i izin listesinde değil
IDEMPOTENCY_CONFLICT409HAYIRAynı kimlik farklı içerikle tekrar kullanıldı
REWARD_NOT_CANCELLABLE409HAYIRÖdül zaten ayrılmış/kullanılmış/süresi dolmuş
CALLBACK_HOST_NOT_REGISTERED400DÜZELTCallback alan adı operatörünüz için kayıtlı değil
CALLBACK_URL_REJECTED400DÜZELTAdres kurallara uymuyor (https, herkese açık IP, yönlendirme yok)
RATE_LIMITED429GÜVENLİYavaşlayın; artan aralıklarla tekrar deneyin

13 Canlıya geçiş kontrol listesi

  • Ed25519 anahtar çiftini ürettik (veya HMAC sırrını aldık); özel anahtar / sır yalnızca sunucuda, sır yöneticisinde.
  • Bölüm 4'teki test vektörleriyle (Ed25519 veya HMAC) imzamız birebir aynı çıkıyor.
  • Launch'ta operatorToken gönderiyoruz; kapalı oturumdan gelen /bet'i SESSION_EXPIRED ile reddediyor, /win ve /rollback'i her zaman uyguluyoruz.
  • Freebet /win çağrılarını (funding: FREEBET, borç yok) doğru işliyoruz.
  • Sunucu saatimiz NTP ile senkron.
  • Launch linkini oyuncu “Oyna”ya bastığı anda üretiyoruz, saklamıyoruz.
  • Oyunu gömdüğümüz origin'leri UrbanRats'a bildirdik; iframe mobilde tam ekran.
  • Cüzdan uç noktalarımız (/balance, /bet, /win, /rollback) transactionId ile idempotent.
  • Rollback borçtan önce gelirse mezar taşı tutuyor, sonraki /bet'i DEBIT_CANCELLED ile reddediyoruz.
  • İsteğinizi HMAC ile doğruluyor, cevabımızı kendi sırrımızla imzalıyoruz; bölüm 9 vektörleri geçiyor.
  • Tutarlarda float yok; wire scale üzerinde anlaştık.
  • Webhook alıcımız imzayı doğruluyor ve eventId ile tekilleştiriyor.
  • Cüzdan ve webhook alan adlarımız https, herkese açık IP, yönlendirmesiz; UrbanRats'a kaydettirildi.
  • Geliştirici portalında DIRECT_V1 uyumluluk testini test oyuncumuzla çalıştırdık ve geçti.
Sertifikasyon ayrı bir adımdır.Uyumluluk testini geçmek “entegrasyona hazır” kaydı oluşturur. “Partner sertifikalı” kaydı yalnızca iki tarafın yetkilileri ortak kabul testini imzaladıktan sonra elle verilir.

14 Sık sorulanlar

Sonuç nasıl belirleniyor? Rastgele mi?

Hayır. Bahisler, sayılacak zaman aralığı başlamadan önce kapanır. Sonuç, o aralıkta kamerada çizgiyi ok yönünde geçen araç sayısıdır. Her turun kuralları, sayımı ve kanıt hash'leri /v1/rounds/{id}/evidence ile sorgulanabilir.

Kamerada sorun olursa ne olur?

Görüntü kesilir, donar veya sayaç durursa tur iptal edilir ve tüm bahisler /rollback (reversalType: ROUND_VOID) ile iade edilir. Kesin olmayan bir sonuçla tur sonuçlandırılmaz.

Oyuncunun kişisel bilgilerini göndermem gerekir mi?

Hayır. Sadece sizin oyuncu kimliğiniz ve beyan ettiğiniz ülke. Kayıtlarımızda oyuncu kimliği takma adla tutulur.

Tarayıcıdaki geri sayım bahsin kabulünü belirler mi?

Hayır. Bahsin kabulü, oranlar ve sonuçlar tamamen sunucu tarafında belirlenir.

Bir aggregator üzerinden bağlanabilir miyiz?

Şu an yalnızca doğrudan entegrasyon (DIRECT_V1) hazır. Bir aggregator protokolü, o aggregator'ın belgeleri ve test erişimi geldiğinde ayrıca eşlenecek.