Skip to content

Free Rounds

Availability

Free rounds are an opt-in add-on — enabled per partner after a short joint test (see checklist below). Ask your TS contact to enable it for your stage brand before integrating. Until it is enabled for your brand, no free-round traffic will ever reach your wallet API.

What a free round is in Tap Markets

Tap Markets is not a slot: a round is a tap — the player picks a price cell and the market settles it seconds later. A free round is therefore:

an operator-funded tap — the player places a trade at a stake they did not pay. A win credits them real money; a loss costs them nothing.

Because there are no reels, two things a slot takes for granted are explicit fields you choose when awarding: the stake per round (you price the promo) and the instrument scope (or all instruments).

Not supported by design: retriggers (a round awarding more rounds), lines, gamble features. Expected promo cost per round ≈ stake × RTP — ask TS for the measured payout figures before pricing a campaign.

Flow at a glance

Everything rides the wallet callbacks you already implement — free rounds add one new call from you to TS (the grant) and a few extra fields on /opentrade / /closetrade.

1. Awarding free rounds (you → TS)

POST https://<your-tap-host>/api/partner/free-rounds

Signed with the same HMAC v2 scheme you already verify on wallet callbacks (see Message Integrity) — same shared secret, same X-Sig-Version: v2, X-Timestamp, X-Nonce, X-Signature headers, just in the other direction: you sign, TS verifies.

Request:

json
{
  "userID":     "alice",
  "count":      10,
  "stake":      "1.00",
  "currency":   "USD",
  "reference":  "campaign-2026-08-abc123",
  "validUntil": "2026-08-31T23:59:59Z"
}
FieldRequiredMeaning
userIDyesyour user id — the same one you return from /authenticate
countyesnumber of free taps (1–10000)
stakeyesstake per free tap, in currency — you price the promo
currencyyesmust match the player's wallet currency
referencerecommendedyour campaign reference — makes the grant idempotent: resending the same reference returns the same grant, never a duplicate. Safe to retry on timeout.
validUntiloptionalISO-8601 UTC expiry; omit for no expiry

Response:

json
{ "status": "Ok", "freeRoundId": "fr-a1b2c3d4e5f60718", "count": 10, "stake": "1.00", "currency": "USD" }

freeRoundId is minted by TS and identifies the grant on every subsequent wallet callback. Grants can be awarded before the player's first launch.

Errors use the same status enum as the wallet callbacks (InvalidDigest, Unauthorized, plus InvalidRequest with a message for bad count/stake/expiry).

2. The free /opentrade — approve, never charge

Each free tap arrives as a normal /opentrade with two differences:

json
{
  "token": "<walletToken>",
  "amount": "0",
  "currency": "USD",
  "externalTradeType": "tap",
  "externalTradeId": "12399",
  "freeRoundId": "fr-a1b2c3d4e5f60718",
  "data": [{
    "userID": "alice",
    "instrumentID": "30102",
    "stake": "1.00",
    "payoutRatio": "1.7",
    "isFreeRound": "1",
    "tStart": "2026-08-04T12:00:00.000Z",
    "tEnd": "2026-08-04T12:00:02.000Z",
    "pMin": "65430.00",
    "pMax": "65432.00",
    "currency": "USD"
  }]
}
  • Top-level amount is "0" — reserve nothing, deduct nothing. data[0].stake still carries the promo's notional stake (for your reporting), but no money moves.
  • freeRoundId + data[0].isFreeRound: "1" mark the round.
  • Respond exactly as usual: { "balance": "<unchanged>", "currency": "USD", "status": "Ok" }.
  • Idempotency by externalTradeId, unchanged.
  • Answering InsufficientFunds/Unauthorized-class rejections on a free open is read by TS as "the operator revoked this promo": the grant is cancelled and no further free rounds are offered. The round you rejected is already in flight on TS's side, so its /closetrade will still arrive — handle it normally (it moves at most the win amount; a loss closes with 0).

3. The free /closetrade — where you pay the win

Settlement is a normal /closetrade with the free-round fields added:

json
{
  "token": "<walletToken>",
  "amount": "1.70",
  "currency": "USD",
  "externalTradeType": "tap",
  "externalTradeId": "12399",
  "freeRoundId": "fr-a1b2c3d4e5f60718",
  "freeRoundsRemaining": 6,
  "freeRoundCompleted": "0",
  "data": [{ "tradeID": "12399", "userID": "alice", "stake": "1.0000000000",
             "pnl": "0.70", "returnedAmount": "1.70", "isFreeRound": "1",
             "instrumentID": "30102", "expiryPrice": "65431.50", "touchPrice": "65431.40",
             "payout": "70", "rebate": "0" }]
}

The "always update balance from amount" rule from Wallet / Trade Callbacks applies unchanged, and does the right thing automatically:

OutcomeamountYour action
Winfull return (stake × payoutRatio, e.g. 1.70)credit it whole — the unpaid stake is not deducted (slot convention)
Loss0credit nothing; the round still closes — a 0-amount free close is normal, not an error
Cancelledarrives as a cancellation, not a closeno-op, and the round goes back onto the grant

Extra fields:

FieldMeaning
freeRoundsRemainingrounds left on the grant after this one
freeRoundCompleted"1" on the grant's last round — the grant is then exhausted

Values are snapshotted at settle time: a retried delivery repeats identical values under the identical externalTradeId.

Funding and reporting

  • The operator funds free-round wins (your promo liability) — the same convention as slot free spins. Confirm this matches your accounting before go-live.
  • Both callbacks carry isFreeRound: "1", so you can split promo turnover from real-money turnover in your reports. TS excludes free-round volume from RTP/GGR statistics on its side the same way.

Expiry & lifecycle

SituationBehaviour
all rounds usedgrant exhausted on the last close (freeRoundCompleted: "1")
validUntil passesunused rounds silently expire — no wire traffic
you reject a free /opentradegrant cancelled (promo revoked); the in-flight round still settles, no further free rounds
round opened just before expiryalways settles — expiry is checked at open only
grant currency ≠ wallet currencyfree tap refused; re-award with the right currency

Unused rounds are never converted to cash.

Test checklist

Run on stage with TS before enabling in production:

  1. Grant 3 rounds @ 1.00 → note freeRoundId; resend the same reference → same freeRoundId, still 3 rounds.
  2. Launch → banner shows 3 @ 1.00.
  3. Free tap → /opentrade amount "0" received, player balance unchanged.
  4. Win → /closetrade amount "1.70" → balance +1.70 exactly.
  5. Loss → /closetrade amount "0" → balance unchanged, round consumed.
  6. Last round → freeRoundsRemaining: 0, freeRoundCompleted: "1".
  7. Short-expiry grant → let it lapse → no wire traffic, rounds gone.
  8. Reject one free /opentrade → grant cancelled, remaining rounds gone.