Operator integration guide

This is how a casino (an operator) connects to JJgamesph. Your server calls our API to launch players; our server calls your wallet endpoint for every bet and result. Players load the game from our servers in an iframe on your site.

Overview

  1. A player opens Drop Ball in your lobby. Your backend calls POST /api/v1/launch with the player, their current balance and your own session id.
  2. We return a launch_url. Show it in an iframe (or redirect to it).
  3. When the player confirms a bet, we send bet.placed to your callback URL. The bet only counts if you answer 2xx.
  4. When the round is drawn we send round.win or round.lose for each bet. These are retried until you confirm them.
Amounts are strings with two decimals in the player's currency ("150.00"). Every game uses whole pesos, so the decimals are always .00.

Signing requests

Every request you make to us carries three headers:

HeaderValue
X-Operator-CodeYour operator code, e.g. nowarcasino
X-TimestampCurrent Unix time in seconds. Requests more than 5 minutes off are refused.
X-Signaturehex(HMAC-SHA256(secret, timestamp + "." + METHOD + "." + path_with_query + "." + raw_body))
// PHP
$ts   = (string) time();
$body = json_encode($payload);
$sig  = hash_hmac('sha256', $ts . '.POST./api/v1/launch.' . $body, $secret);

A bad or missing signature returns 401 with {"ok":false,"error":{"code":"INVALID_SIGNATURE"}}.

Launch a player

POST/api/v1/launch
FieldDescription
game_code"dropball" (Drop Ball), "colorgame" (Color Game) or "karera" (Karera). Each game has its own live table and its own launch_url.
player.idYour player id. Sent back to you in every callback.
player.nameShown in the game header (optional).
balanceThe player's playable balance right now, in whole units.
session_refYour own session id. Sent back as session_uuid in every callback so you can find the session.
currencyDefaults to your operator currency (PHP).
lobby_urlWhere the game's LOBBY button goes.
{
  "game_code": "dropball",
  "player": { "id": "10482", "name": "juan88" },
  "balance": 2500,
  "session_ref": "9f1c2d3e-…",
  "lobby_url": "https://bountyph.com/lobby"
}

// 200
{ "ok": true, "data": {
  "token": "q8…",
  "launch_url": "https://jjgamesph.com/play/?token=q8…",
  "expires_in_idle_minutes": 120 } }

The session stays open while the player is active and closes after 120 idle minutes.

Push a balance

POST/api/v1/sessions/{token}/balance

Send {"balance": 3000} when the player's balance changes outside the game (a deposit, another game). The game updates immediately. POST /api/v1/sessions/{token}/close ends a session.

List games

GET/api/v1/games

Returns each open game's code, name, status (enabled or maintenance), category (category and category_name), whether the play-money demo is on (demo_enabled), limits, paytable, RTP and thumbnail URL. A disabled game is left out. A game in maintenance can be launched only for player ids the provider has authorized, or through the provider's own demo.

Rounds and tickets

GET/api/v1/rounds?limit=50&before={round_id}
GET/api/v1/rounds/{round_id}
GET/api/v1/tickets?player_id=&round_id=&limit=

A ticket is one confirmed bet (one bet.placed). Tickets carry the stake per card, the win and the status: accepted, settled, rejected or refunded. Use them to reconcile with your ledger.

Credits and charges

Your account is prepaid. You buy credits from us, and each day we charge a fee on that day's play: by default 10% of GGR. GGR is stakes minus wins on settled tickets. The fee terms in your contract are shown in fee_basis and fee_rate_percent.

GET/api/v1/billing
{
  "credit_balance": 98950.00,
  "available_credit": 98570.00,
  "fee_basis": "ggr", "fee_rate_percent": 10,
  "carry_forward_loss": 0.00,
  "today": { "date": "2026-09-26", "bets": 1, "turnover": 21000, "payout": 17200, "ggr": 3800, "estimated_charge": 380.00 },
  "state": "ok",                 // ok · low · empty
  "bets_blocked": false
}
GET/api/v1/billing/statements?limit=60

One row per charged day, newest first: period, bets, turnover, payout, ggr, carry_in / carry_out (loss carried forward), fee_rate_percent, amount (the charge, negative) and balance_after.

GET/api/v1/billing/ledger?limit=100

Every credit movement: topup (credits you bought, with our payment reference), adjustment (a correction, with the reason) and charge.

Wallet callbacks

We POST JSON to the callback URL you gave us, with these headers:

HeaderValue
X-Provider-Signaturehex(HMAC-SHA256(secret, raw_body)). Reject the call if it doesn't match.
X-Provider-TimestampUnix seconds when the call was signed.
X-Provider-Codeperya
{
  "event": "bet.placed",
  "session_uuid": "9f1c2d3e-…",      // your session_ref
  "round_ref": "DB100245-8812",      // one per ticket
  "amount": "150.00",
  "currency": "PHP",
  "idempotency_key": "perya-bet-8812",
  "player_id": "10482",
  "game_code": "dropball",
  "round_id": 100245,
  "ticket_id": 8812,
  "timestamp": "2026-09-25T14:03:11.204Z"
}

Events

EventWhat to doDelivery
bet.placedDebit amount. Refuse with a 4xx if the player can't afford it.Live, the player is waiting (8 s timeout)
round.winCredit win_amount (stake included) and close the round.Queued, retried until 2xx
round.loseClose the round with no payout (win_amount is "0.00").Queued, retried until 2xx
round.refundGive back amount if you applied the matching bet.placed; otherwise just acknowledge.Queued, retried until 2xx

Each confirmed bet is its own round on your side (round_ref is unique per ticket), so a player adding to their bet during the same round sends a second bet.placed with a new round_ref. Drop Ball refs start with DB, Color Game refs with CG and Karera refs with KR; game_code says which game it is.

Your responses

Retries

Queued callbacks retry after 5 s, 15 s, 30 s, 1 min, 2 min, 5 min, 10 min, 30 min, then hourly, up to 40 attempts. A 4xx other than 408 or 429 stops the retries and the callback shows as failed in our back office, where our team can resend it.

Verifying results

Each round is announced with seed_hash = SHA-256(server_seed) before betting opens. After the draw, GET /api/v1/rounds/{id} returns the server_seed, the six cards in table order and the three balls. To recompute ball n (1, 2, 3):

for i = 0, 1, 2, …
  h = HMAC-SHA256(key = server_seed, message = "{round_id}:{n}:{i}")
  v = first 4 bytes of h, unsigned big-endian
  if v < 4294967292: ball n lands on cards[v mod 6]

The last step throws away the 4 highest values so every card is exactly equally likely.

Color Game

Color Game rounds (table colorgame-1) publish the same commitment. cards is the fixed board order YELLOW, WHITE, PINK, BLUE, RED, GREEN and balls holds the three dice. To recompute die n (1, 2, 3):

for k = 0, 1, 2, …
  h = HMAC-SHA256(key = server_seed, message = "{round_id}:die{n}:{k}")
  v = first 4 bytes of h, unsigned big-endian
  if v < 4294967292: die n shows colors[v mod 6]

Karera

Karera rounds (table karera-1) publish the same commitment, plus the odds for every bet key in ROUND_CREATED.odds before betting opens. cards holds the six horse numbers and balls the finishing order, winner first. Every random number below is draw(label, n), an unbiased whole number in [0, n):

for k = 0, 1, 2, …
  h = HMAC-SHA256(key = server_seed, message = "{round_id}:{label}:{k}")
  v = first 4 bytes of h, unsigned big-endian
  if v < floor(2^32 / n) × n: return v mod n

Strengths. Labels are str0, str1, … in call order. Shuffle the profile [300, 220, 160, 115, 75, 35] onto horses 1–6 (Fisher-Yates, j = draw(i + 1) for i = 5 down to 1), then for horses 1–6 in turn set strength = round(base × (1000 + draw(241) − 120) / 1000).

Odds (stake included, S = total strength, w = a horse's strength, rates from the game's paytable):

winner  tenths = floor(win_rtp×10 × S / (100 × w)); if tenths ≥ 100, drop the last digit
        odds = min(tenths / 10, 50)
pair    odds = floor(pair_rtp×10 × S(S−wa)(S−wb) / (1000 × wa × wb × (2S − wa − wb))), between 1 and 200

Finishing order. Labels are pos0 … pos4. For each place, draw r in [0, sum of the strengths still running) and take the first remaining horse, by number, whose running total of strengths passes r. So each horse takes a place with chance strength / remaining strength, and the odds match the real chances before rounding. The race the player watches is drawn to match this order; how the horses run on the way is cosmetic.

Bet keyWins whenReturns per peso (stake included)
W1 … W6That horse finishes firstodds.W{n} for this race
P1-2 … P5-6 (15 pairs, lower number first)Those two horses finish first and second, in either orderodds.P{a}-{b} for this race

The game listing's Karera paytable is { win_rtp, pair_rtp }: the return before rounding, 93.3 by default. Rounding the odds down brings the average to about 91.6% (winner) and 90.0% (pair); rtp shows the lower one. Payout is floor(stake × round(odds × 100) / 100).

Crowd cheers

In every game, players can tap CLAP or CHEER for a card, a colour or a horse from the moment betting opens until the draw is over on screen. Cheers are cosmetic: they carry no money, are never stored, never change the result and send no callbacks. Each table counts them (at most 5 per second per player) and shows every player at that table the crowd meter.

NowarCasino adapter

The callback body above uses the same fields as NowarCasino's generic provider endpoint (/v1/provider/{code}/callback). A ready-to-install adapter, PeryaGameProviderAdapter.php, and step-by-step install notes are in the provider repository under integrations/casinosite/.