Free Rounds (Free Spins)
Free rounds (also called free spins) let a player spin a game without staking their own cash — the stake is funded by a grant you issue, and any winnings are credited to the player.
There are two sides to free rounds:
- Issuing — you call
POST /v1/free-roundsto grant a player free rounds on one or more games (see Issuing free rounds below). - Receiving — the resulting wallet events arrive at your existing callback endpoint as ordinary
bet/wincallbacks carrying one extra signal,is_free: true. There is no new endpoint to implement on your side to receive them.
If you have not yet integrated the standard wallet callback, read Wallet callbacks first. This guide describes what is different for free rounds.
Issuing free rounds
Section titled “Issuing free rounds”Issue a grant with POST /v1/free-rounds (API reference). count is per game — games: [A, B] with count: 10 issues 10 rounds on each (20 total). The grant is not tied to a session; each game must support free rounds and passes the same jurisdiction check as a launch.
Check free_rounds_support on the game (from GET /v1/games or GET /v1/games/{id}) before issuing — a game where it is false is rejected at grant time.
curl -X POST https://api.aggregator.gg/v1/free-rounds \ -H "Authorization: Bearer sk_live_xxx" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "provider_code": "truelabs", "player_id": "player-123", "games": ["550e8400-e29b-41d4-a716-446655440000"], "count": 10, "currency": "EUR", "country": "DE", "coin_level": 1, "start_at": "2026-07-01T00:00:00Z", "end_at": "2026-07-10T00:00:00Z" }'- Scope + idempotency. Requires the
writescope and anIdempotency-Keyheader — the same key + same body replays the cached result; same key + different body →409. - Stake. Provide exactly one of
coin_levelorbet_amount. Which one — and the valid values — depend on the game, so callGET /v1/games/{game_id}/stakes?currency=first and branch on themodeit returns:enumerated→ we hold the valid list; pick one of the listedstakesand send the field named bystake_field(coin_levelfor TrueLabs,bet_amountfor Apparat).provider_validated→ we don’t have the list for this currency, sostakesis empty; send anybet_amountand the provider validates it on its side at issue time (we mirror its accept/reject).unsupported→ the game has no free-round stake model.
- Response. You get back a
grant_idand a per-gamestatus. Manage the grant later withGET /v1/free-rounds(list) andPOST /v1/free-rounds/{grant_id}/cancel(cancel unplayed rounds). - Availability. Free rounds must be enabled for the provider on your account first; until then issuance returns
403.
The rest of this guide covers the receiving side — how the spins reach your wallet endpoint.
What changes for a free round
Section titled “What changes for a free round”A free-round callback is the standard forward payload (see Wallet callbacks → Forward Payload) plus these fields:
| Field | Type | Description |
|---|---|---|
is_free |
boolean |
true when this callback belongs to a free round. This is the field you branch on. Absent or false on ordinary cash play. |
free_round_grant_id |
string |
The grant_id of the free-round grant this spin belongs to — use it to group a campaign’s activity. May be empty if the spin is not tied to a specific grant. |
free_round_kind |
string | null |
The kind of free round, as you set it when issuing (freespin / freeticket / voucher). |
action, transaction_type, transaction_id, amount, currency, player_id, provider_code, and session_id carry exactly the same meaning and units as on a normal callback — amount and balance are integers in minor units (details). Branch on transaction_type and is_free, never on action.
Balance handling
Section titled “Balance handling”| Callback | is_free |
What to do |
|---|---|---|
bet |
true |
Do not touch the real-money balance. The grant funds the stake. Record the spin for reporting if you wish; amount is the nominal stake, not a charge. |
win |
true |
Credit the player by amount, exactly like a normal win. Optionally book it as bonus funds subject to your wagering / withdrawability rules (see below). |
bet / win |
absent / false |
Ordinary cash play — debit / credit as documented in Wallet callbacks. |
The net effect of a correctly handled free round is that the player risks nothing (free bet is not debited) and keeps the winnings (free win is credited).
// pseudo-code — the only change vs cash play is the is_free guard on betif (callback.transaction_type === "bet" && callback.is_free) { // grant-funded — DO NOT debit. Optionally log for reporting. balance = currentBalance(callback.player_id);} else if (callback.transaction_type === "bet") { balance = debit(callback.player_id, callback.amount); // normal cash bet} else if (callback.transaction_type === "win") { balance = credit(callback.player_id, callback.amount); // free or cash — credit either way}Winnings and withdrawability
Section titled “Winnings and withdrawability”is_free exists so you can apply your own promotional accounting. Free-round winnings are commonly treated as bonus funds subject to a wagering requirement before withdrawal. Whether you credit them as cash or as a restricted bonus balance is entirely your decision — The Aggregator does not impose a model. If you do nothing special, crediting them as cash is valid and the player simply keeps the winnings.
Provider differences — don’t assume a bet always precedes a win
Section titled “Provider differences — don’t assume a bet always precedes a win”The callback sequence varies by game. Your handler must cope with both shapes, which is why you branch on is_free per callback rather than assuming a bet always arrives before a win:
- Both legs. Some free rounds produce a free
bet(is_free: true, nominal stake) followed, on a winning spin, by a freewin(is_free: true). - Win only. Others settle as a single free
win(is_free: true) carrying the total winnings, withfinished: true— no freebetis sent. A zero-win round still settles (you may receive awinwithamount: 0).
Treat each callback independently by transaction_id and is_free. Do not require a matching free bet before accepting a free win.
Example payloads
Section titled “Example payloads”A free spin’s bet leg (per-spin provider) — record, do not debit:
{ "action": "BET", "transaction_type": "bet", "is_free": true, "free_round_grant_id": "9f1c0b3a-2d4e-4a6b-8c0d-1e2f3a4b5c6d", "free_round_kind": "freespin", "transaction_id": "provider-unique-tx-id", "amount": 20, "currency": "EUR", "player_id": "player-123", "provider_code": "apparat", "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}The free win — credit the player:
{ "action": "WIN", "transaction_type": "win", "is_free": true, "free_round_grant_id": "9f1c0b3a-2d4e-4a6b-8c0d-1e2f3a4b5c6d", "free_round_kind": "freespin", "transaction_id": "provider-unique-tx-id-2", "amount": 450, "currency": "EUR", "player_id": "player-123", "provider_code": "truelabs", "finished": true, "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}Both are signed with your callback_secret exactly like any other callback — verify X-SIGNATURE against the raw body before processing (Signature verification).
Idempotency
Section titled “Idempotency”No change from the standard contract: use transaction_id as your idempotency key (treat it as an opaque string) and return the same response on a duplicate. A retried free-round settlement carries the same transaction_id, so it de-duplicates naturally. See Idempotency.
Go-Live Checklist — free rounds
Section titled “Go-Live Checklist — free rounds”Complete these in addition to the standard wallet-callback go-live checklist.
- Your handler reads
is_freeon every callback and branches on it. - A
betwithis_free: truedoes not debit the player’s real-money balance. - A
winwithis_free: truecredits the player (as cash, or as bonus funds per your promo rules). - You do not require a preceding free
betbefore accepting a freewin(some free rounds settle with only a win). - You handle a free
winwithamount: 0(a losing free-round batch still settles). - Idempotency keys off
transaction_idfor free rounds too (treat it as opaque). - You verified the signature on a free-round callback with a known body + secret pair.