1Как устроена сессияHow a session works
Границу ответственности проще всего увидеть по шагам.The division of labour is easiest to see step by step.
1. Игрок нажимает «Играть» у вас на сайте.
2. Вы выпускаете сессионный токен и открываете нашу ссылку в <iframe>.
3. Мы зовём POST /auth на вашем кошельке с этим токеном.
Вы отвечаете, кто это и сколько у него денег —
именно отсюда мы узнаём валюту.
4. Идёт игра. Каждая ставка — POST /bet, каждая выплата — POST /win.
Ставку, которую нужно вернуть, отменяет POST /rollback.
5. Игрок уходит. Токен перестаёт быть валидным — кроме rollback.1. The player presses Play on your site.
2. You mint a session token and open our link in an <iframe>.
3. We call POST /auth on your wallet with that token.
You answer with the player and their balance —
this is where we learn the currency.
4. The game runs. Every stake is POST /bet, every payout POST /win.
A stake that has to be given back is POST /rollback.
5. The player leaves. The token stops being valid, except for rollback.Шаги 3–5 держите вы. Всё остальное — мы.You host steps 3–5. We host everything else.
Валюту называете вы, а не URL.The currency comes from you, not from the URL.
Параметр currency в ссылке необязателен. Если его нет, мы спросим кошелёк — потому что единственный, кто точно знает, в чём номинирован счёт игрока, это тот, кто этот счёт держит.
The currency parameter is optional. Leave it out and we ask your wallet — because the only party that knows what a player's account is denominated in is the one holding it.
2Ссылка на игруThe launch link
Её вы собираете у себя и открываете в iframe.You build it on your side and open it in an iframe.
https://{launch-host}/game/{customer}/{module}/{game}/?<params>
{launch-host} |
Адрес, который мы выдаём при интеграции. Спросите его; не берите тот, что видели на тестах — это наш стенд, и он переезжает.The address we give you at integration. Ask for it; do not reuse one you were shown during testing — that is our stage and it moves. |
{customer} |
Идентификатор, который мы вам присваиваем. Ваш — exter-casino.The id we assign you. Yours is exter-casino. |
{module} / {game} |
Называют игру; оба даём мы, по каждому тайтлу.Name the game; we give you both per title. |
2.1ПараметрыParameters
| ПараметрParameter | Что кладёмWhat goes in it | |
|---|---|---|
token | обяз.required | Сессионный токен игрока. Вернём его вам в каждом вызове кошелька.The player's session token. We send it back on every wallet call. |
partnerId | opt. | Ваш идентификатор. Если на одном кошельке несколько брендов — кладите сюда бренд: мы вернём его в каждом вызове как operatorId, и один и тот же токен под двумя брендами останется двумя разными игроками.Your own id. If you run several brands off one wallet, put the brand here: we return it on every call as operatorId, so one token string under two brands stays two different players. |
currency | opt. | ISO 4217. Без него спросим кошелёк.ISO 4217. Omit it and we ask your wallet. |
language | opt. | ISO 639-1: en, ru. en-US тоже принимаем, регион сохраняем.ISO 639-1: en, ru. en-US is accepted; the region is kept. |
openType | opt. | real или fun. Всё, чего мы не узнаём, и отсутствие параметра — это реальные деньги.real or fun. Anything we do not recognise, and anything absent, means real money. |
devicetypeid | opt. | 1 web, 2 мобильный web, 3 iOS, 4 Android.1 web, 2 mobile web, 3 iOS, 4 Android. |
gameId | opt. | Ваш каталожный id игры, если он у вас есть.Your own catalogue id for the game, if you have one. |
exitURL | opt. | Куда кнопка «выход» уводит игрока.Where the game's exit button sends the player. |
depositURL | opt. | Куда его уводит кнопка кассы.Where its cashier button sends them. |
https://{launch-host}/game/exter-casino/crash/default/
?token=abc123&partnerId=198&language=en&openType=real
&devicetypeid=1&exitURL=https://casino.example/lobby2.2Три вещи, которые стоит знатьThree things worth knowing
Регистр и разделители не важны. auth_token, authToken и AUTH-TOKEN — для нас один и тот же параметр. Если ваша платформа уже отдаёт другое написание — присылайте как есть: скорее всего мы его уже принимаем, а если нет, добавить его — это правка конфигурации у нас, а не у вас.
Case and separators do not matter. auth_token, authToken and AUTH-TOKEN are the same parameter to us. If your platform already emits a different spelling, send it: we probably accept it already, and if not, accepting it is a configuration change on our side, not a change on yours.
Незнакомые параметры мы не выбрасываем. Они возвращаются в диагностике, так что если вы что-то прислали, а мы будто не заметили — видно ровно то, что пришло. Anything we do not recognise is kept, not dropped. It comes back in the diagnostics, so if you send us something and we appear to ignore it, you can see exactly what arrived.
Откройте ссылку в браузере до того, как напишете код кошелька. Мы отдадим страницу, которая перечисляет каждый присланный параметр, во что мы его сопоставили, и что ваш кошелёк ответил про токен. Это самый быстрый способ найти ссылку, собранную не на того кастомера. Open the launch link in a browser before writing any wallet code. We serve a page listing every parameter you sent, what we matched it to, and what your wallet said about the token. It is the fastest way to find a link built against the wrong customer.
В ссылке нет ваших секретов — и не должно быть.The link carries none of your secrets, and must not.
Общий ключ подписи не участвует в запуске: он живёт только на вашем бэкенде и на нашем. URL уходит в браузер игрока, где его видно целиком, поэтому единственная тайна там — сессионный токен, и он одноразовый и ваш. The shared signing key takes no part in a launch: it lives on your backend and ours. The URL goes into a player's browser where all of it is visible, so the only secret in it is the session token — single-use, and yours.
3Встраивание iframeEmbedding the iframe
Игра — обычная страница на нашем домене. От вас нужны размер и разрешение на звук.The game is an ordinary page on our domain. What it needs from you is a size and permission to make sound.
<iframe
src="https://{launch-host}/game/exter-casino/crash/default/?token=abc123&openType=real"
allow="autoplay; fullscreen"
title="Crash"
style="border:0; width:100%; height:100%; display:block">
</iframe>- Размер задаёте вы. Игра тянется на весь фрейм; своей высоты у неё нет. Дайте контейнеру реальную высоту —
height:100%внутри блока нулевой высоты даёт пустой прямоугольник. allow="autoplay"— без него в игре не будет звука до первого касания.fullscreenнужен, если даёте кнопку на весь экран.- Не ставьте
sandbox, если не уверены в наборе флагов: игре нужны скрипты, свой origin и хранилище. Урезанный sandbox ломает её молча. - Новая сессия — новый элемент. Не переиспользуйте один iframe, меняя
src: создавайте элемент заново, чтобы перезапуск был настоящей перезагрузкой, а не страницей, сохранившей прежнее состояние.
- The size is yours to set. The game fills the frame and has no height of its own. Give the container a real height —
height:100%inside a zero-height block is an empty rectangle. allow="autoplay"— without it there is no sound until the first tap.fullscreenis needed if you offer a full-screen button.- Do not set
sandboxunless you are sure of the flags: the game needs scripts, its own origin and storage. A trimmed sandbox breaks it silently. - A new session is a new element. Do not reuse one iframe by changing
src; create the element again, so reopening is a real reload rather than a page that kept its state.
Только https, с обеих сторон.https on both sides, always.
Ваша страница по https не может открыть фрейм по http — браузер режет это как mixed content, молча и без диалога. Наш адрес всегда https; следите, чтобы и ваш был. A page on https cannot open a frame on http — browsers block it as mixed content, silently and with no prompt. Our address is always https; keep yours that way too.
4Эндпоинт кошелькаThe wallet endpoint
Пять методов. Вы даёте один базовый URL, пути мы дописываем сами.Five endpoints. You give us one base URL; we append the paths.
POST {base}/auth проверить токен → кто игрок + баланс
POST {base}/getBalance прочитать баланс, ничего не двигает
POST {base}/bet списать ставку
POST {base}/win начислить выплату
POST {base}/rollback вернуть ставкуPOST {base}/auth verify a token → who the player is + balance
POST {base}/getBalance read the balance, moves nothing
POST {base}/bet debit a stake
POST {base}/win credit a payout
POST {base}/rollback give a stake backContent-Type: application/jsonв обе стороны.- Всегда отвечайте HTTP 200, в том числе на отказ. Вердикт — это поле
codeв теле. Кошелёк, который говорит «недостаточно средств» четырёхсотым, неотличим от прокси, который говорит «плохой запрос», а нам эти два случая нужно различать: от них зависит, повторять операцию или нет.
Content-Type: application/json, both ways.- Always answer HTTP 200, including for a refusal. The verdict is the
codein the body. A wallet that says "insufficient funds" with a 400 is indistinguishable from a proxy saying "bad request", and we have to tell those apart to decide whether to retry.
5ПодписьSignature
Каждый запрос несёт дайджест собственного тела.Every request carries a digest of its own body.
Auth: sha256(<сырые байты тела> + <общий ключ>) hex, нижний регистр
Auth: sha256(<raw request body bytes> + <shared key>) hex, lower case
Общий ключ обменивается при интеграции и по проводу не ходит.The shared key is exchanged at integration and never travels on the wire.
Проверяйте по тем байтам, которые пришли.Verify against the bytes you received.
Не по повторной сериализации разобранного тела. Пересборка меняет порядок ключей и пробелы, и расхождение выглядит как ошибка аутентификации — ровно до тех пор, пока кто-нибудь не заподозрит кодировщик. Прочитайте тело один раз, проверьте подпись, потом разбирайте. Not against a re-serialisation of the parsed body. Re-encoding reorders keys and changes spacing, and the resulting mismatch presents as an authentication failure for as long as nobody suspects the encoder. Read the body once, verify, then parse it.
6Запросы и ответыRequests and responses
В каждом запросе есть partnerId и token. Остальное зависит от операции.
Every request carries partnerId and token. The rest depends on the operation.
6.1Что присылаем мыWhat we send
// POST /auth и POST /getBalance { "partnerId": "198", "operatorId": "brand-7", // только если бренд был в ссылке "token": "abc123", "currency": "USD" // только getBalance; в auth его нет } // POST /bet { "partnerId": "198", "token": "abc123", "transactionId": "bet:alice:1187:0", // наш; уникален навсегда "amount": 2.50, "currency": "USD", "gameId": "555", // ВАШ каталожный id "roundId": "crash:USD:1187", "roundFinished": false } // POST /win — то же плюс ставка, которую выплачивает { "partnerId": "198", "token": "abc123", "transactionId": "take:alice:1187:0", "betTransactionId": "bet:alice:1187:0", "amount": 7.00, "currency": "USD", "gameId": "555", "roundId": "crash:USD:1187", "roundFinished": true } // POST /rollback — вернуть ставку { "partnerId": "198", "token": "abc123", // МОЖЕТ БЫТЬ ПРОСРОЧЕН — см. §8 "transactionId": "cancel:alice:1187:0", "betTransactionId": "bet:alice:1187:0", "amount": 2.50, "currency": "USD", "roundId": "crash:USD:1187", "reason": "rollback" }
// POST /auth and POST /getBalance { "partnerId": "198", "operatorId": "brand-7", // only when the link named a brand "token": "abc123", "currency": "USD" // getBalance only; absent on auth } // POST /bet { "partnerId": "198", "token": "abc123", "transactionId": "bet:alice:1187:0", // ours; unique forever "amount": 2.50, "currency": "USD", "gameId": "555", // YOUR catalogue id "roundId": "crash:USD:1187", "roundFinished": false } // POST /win — the same, plus the bet it pays out { "partnerId": "198", "token": "abc123", "transactionId": "take:alice:1187:0", "betTransactionId": "bet:alice:1187:0", "amount": 7.00, "currency": "USD", "gameId": "555", "roundId": "crash:USD:1187", "roundFinished": true } // POST /rollback — give a stake back { "partnerId": "198", "token": "abc123", // MAY BE EXPIRED — see §8 "transactionId": "cancel:alice:1187:0", "betTransactionId": "bet:alice:1187:0", "amount": 2.50, "currency": "USD", "roundId": "crash:USD:1187", "reason": "rollback" }
roundId собран из игры, валюты и номера раунда: каждая игра нумерует свои раунды сама, поэтому голый номер уникален только внутри одной игры — составной id не даёт пятому раунду двух разных игр слиться в ваших отчётах.
roundId is composed of the game, the currency and the round number. Each game numbers its own rounds, so the bare number is unique only within one game — a composed id keeps round 5 of two different games apart in your reports.
partnerId и operatorId — не два имени одного.partnerId and operatorId are not two names for one thing.
partnerId — это учётные данные: наш идентификатор в вашей системе, одно значение на всю интеграцию. operatorId приезжает с каждым запуском и говорит, к какому вашему бренду относится игрок. Он отсутствует целиком, если в ссылке бренда не было — так что при одном бренде про него можно забыть.
partnerId is a credential: our id in your system, one value for the whole integration. operatorId arrives with each launch and says which of your brands the player belongs to. It is absent entirely when the link named no brand, so with one brand you can ignore it.
6.2Что отвечаете выWhat you answer
{
"code": 0,
"balance": 997.50, // ПОСЛЕ операции
"currency": "USD",
"transactionId": "bet:alice:1187:0", // эхо нашего
"message": "", // свободный текст, для человека в логах
// только /auth:
"playerId": "p-1001",
"username": "alice"
}{
"code": 0,
"balance": 997.50, // AFTER the operation
"currency": "USD",
"transactionId": "bet:alice:1187:0", // echo ours
"message": "", // free text, for humans reading logs
// /auth only:
"playerId": "p-1001",
"username": "alice"
}
balance — баланс после операции, в той валюте, о которой спрашивали. На отказе присылайте баланс как есть, если он у вас под рукой: вам это ничего не стоит, а нам экономит вызов.
balance is the balance after the operation, in the currency you were asked about. On a refusal send the unchanged balance if you have it: it costs you nothing and saves us a call.
7КодыCodes
| КодCode | ЗначениеMeaning | Что делаем мыWhat we do |
|---|---|---|
| 0 | OK | операция примененаthe operation took effect |
| 110 | Уже примененоAlready processed | операция прошла раньше; засчитываем один разthe operation took effect earlier; we count it once |
| 1 | Общая ошибкаGeneral error | бросаем эту операциюgive up on this operation |
| 3 | Недостаточно средствInsufficient funds | говорим игре, игрок это видитtell the game; the player sees it |
| 4 | Токен не найден или просроченToken not found or expired | сессия оконченаthe session is over |
| 7 | Транзакция не найденаTransaction not found | осмысленно только на rollback — см. нижеonly meaningful on rollback — see below |
110 стоит перечитать дважды.110 is worth reading twice.
Это не синоним нуля. Он говорит, что деньги двинулись ровно один раз, а этот вызов был повтором — и именно он велит нам записать одну транзакцию вместо двух. На нём сходятся цифры оборота у вас и у нас. It is not a synonym for 0. It says the money moved exactly once and this call was a repeat — which is what tells us to record one transaction instead of two, and keeps the turnover figures on both sides agreeing.
8Пять правилThe five rules
Идемпотентность: ключ — это transactionId.Idempotency: transactionId is the key.
Повтор уже виденного не должен двигать деньги снова и должен отвечать 110 с текущим балансом. Мы повторяем: потерянный ответ не говорит нам, применили вы операцию или нет, — поэтому мы шлём тот же самый transactionId, пока не узнаем. Это самый важный пункт всего документа.
A repeat of one you have seen must not move money again, and must answer 110 with the balance as it stands. We retry: a lost response does not tell us whether you applied the operation, so we re-send the same transactionId until we know. This is the single most important clause in this document.
Rollback обязан работать на просроченном токене.A rollback must work on an expired token.
Конец сессии игрока — не причина оставить его ставку у себя. Не проверяйте токен на /rollback.
A player's session ending is not a reason to keep their stake. Do not check the token on /rollback.
Rollback того, чего у вас нет, — это 7, а не 1.A rollback of something you do not have is 7, not 1.
Семёрку мы считаем окончательным ответом: деньги в любом случае там, где должны быть, и мы останавливаемся. Единица означает «неизвестная ошибка», и мы продолжим пытаться. We treat 7 as settled — the money is where it should be either way — and stop. 1 means "unknown error" and we will keep trying.
Суммы положительные.Amounts are positive.
Направление задаёт эндпоинт: /bet списывает, /win начисляет, /rollback возвращает. Минуса не бывает никогда.
The direction is the endpoint: /bet debits, /win credits, /rollback returns. There is never a minus sign.
/win с нулевой суммой — настоящий, и его нужно записать.A zero-amount /win is real and must be recorded.
Проигранный раунд не платит ничего, и мы всё равно о нём сообщаем: он должен дойти до вашего аудита, отчётности и бонусных лестниц. Не отбрасывайте его. A round the player lost pays nothing, and we still report it so that it reaches your audit, your reporting and your bonus ladders. Do not discard it.
9ДеньгиMoney
Суммы — JSON-числа в мажорных единицах с десятичными долями валюты: 2.50, не 250 и не "2.50".
Amounts are JSON numbers in major units with the currency's decimals: 2.50, never 250 and never "2.50".
У себя мы держим деньги целым числом минорных единиц и конвертируем один раз — здесь, на краю провода. Конвертация, которая потеряла бы долю минорной единицы, падает, а не округляет: мы не станем молча срезать часть выплаты и предпочтём, чтобы интеграция громко сломалась на тестах. We hold money as an integer count of minor units and convert once, here at the wire edge. A conversion that would lose a fraction of a minor unit fails rather than rounding — we will not silently shave value off a payout, and we would rather an integration break loudly during testing.
Сделайте так же.Do the same on your side.
amount * 100 через float — это то, как 2.50 превращается в 249.
amount * 100 through a float is how 2.50 becomes 249.
10Чек-листChecklist
Интеграция готова, когда выполняется всё перечисленное.Your integration is done when all of these hold.
/authс валидным токеном возвращает игрока и баланс./authwith a valid token returns the player and a balance./betуменьшает баланс ровно на сумму./betdecreases the balance by exactly the amount./winувеличивает его ровно на сумму./winincreases it by exactly the amount.- Один и тот же
transactionIdдважды не двигает деньги дважды, и второй ответ — 110.The sametransactionIdtwice does not move money twice, and the second answer is 110. /rollbackвосстанавливает баланс./rollbackrestores the balance./rollbackна просроченном токене всё равно работает./rollbackwith an expired token still works./rollbackнеизвестногоbetTransactionIdотвечает 7./rollbackof an unknownbetTransactionIdanswers 7.- Ставка больше баланса отвечает 3, и баланс не меняется.A bet larger than the balance answers 3, and the balance is unchanged.
/winс нулевой суммой принят и записан.A zero-amount/winis accepted and recorded.2.50на проводе — это 250 минорных единиц в вашем реестре, в обе стороны.2.50on the wire is 250 minor units in your ledger, both ways.
Четвёртый и шестой — те, что падают в проде. Проверить их дёшево, а обнаружить дорого. Numbers 4 and 6 are the ones that fail in production. They are cheap to test and expensive to discover.
Есть эталонная реализация — тестируйте об неё.There is a reference implementation — test against it.
Мы отдадим эмулятор кошелька, который реализует этот документ буквально и работает строго: неверный дайджест тела он отвергает, а не терпит, — так что ошибка в подписи всплывает сейчас, а не на боевом запуске. Он же умеет ронять ответ после того, как деньги двинулись: это тот случай, который решает, спишется у игрока один раз или два, и его не даст на заказ ни одна песочница. We will give you a wallet emulator that implements this document exactly and is strict: a wrong body digest is refused rather than tolerated, so a signature mistake shows up now instead of at go-live. It also drops the response after the money has moved — the case that decides whether a player is charged once or twice, and the one no sandbox gives you on demand.
11Что прислать намWhat to send us
| Базовый URL кошелькаWallet base URL | один; пути /auth, /bet, … допишем самиone; we append /auth, /bet, … |
| Общий ключShared key | для дайджеста телаfor the body digest |
| Ваш идентификаторYour operator id | ляжет в partnerIdgoes in partnerId |
| Несколько ли брендов на одном кошелькеWhether you run several brands off one wallet | если да — кладите бренд в ссылку и ждите его обратно как operatorIdif so, put the brand on the link and expect it back as operatorId |
| ВалютыCurrencies | какие обслуживает эта интеграцияwhich ones this integration serves |
| Каталожные id игрGame ids | ваш id для каждой нашей игры, если пользуетесь своимиyour catalogue id for each of our games, if you use your own |
| Горизонт повторовRetry horizon | сколько вы держите transactionId, прежде чем считаете операцию окончательной — мы подстроимся, чтобы не сдаться раньше васhow long you keep a transactionId before you consider it settled — we match it, so that we never give up before you do |
Если что-то из этого потом поменяется — всё это конфигурация на нашей стороне. Ни один пункт не требует деплоя. If any of it changes later, all of it is configuration on our side. None of it is a deploy.