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
- A player opens Drop Ball in your lobby. Your backend calls
POST /api/v1/launchwith the player, their current balance and your own session id. - We return a
launch_url. Show it in an iframe (or redirect to it). - When the player confirms a bet, we send
bet.placedto your callback URL. The bet only counts if you answer2xx. - When the round is drawn we send
round.winorround.losefor each bet. These are retried until you confirm them.
"150.00"). Every game uses whole
pesos, so the decimals are always .00.
Signing requests
Every request you make to us carries three headers:
| Header | Value |
|---|---|
| X-Operator-Code | Your operator code, e.g. nowarcasino |
| X-Timestamp | Current Unix time in seconds. Requests more than 5 minutes off are refused. |
| X-Signature | hex(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
| Field | Description |
|---|---|
| game_code | "dropball" (Drop Ball), "colorgame" (Color Game) or "karera" (Karera). Each game has its own live table and its own launch_url. |
| player.id | Your player id. Sent back to you in every callback. |
| player.name | Shown in the game header (optional). |
| balance | The player's playable balance right now, in whole units. |
| session_ref | Your own session id. Sent back as session_uuid in every callback so you can find the session. |
| currency | Defaults to your operator currency (PHP). |
| lobby_url | Where 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
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
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
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.
- Billing day: a calendar day in Philippine time (UTC+8). A day's charge posts shortly after midnight and is deducted from your credits.
- Losing days: when players win more than they stake (negative GGR), there is no charge, and the loss is carried forward. It is offset against your next winning days before any fee is charged.
- Today:
today.estimated_chargeis the fee on today's play so far, andavailable_creditis your credits minus that estimate. - Low credit: when available credit falls below your warning level,
statebecomeslow. - No credit: at zero,
statebecomesempty. New launches are refused with402 CREDIT_EXHAUSTEDand new bets are refused, until you top up. Bets already placed still settle and pay out normally.
{
"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
}
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.
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:
| Header | Value |
|---|---|
| X-Provider-Signature | hex(HMAC-SHA256(secret, raw_body)). Reject the call if it doesn't match. |
| X-Provider-Timestamp | Unix seconds when the call was signed. |
| X-Provider-Code | perya |
{
"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
| Event | What to do | Delivery |
|---|---|---|
| bet.placed | Debit amount. Refuse with a 4xx if the player can't afford it. | Live, the player is waiting (8 s timeout) |
| round.win | Credit win_amount (stake included) and close the round. | Queued, retried until 2xx |
| round.lose | Close the round with no payout (win_amount is "0.00"). | Queued, retried until 2xx |
| round.refund | Give 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
- 2xx means done. Include the player's new balance anywhere in the JSON as
"balance"and we show it to the player straight away. - 4xx means refused and final. For
bet.placedthe bet is rejected; sayINSUFFICIENT_FUNDSanywhere in the body and the player sees "Insufficient balance". - 5xx or no answer for
bet.placed: the bet is rejected and we sendround.refundin case you applied it. - The same
idempotency_keymay arrive more than once. Return your original answer and don't apply it twice.
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 key | Wins when | Returns per peso (stake included) |
|---|---|---|
W1 … W6 | That horse finishes first | odds.W{n} for this race |
P1-2 … P5-6 (15 pairs, lower number first) | Those two horses finish first and second, in either order | odds.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/.