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"). Drop Ball 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" |
| 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 game's code, name, category (category and category_name), whether the play-money demo is on (demo_enabled), limits, paytable, RTP and thumbnail URL, ready for your catalog.
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 Drop Ball round sends a second bet.placed with a new round_ref.
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.
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/.