Developer documentation
Integrate a game in one call
A multi-tenant game API. Your server signs a launch request; we return a URL you drop in an
<iframe>. The game runs in Tunisian Dinar under an RTP you control centrally β no game code, no wallet plumbing on your side.
Quickstart
The whole integration is: sign β launch β embed. From zero to a playable game:
// 1. your credentials (from the Keys console) $API = 'https://vortexsoftgaming.online/api.php'; $KEY = 'pk_xxxxxxxxxxxxxxxxxxxx'; $SECRET = 'your_api_secret'; // 2. sign the request (sorted params, HMAC-SHA256) $p = ['api_key'=>$KEY, 'player'=>'user_42', 'game'=>'vswaysmadame']; ksort($p); $p['sign'] = hash_hmac('sha256', http_build_query($p), $SECRET); // 3. launch β get the iframe URL $ch = curl_init($API.'?action=launch'); curl_setopt_array($ch,[CURLOPT_POST=>1,CURLOPT_POSTFIELDS=>http_build_query($p),CURLOPT_RETURNTRANSFER=>1]); $res = json_decode(curl_exec($ch), true); // 4. embed echo '<iframe src="'.htmlspecialchars($res['launch_url']).'" allow="autoplay; fullscreen"></iframe>';
const crypto = require('crypto'); const API='https://vortexsoftgaming.online/api.php', KEY='pk_xxx', SECRET='your_secret'; function sign(p){ const q = Object.keys(p).sort().map(k=>`${k}=${encodeURIComponent(p[k])}`).join('&'); return crypto.createHmac('sha256',SECRET).update(q).digest('hex'); } const p = {api_key:KEY, player:'user_42', game:'vswaysmadame'}; p.sign = sign(p); const r = await fetch(API+'?action=launch',{method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:new URLSearchParams(p)}); const {launch_url} = await r.json(); // β <iframe src={launch_url}>
# sign = HMAC-SHA256 of the sorted query string, keyed by your secret SIG=$(printf 'api_key=pk_xxx&game=vswaysmadame&player=user_42' \ | openssl dgst -sha256 -hmac "your_secret" | awk '{print $2}') curl -s "https://vortexsoftgaming.online/api.php?action=launch" \ --data-urlencode "api_key=pk_xxx" \ --data-urlencode "player=user_42" \ --data-urlencode "game=vswaysmadame" \ --data-urlencode "sign=$SIG" # { "ok":true, "token":"β¦", "launch_url":"β¦/play.php?s=β¦" }
Get your keys
Open the Keys & Domains console and generate a key for your website. You choose:
| Field | Purpose |
|---|---|
| api_key | Public identifier sent with every request. |
| api_secret | Signs requests (HMAC). Server-side only β shown once, regenerate anytime. |
| domain | The site allowed to embed the game (enforced via frame-ancestors). |
| allowed_ips | Server IPs / CIDR ranges allowed to call the API. |
| currency | Player wallet currency β TND by default. |
Authentication
Money and launch calls (launch, balance, deposit) must be signed. The signature proves the request
came from you and wasn't tampered with. games needs only the api_key.
Signing algorithm
| # | Step |
|---|---|
| 1 | Collect your parameters including api_key, excluding sign. |
| 2 | Sort the keys ascending (ksort). |
| 3 | Build a URL-encoded query string: k1=v1&k2=v2β¦ |
| 4 | sign = HMAC_SHA256(query_string, api_secret) as lowercase hex. |
| 5 | POST all params plus sign. |
// worked example params = { api_key:"pk_abc", game:"vswaysmadame", player:"user_42" } sorted = "api_key=pk_abc&game=vswaysmadame&player=user_42" sign = hmac_sha256(sorted, api_secret) // e.g. 9f3c1aβ¦
bad_signature.IP whitelist & domain lock
Two independent guards, both set per operator in the Keys console:
API β IP whitelist
When enforcement is on, only the listed server IPs (or IPv4 CIDR ranges) may call launch/balance/deposit.
A blocked call returns ip_not_whitelisted and echoes the IP it saw, so you know exactly what to add.
203.0.113.7 # a single server 10.0.0.0/24 # a whole range # blank list = no restriction (open)
Iframe β domain lock
The game page ships a Content-Security-Policy: frame-ancestors header built from your domain, so the game can
only be embedded on your site. Set the domain and no other origin can iframe your sessions.
REMOTE_ADDR. Set env TRUST_XFF=1 only if a trusted proxy fronts the platform, so it reads X-Forwarded-For.Embedding the game
The launch_url is a normal page β put it in an iframe and give it room. It's fully responsive.
<!-- launch_url from the launch response --> <iframe src="β¦/play.php?s=SESSION_TOKEN" style="width:100%;height:100vh;border:0" allow="autoplay; fullscreen"></iframe>
Each launch mints a fresh session bound to that one player + game. Call launch again to switch games or start a new session.
Endpoints
Start a game session for a player and get the embed URL. Creates the player wallet on first use.
Parameters
| Param | Type | Notes |
|---|---|---|
| api_key req | string | Your key. |
| player req | string | Your user's id (any stable string). |
| game req | string | Game symbol from the catalog, e.g. vswaysmadame. |
| start_balance | number | Initial TND balance for a new player (local wallet). Default 5000. |
| sign req | string | HMAC signature. |
Response
{ "ok":true,
"token":"8c9aβ¦4ca7",
"launch_url":"https://vortexsoftgaming.online/play.php?s=8c9aβ¦4ca7" }Read a player's current TND balance.
| Param | Type | Notes |
|---|---|---|
| api_key req | string | Your key. |
| player req | string | Player id. |
| sign req | string | HMAC signature. |
{ "ok":true, "balance":4980.50, "currency":"TND" }Credit a player's local wallet (top-up / transfer model). Not used by seamless operators β there the balance lives on your side.
| Param | Type | Notes |
|---|---|---|
| api_key req | string | Your key. |
| player req | string | Player id. |
| amount req | number | TND to add (positive). |
| sign req | string | HMAC signature. |
{ "ok":true, "balance":5980.50 }List the enabled game catalog to build your lobby.
GET /api.php?action=games&api_key=pk_xxx
{ "ok":true, "games":[
{ "symbol":"vswaysmadame", "name":"Madame Destiny Megaways", "provider":"pragmatic", "category":"slots" },
{ "symbol":"vs20olympgold", "name":"Gates of Olympus Super Scatter", β¦ }
] }Wallet models
Pick per operator in the console. Both run the same RTP control.
| Model | Who holds the balance | When to use |
|---|---|---|
| local | We do. You top up via deposit and read via balance. | Fastest to integrate; demo & play-money sites. |
| seamless | You do. We call your callback_url on every bet/win. | Real-money sites with one wallet of record. |
Player balance & the insufficient-funds popup
Every one of your users is a player with a TND balance (solde). They play against it, not against unlimited demo credits β the platform enforces the wallet as the real spending limit on the server, on every spin.
| Situation | What happens |
|---|---|
| Player can afford the bet | Spin proceeds; bet + governed win are booked to the wallet. |
| Bet exceeds the balance | The spin is refused server-side β no wager is placed, the wallet can't go negative. |
| Balance too low to keep playing | The game shows a built-in βBalance insufficientβ popup that blocks play. |
| Player tops up | The popup's Reload re-checks the balance (it also polls every few seconds) and dismisses once funded β play continues. |
The popup is baked into the game page β nothing to build on your side. To fund a player, credit their wallet:
start_balance on the first launch
(default 5000 TND). After that, top up with deposit or your seamless wallet.Seamless wallet callback
If your wallet_mode is seamless, host an endpoint at your callback_url. We POST a signed debit/credit on
every bet and win; you apply it to your wallet and return the new balance.
We send (form-urlencoded)
| Field | Notes |
|---|---|
| api_key | Your key (identifies the operator). |
| player | Player id. |
| type | bet or win. |
| amount | Signed TND β bet is negative, win positive. |
| game | Game symbol. |
| token | Session token. |
| ref | Idempotency key β ignore duplicates. |
| sign | HMAC (verify with your secret, same algorithm). |
You return
{ "ok":true, "balance":4980.50 } // the player's new balance, TNDref β a retried callback must not double-charge. A runnable
reference is in integration/seamless_callback.php.RTP control
You don't set RTP per request β it's governed centrally. In the Dashboard you set a target payout ratio, volatility, bet limits and a max-win cap per operator and per game. Every booked win is scaled so the wallet's realised RTP (paid Γ· staked) tracks your target. Turn the governor off for pure pass-through. Realised RTP is shown live from the ledger.
Error codes
Errors return HTTP 200 with { "ok": false, "error": "β¦" }.
| error | Meaning & fix |
|---|---|
| bad_api_key | Unknown or disabled key. Check the value; confirm the operator is active. |
| bad_signature | Signature mismatch. Re-check the sort, the exact query string, and the secret. |
| ip_not_whitelisted | Caller IP not allowed. The response's your_ip shows what to add. |
| unknown_game | Symbol not in catalog or disabled. Use action=games. |
| game_disabled_for_operator | Game turned off for you in the dashboard. |
| upstream_unavailable | Couldn't mint the game session. Transient β retry. |
| unknown_action | Bad action value. |
Go-live checklist
| # | Step |
|---|---|
| 1 | Generate a key for your production domain in the Keys console. |
| 2 | Add your production server IPs to the whitelist and enable enforcement. |
| 3 | Store the secret in server config (never in the repo or client). |
| 4 | Choose local or seamless; if seamless, deploy your callback and verify signatures + idempotency. |
| 5 | Confirm the game embeds only on your domain (frame-ancestors) and RTP reads correctly in the dashboard. |
Live base URL https://vortexsoftgaming.online β all endpoints are relative to it.