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.
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ön | Ne için | Güvenlik |
|---|---|---|
| Siz → UrbanRats Partner API | Oyun listesi, oturum açma, bahis/tur sorgulama, promosyon, webhook yönetimi | Ed25519 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, iade | HMAC-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.
Sunucunuz imzalı POST /v1/launch-sessions çağırır, launchUrl alır.
Sayfanızda launchUrl'i iframe ile açarsınız (60 sn içinde, tek kullanımlık).
Oyuncu bahis yapar; biz cüzdanınıza /bet gönderir, tutarı düşmenizi isteriz.
Tur bitince /win (kayıpta 0) veya iptalde /rollback gelir; webhook da gönderilir.
03 Ortamlar ve anahtarlar
| Ortam | API adresi | Not |
|---|---|---|
| Sandbox | https://api.urbanrats.com | Demo 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 bildirilecek | Ortak 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
- 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).
- Markalarınıza Chaos Lane (
gameId: traffic-pulse) yetkisi verilir. - 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.
- 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
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ık | Değer |
|---|---|
x-ur-key-id | Anahtar kimliğiniz |
x-ur-timestamp | Unix zamanı, milisaniye. Saatimizden en fazla ±60 sn farklı olabilir (NTP kullanın). |
x-ur-nonce | 16–64 karakter, [A-Za-z0-9_-]. Her istekte yeni; aynı nonce ikinci kez kabul edilmez. |
x-ur-signature | Aş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.
Ö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;
}
# pip install cryptography requests
import base64, hashlib, json, secrets, time
from urllib.parse import urlsplit, parse_qsl, quote
import requests
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def enc(s): # JavaScript encodeURIComponent ile aynı
return quote(s, safe="-_.!~*'()")
def canonical_query(query):
pairs = sorted((enc(k), enc(v)) for k, v in parse_qsl(query, keep_blank_values=True))
return "&".join(f"{k}={v}" for k, v in pairs)
def urbanrats(method, url, body_obj, key_id, private_key_pem):
body = json.dumps(body_obj, separators=(",", ":")) if body_obj is not None else ""
u = urlsplit(url)
ts = str(int(time.time() * 1000))
nonce = secrets.token_urlsafe(18)
canonical = "\n".join(["UR1-ED25519-REQUEST", method.upper(), u.path, canonical_query(u.query),
hashlib.sha256(body.encode()).hexdigest(), ts, nonce, key_id])
key = load_pem_private_key(private_key_pem.encode(), password=None)
sig = base64.b64encode(key.sign(canonical.encode())).decode()
headers = {"content-type": "application/json", "x-ur-key-id": key_id, "x-ur-timestamp": ts,
"x-ur-nonce": nonce, "x-ur-signature": sig}
r = requests.request(method, url, data=body.encode() if body else None, headers=headers, timeout=10)
r.raise_for_status()
return r.json()
// PHP 8 + ext-sodium. Ed25519 tohumunu (32 bayt) PEM'den alıp sodium anahtarına çevirin.
function urCanonicalQuery(string $q): string {
if ($q === '') return '';
$pairs = [];
foreach (explode('&', $q) as $p) {
[$k, $v] = array_pad(explode('=', $p, 2), 2, '');
$enc = fn($s) => strtr(rawurlencode(urldecode($s)), ['%21'=>'!','%2A'=>'*','%27'=>"'",'%28'=>'(','%29'=>')']);
$pairs[] = [$enc($k), $enc($v)];
}
usort($pairs, fn($a, $b) => strcmp($a[0], $b[0]) ?: strcmp($a[1], $b[1]));
return implode('&', array_map(fn($p) => "{$p[0]}={$p[1]}", $pairs));
}
function urSign(string $method, string $url, string $body, string $keyId, string $seed32): array {
$u = parse_url($url);
$ts = (string) (int) floor(microtime(true) * 1000);
$nonce = rtrim(strtr(base64_encode(random_bytes(18)), '+/', '-_'), '=');
$canonical = implode("\n", ['UR1-ED25519-REQUEST', strtoupper($method), $u['path'],
urCanonicalQuery($u['query'] ?? ''), hash('sha256', $body), $ts, $nonce, $keyId]);
$kp = sodium_crypto_sign_seed_keypair($seed32);
$sig = base64_encode(sodium_crypto_sign_detached($canonical, sodium_crypto_sign_secretkey($kp)));
return ['x-ur-key-id' => $keyId, 'x-ur-timestamp' => $ts, 'x-ur-nonce' => $nonce, 'x-ur-signature' => $sig];
}
// $seed32: PKCS#8 PEM'in base64 gövdesi çözüldüğünde son 32 bayt.
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.
/v1/launch-sessionslaunch:createGövde katı (strict) şemadır: bilinmeyen bir alan gönderirseniz 400 BAD_REQUEST alırsınız.
| Alan | Tip | Açıklama |
|---|---|---|
brandId * | string | Kurulumda tanımlanan marka kimliğiniz |
externalPlayerId * | string | Sizin oyuncu kimliğiniz. Kişisel veri (ad, e-posta) göndermeyin; sabit, anlamsız bir kimlik yeterli. |
gameId * | string | traffic-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-2 | Bu oyuncunun yetki alanı (ör. DE, TR); her açılışta oyuncuya göre gönderilir, sabit bir ayar değildir. KYC sorumluluğu sizdedir. |
currency | string | İsteğe bağlı doğrulama. Markanın cüzdan para biriminden farklıysa CURRENCY_MISMATCH. |
displayCurrency | string | Yalnızca gösterim için, marka için açık olmalı |
language | xx / xx-XX | Arayüz dili, ör. tr, en-GB |
allowedOrigin | origin | Oyunu gömeceğiniz sayfanın origin'i (ör. https://casino.example). Markanın izin listesinde olmalı. |
operatorToken | string (ö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. |
device | desktop / mobile | Cihaz türü; biz tahmin etmeyiz. |
lobbyUrl, depositUrl, returnUrl | URL | Mutlak 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.
/v1/demo-sessionslaunch:createAlanlar: 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" }
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
brandIdolarak kendi site kimliğinizi, ayrıca oyuncunun cüzdan para birimini (currency) ve isteğe bağlısiteNamegö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), cevaptabrandIdolarak döner. countryyine her oyuncunun kendi yetki alanıdır ve şablondaki ülkelere göre kontrol edilir.externalPlayerIdtüm siteleriniz arasında benzersiz olmalı (ör. site kimliğiyle önekleyin); cüzdan çağrılarında size aynen geri gelir.- Varsayılan
DECLAREDpolitikası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,USDTyalnı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/sessioniçindemoneyMode:REAL,DEMOveyaFUN. - Katalog:
GET /v1/gamesiçinde site başınaentitlements[].moneyMode; sitelerinizden en az biri gerçek paradaysarealMoneyEnabled: 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
countrygeç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_UNAVAILABLEdöner; hiçbir şey oyun parasına kaymaz.
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-state | ready, error, session-expired, round.phase, bet.accepted, bet.rejected, bet.settled, balance.changed |
*) 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.
/v1/gamesgames:readYetkili olduğunuz oyunlar ve her markanın izinli ülkeleri. İmzasız çağrıda herkese açık katalog döner.
/v1/bets · /v1/bets/{betId}bets:readFiltreler: 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.
/v1/rounds/{id} · /v1/rounds/{id}/evidencerounds:readTurun 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.
/v1/rewards · /v1/rewards/{id}/cancel · GET/v1/rewardsrewards:writeOyuncuya 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"
}
}
/v1/webhooks · GET/v1/webhookswebhooks:manageWebhook 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.
/v1/keysherkese açıkAçı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
/v1/players/{externalPlayerId}/statsbets:readOyuncunun 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": [ /* … */ ] }
/v1/leaderboards · /v1/leaderboards/{id}games:readMarkaları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ç nokta | Ne zaman | Sizin yapacağınız | İdempotent anahtar |
|---|---|---|---|
/balance | Oyun açılırken, gerektiğinde | Bakiyeyi döndürün (değiştirmeyin) | — |
/bet | Oyuncu bahis yapınca | Tutarı düşün | transactionId |
/win | Tur sonuçlanınca | Tutarı yatırın; kayıpta tutar 0 gelir ve cüzdan turunu kapatır | transactionId |
/rollback | Tur iptali, reddedilen bahis, geç gelen borç | Düşülen tutarı iade edin | originalTransactionId + reversalType |
/status | Cevabı 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ı
| Alan | Hangi çağrılarda | Açıklama |
|---|---|---|
protocolVersion | hepsi | Her zaman 1 |
requestId | hepsi | Taşıma kimliği; her denemede yeni |
playerId | balance, bet, win, rollback | Launch'ta verdiğiniz externalPlayerId |
gameId | balance, bet, win, rollback | traffic-pulse |
currency | balance | Cüzdan para birimi |
transactionId | bet, win, rollback, status | Ekonomik kimlik; tekrar denemelerde sabit |
betId, gameRoundId, walletRoundId | bet, win, rollback | Bahis, oyun turu ve cüzdan turu kimlikleri (v1: her bahis için bir cüzdan turu) |
operatorToken | hepsi (launch'ta verildiyse) | Launch'ta verdiğiniz oturum anahtarınız; verilmediyse alan yoktur |
debitTransactionId | win | Bu sonucun ait olduğu borç işlemi (freebet bahsinde null) |
funding, promotionId | win (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). |
roundClosed | win | Her zaman true |
originalTransactionId, reversalType | rollback | BET_REJECTED, ROUND_VOID veya LATE_DEBIT_COMPENSATION |
amount | bet, 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
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.
| Olay | Anlamı |
|---|---|
bet.accepted / bet.rejected | Bahis kabul edildi / reddedildi |
bet.settled | Sonuç kesinleşti (kazanç veya kayıp) |
bet.refunded | Tur iptal edildi, bahis iade edildi |
bet.compensated | Geç düşülen bahis telafi edildi |
reward.granted / cancelled / consumed / returned | Promosyon yaşam döngüsü |
webhook.test | Test 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.
eventIdile 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ıeventIdile).- 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
| Kod | HTTP | Tekrar | Anlamı |
|---|---|---|---|
BAD_REQUEST | 400 | HAYIR | Gövde/sorgu şemaya uymuyor (bilinmeyen alanlar reddedilir) |
SIGNATURE_MISSING | 401 | DÜZELT | Dört imza başlığı da zorunlu |
NONCE_INVALID | 401 | DÜZELT | Nonce 16–64 karakter, [A-Za-z0-9_-] |
TIMESTAMP_SKEW | 401 | DÜZELT | Saatiniz 60 sn'den fazla kaymış (NTP) |
KEY_UNKNOWN | 401 | DÜZELT | Bilinmeyen anahtar kimliği |
KEY_REVOKED | 401 | DÜZELT | Anahtar iptal edilmiş; güncel anahtarınızı kullanın |
KEY_EXPIRED | 401 | DÜZELT | Döndürme geçiş süresi bitti |
SIGNATURE_INVALID | 401 | DÜZELT | İmza kanonik metinle eşleşmiyor |
NONCE_REPLAY | 401 | GÜVENLİ | Nonce daha önce kullanılmış; yeni nonce ile tekrar gönderin |
IP_NOT_ALLOWED | 403 | DÜZELT | Kaynak IP anahtarın izin listesinde değil |
SCOPE_MISSING | 403 | DÜZELT | Anahtarın bu rota için yetkisi yok |
NOT_FOUND | 404 | HAYIR | Kiracınızda bulunamadı |
GAME_NOT_ENTITLED | 403 | HAYIR | Markanın bu oyuna yetkisi yok |
OPERATOR_SUSPENDED / BRAND_SUSPENDED | 403 | DÜZELT | Operatör veya site askıya alınmış; launch reddedilir |
NAVIGATION_URL_INVALID | 400 | HAYIR | lobbyUrl / depositUrl / returnUrl mutlak https olmalı, kullanıcı bilgisi içermemeli |
GAME_RETIRED | 403 | HAYIR | Oyun artık sunulmuyor; kataloğu kullanın |
CURRENCY_REQUIRED / CURRENCY_NOT_ALLOWED | 400 / 409 | DÜZELT | Aggregator: para birimi gönderin / şablonunuzda açık değil |
SITE_LIMIT | 403 | DÜZELT | Aggregator: anlaşılan site sayısı doldu |
DEMO_PLAYER_RESERVED | 400 | DÜZELT | demo: ile başlayan kimlikler eğlence moduna ayrılmış; POST /v1/demo-sessions kullanın |
COUNTRY_NOT_ALLOWED | 403 | HAYIR | Beyan edilen ülke bu marka/oyun için izinli değil |
REAL_MONEY_UNAVAILABLE | 503 | DÜZELT | Site gerçek para modunda ama seamless cüzdanınız bağlı değil; bağlanınca açılışlar devam eder |
CURRENCY_MISMATCH | 409 | HAYIR | Para birimi markanın cüzdan para biriminden farklı |
DISPLAY_CURRENCY_NOT_ALLOWED | 400 | HAYIR | Gösterim para birimi marka için açık değil |
ORIGIN_NOT_ALLOWED | 400 | HAYIR | Gömme origin'i izin listesinde değil |
IDEMPOTENCY_CONFLICT | 409 | HAYIR | Aynı kimlik farklı içerikle tekrar kullanıldı |
REWARD_NOT_CANCELLABLE | 409 | HAYIR | Ödül zaten ayrılmış/kullanılmış/süresi dolmuş |
CALLBACK_HOST_NOT_REGISTERED | 400 | DÜZELT | Callback alan adı operatörünüz için kayıtlı değil |
CALLBACK_URL_REJECTED | 400 | DÜZELT | Adres kurallara uymuyor (https, herkese açık IP, yönlendirme yok) |
RATE_LIMITED | 429 | GÜ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
operatorTokengönderiyoruz; kapalı oturumdan gelen/bet'iSESSION_EXPIREDile reddediyor,/winve/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)transactionIdile idempotent. - Rollback borçtan önce gelirse mezar taşı tutuyor, sonraki
/bet'iDEBIT_CANCELLEDile 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
eventIdile 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.
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.