Skip to Content
WalletSeamless WalletPerform TransactionOverview

perform-transaction

The perform-transaction endpoint is the core of wallet integration. It handles all financial operations including deposits, withdrawals, and rollbacks for bets, settlements, and bonus operations.

POST /api/v2/wallet/{playerId}/transactions

Key points:

  • Must guarantee idempotency (same transaction ID = same response)
  • Get balance is called after each transaction by default

See Integration for the rules shared by all three wallet endpoints.


Quick Reference

Transaction Types Overview

TypeDirectionRequiredCommon Use Cases
withdrawalMoney outYesBets, cancel settlements, negative adjustments
depositMoney inYesSettlements, winnings, positive adjustments
rollbackReverseYesBet placement cancellations only
Note

Bonus Integration Transaction types award, release, and retract are only required if you integrate with the GR8 Tech Bonus System.


Endpoint Configuration

Warning

Request Timeout: 2 seconds All transaction requests have a 2-second timeout. Your endpoint must respond within this time limit, otherwise:

Ensure your wallet system can process requests quickly and efficiently.

Request Parameters

ParameterLocationTypeRequiredDescription
playerIdPathstringYesUnique player identifier
X-BrandHeaderstringYesTenant identifier representing site and operator (configured during setup, cannot be changed). Get this value from your GR8 Tech manager.
X-Operator-IdHeaderstringYesTenant identifier representing site and operator (configured during setup, cannot be changed). Get this value from your GR8 Tech manager.

Request Structure

Core Fields

FieldTypeRequiredDescription
idstringYesUnique transaction identifier. Used for idempotency - same ID = same result
currencystringYesISO 4217 currency code (USD, EUR, KES, etc.)
platformstringYesPlatform identifier (typically “sport” or “CRM”)
typestringYesTransaction type: deposit, withdrawal, rollback, award, release, retract
initiatedAtstringYesISO 8601 timestamp when transaction was initiated
createdAtstringYesISO 8601 timestamp when transaction was created

Context Object

FieldTypeRequiredDescription
productstringYesProduct identifier (sportsbook, crm)
reasonstringYesTransaction reason code (see Reference)
channelstringNoBet related channel for bet transaction (placement + settlement)
betIdstringNoBet identifier (present for bet-related transactions)
parentIdstringNoParent transaction ID. Sent only on rollback transactions, where it always points to the bet placement being reversed.
sessionIdstringNoPlayer session identifier. Present in bet placement transactions when the operator passes a sessionId when starting the session — in an iFrame integration, as a query parameter of the iFrame URL
languagestringNoPlayer language at the moment of bet placement (taken from the X-Language header of the placement request). Sent only on bet placement transactions — reason = bet, bet with tax, or freebet. Use it to localize the error message when rejecting a bet with decline.client.custom.
SportBonusOfferIdstringNoActivity ID from Journey Builder (for bonus transactions)
SportBonusPlayerOfferIdstringNoPlayer-specific bonus activation ID (for bonus transactions)
extrasobjectNoExtended bet details — bet type, total odds, and per-item event, market and outcome data. Disabled by default, enabled per brand on request. See Extended Bet Details

Amount Breakdown

FieldTypeDescription
cashstringCash amount as decimal string (e.g., “100.50”)
bonusstringBonus amount as decimal string
lockedstringLocked amount as decimal string (used for wagering requirements)
retractstringRetract amount as decimal string (bonus funds removed/excess)

Request Schema

Full Request Schema (JSON Schema)

{ "type": "object", "properties": { "currency": { "type": "string", "description": "ISO 4217 currency code (e.g., USD, EUR, KES)" }, "platform": { "type": "string", "description": "Platform identifier (e.g., sport, CRM)" }, "id": { "type": "string", "description": "Unique transaction identifier (used for idempotency)" }, "type": { "type": "string", "enum": ["deposit", "withdrawal", "rollback", "award", "release", "retract"], "description": "Transaction type" }, "initiatedAt": { "type": "string", "format": "date-time", "description": "When transaction was initiated (ISO 8601)" }, "createdAt": { "type": "string", "format": "date-time", "description": "When transaction was created (ISO 8601)" }, "context": { "type": "object", "properties": { "channel": { "type": "string", "description": "Bet related channel for bet transaction (placement + settlement)" }, "product": { "type": "string", "description": "Product identifier (e.g., sportsbook, crm)" }, "reason": { "type": "string", "description": "Transaction reason (see Transaction Types Reference section)", }, "betId": { "type": "string", "description": "Bet identifier (for bet-related transactions: bet, settle, resettle...)" }, "parentId": { "type": "string", "description": "Parent transaction ID (for rollbacks)" }, "SportBonusOfferId": { "type": "string", "description": "Bonus offer ID (Activity ID from Journey Builder)" }, "SportBonusPlayerOfferId": { "type": "string", "description": "Player-specific bonus activation ID" }, "sessionId": { "type": "string", "description": "Player session identifier. Present in bet placement transactions when the operator passes a sessionId when starting the session (in an iFrame integration, as a query parameter of the iFrame URL)" }, "language": { "type": "string", "description": "Player language at the moment of bet placement (taken from the X-Language header of the placement request). Sent only on bet placement transactions (reason = bet, bet with tax, or freebet). Use it to localize the error message when rejecting a bet with decline.client.custom." }, "extras": { "type": "object", "description": "Extended bet details. Disabled by default; enabled per brand on request. See the Extended Bet Details page for the full schema." } }, "required": ["product", "reason"], "additionalProperties": true }, "amountBreakdown": { "type": "object", "properties": { "cash": { "type": "string", "description": "Cash amount (decimal string)" }, "bonus": { "type": "string", "description": "Bonus amount (decimal string)" }, "locked": { "type": "string", "description": "Locked amount (decimal string)" }, "retract": { "type": "string", "description": "Retract amount (decimal string)" } }, "additionalProperties": true } }, "required": ["currency", "platform", "id", "type", "amountBreakdown", "initiatedAt", "createdAt", "context"] }

Basic Request Example

Bet Placement Request
{ "id": "5f338453-6172-491f-b8f0-27e382ea05eb", "currency": "USD", "platform": "sport", "type": "withdrawal", "createdAt": "2025-01-29T00:34:25Z", "initiatedAt": "2025-01-29T00:34:25Z", "context": { "channel": "DESKTOP_AIR_PM", "product": "sportsbook", "reason": "bet", "betId": "5f338453-6172-491f-b8f0-27e382ea05eb", "sessionId": "some-session-id" }, "amountBreakdown": { "cash": "13.9", "locked": "0", "bonus": "0" } }

For more examples, see Transaction Examples.


Response Structure

Warning

Idempotency Requirements Your wallet MUST implement idempotency to handle duplicate transaction requests:

  • Store transaction snapshots for at least 3 days
  • Return the same response for duplicate transaction IDs (same id field)
  • Set alreadyProcessed: true for duplicate requests
  • Store snapshots even for failed transactions (e.g., decline.lowbalance)

This ensures system reliability during network issues and retries.

Success Response

All successful transactions return HTTP 200 with the following structure:

Full Response Schema (JSON Schema)

{ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "currency": { "type": "string" }, "platform": { "type": "string" }, "id": { "type": "string" }, "type": { "type": "string", "enum": ["deposit", "withdrawal", "rollback", "award", "release", "retract"] }, "balances": { "type": "object", "description": "Nested structure: platform -> product -> currency -> balance details", "patternProperties": { "^.*$": { "type": "object", "patternProperties": { "^.*$": { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "cash": { "type": "string" }, "bonus": { "type": "string" }, "locked": { "type": "string" }, "retract": { "type": "string" } }, "required": ["cash", "bonus", "locked"], "additionalProperties": false } } } } } } }, "alreadyProcessed": { "type": "boolean", "description": "True if transaction was already processed (idempotency)" }, "initiatedAt": { "type": "string" }, "createdAt": { "type": "string" }, "context": { "type": "object", "properties": { "product": { "type": "string" }, "reason": { "type": "string" } }, "additionalProperties": true }, "amountBreakdown": { "type": "object", "properties": { "cash": { "type": "string" }, "bonus": { "type": "string" }, "locked": { "type": "string" }, "retract": { "type": "string" } }, "additionalProperties": false } }, "required": [ "currency", "platform", "id", "type", "balances", "alreadyProcessed", "initiatedAt", "createdAt", "context", "amountBreakdown" ] }

Response Examples

First Request (New Transaction)

Success Response - First Request
{ "balances": { "sport": { "main": { "USD": { "cash": "86.1", "bonus": "0", "locked": "0", "retract": "0" } }, "sportsbook": { "USD": { "cash": "86.1", "bonus": "0", "locked": "0", "retract": "0" } } } }, "alreadyProcessed": false, "currency": "USD", "platform": "sport", "id": "5f338453-6172-491f-b8f0-27e382ea05eb", "type": "withdrawal", "initiatedAt": "2025-01-29T00:34:25.000Z", "createdAt": "2025-01-29T00:34:26.000Z", "context": { "channel": "DESKTOP_AIR_PM", "product": "sportsbook", "reason": "bet" }, "amountBreakdown": { "cash": "13.9", "bonus": "0", "locked": "0" } }

Duplicate Request (Idempotency)

If the same transaction ID is sent again, return the exact same response with alreadyProcessed: true:

Success Response - Duplicate Request
{ "balances": { "sport": { "main": { "USD": { "cash": "86.1", "bonus": "0", "locked": "0", "retract": "0" } }, "sportsbook": { "USD": { "cash": "86.1", "bonus": "0", "locked": "0", "retract": "0" } } } }, "alreadyProcessed": true, "currency": "USD", "platform": "sport", "id": "5f338453-6172-491f-b8f0-27e382ea05eb", "type": "withdrawal", "initiatedAt": "2025-01-29T00:34:25.000Z", "createdAt": "2025-01-29T00:34:26.000Z", "context": { "channel": "DESKTOP_AIR_PM", "product": "sportsbook", "reason": "bet" }, "amountBreakdown": { "cash": "13.9", "bonus": "0", "locked": "0" } }

For more response examples, see Transaction Examples.


Error Responses

Error Response Structure

Return HTTP 400 with the following structure for business logic errors:

Error Response Schema

{ "type": "object", "properties": { "error": { "type": "object", "properties": { "code": { "type": "string", "description": "Error code from the error codes list" }, "message": { "type": "string", "description": "Human-readable error message (English, for logs/operations)" }, "origin": { "type": "string", "description": "Your company/system name" }, "details": { "type": "object", "description": "Optional structured payload for the error.", "properties": { "localizedMessage": { "type": "string", "description": "Player-language message to be displayed to the player as-is. Used together with the decline.client.custom error code; the language is supplied to your wallet via context.language on the original placement request." } }, "additionalProperties": true } }, "required": ["code", "message", "origin"] }, "alreadyProcessed": { "type": "boolean", "description": "Optional: True if this error was already returned for this transaction ID" }, "balances": { "type": "object", "description": "Optional. Current player balances — same nested structure (platform -> product -> currency -> balance details) as in the success response. When present on a decline.lowbalance error, the Sportsbook BE will use these balances directly instead of making a follow-up get-user-balance call. See Returning balances in error responses.", "patternProperties": { "^.*$": { "type": "object", "patternProperties": { "^.*$": { "type": "object", "patternProperties": { "^.*$": { "type": "object", "properties": { "cash": { "type": "string" }, "bonus": { "type": "string" }, "locked": { "type": "string" }, "retract": { "type": "string" } }, "required": ["cash", "bonus", "locked"], "additionalProperties": false } } } } } } } }, "required": ["error"] }

Error Response Examples

New Error

Error Response - First Occurrence
{ "error": { "code": "decline.lowbalance", "message": "Not enough funds for performing transaction", "origin": "YourCompanyName" }, "alreadyProcessed": false }

Duplicate Error (Idempotency)

If the same transaction ID is sent again and previously failed, return the same error with alreadyProcessed: true:

Error Response - Duplicate Request
{ "error": { "code": "decline.lowbalance", "message": "Not enough funds for performing transaction", "origin": "YourCompanyName" }, "alreadyProcessed": true }

Returning balances in error responses

Your wallet may include the player’s current balances in any error response — the field has the exact same nested structure (platform → product → currency → balance details) as in a successful response.

Primary use case — decline.lowbalance. By default, after a decline.lowbalance the Sportsbook BE calls get-user-balance to show the player the amount available for placing a bet. If your error response already carries balances, the Sportsbook BE uses them directly and skips the follow-up call — fewer round-trips and less load on your wallet.

Behavior notes:

  • The field is optional. If omitted, the existing behaviour (extra get-user-balance call) is preserved.
  • Including balances is also supported on other errors, but decline.lowbalance is the case where it provides a measurable benefit, since it’s the one that triggers an automatic balance refresh.
  • Idempotency rules still apply: when returning the same error for a duplicate transaction ID (alreadyProcessed: true), return the same balances you returned originally.

See Error Response with balances for the full example.

Common Error Codes

Every code your wallet may return, with its HTTP status and when it applies, is listed on Wallet Error Codes.


Custom decline with localized message

Use decline.client.custom when your wallet wants to reject a bet for an operator-specific reason and display a tailored message to the player. The Sportsbook surfaces the message as-is — you do not need to register the reason on our side and we do not translate or rewrite it.

Two parts work together:

  1. Inbound — context.language. On bet placement transactions (reason = bet, bet with tax, or freebet), the request context carries the player’s current language (e.g. en, uk, es). Use it to pick the correct localization for localizedMessage.
  2. Outbound — error.details.localizedMessage. Reject with HTTP 400 and the standard error envelope, but populate details.localizedMessage with the text the player should see. The Sportsbook returns this exact string to the frontend as the error reason; the top-level error.message is used only for logs/operations and is never displayed.

Behavior notes:

  • This applies only to bet placement; settlements, rollbacks and bonus transactions are not user-facing in the same way and do not need a localized message.
  • If context.language is missing or your wallet has no translation for it, fall back to a default language
  • localizedMessage is the only field we read from details for this purpose; you may include other diagnostic fields alongside it, they will be ignored by the Sportsbook.
  • Idempotency rules apply as for any other declined transaction: store the snapshot and return the same response (with alreadyProcessed: true) on a duplicate transaction ID.

See Error Response with Localized Message for the full request/response example.


Rollback and Retry Requirements

Caution

Critical: Must Read Rollback handling has special requirements including:

  • Applies to bet placements only — a settlement is reverted with cancelsettle, not with a rollback
  • Specific error codes (decline.parent.notfound, decline.parent.failed)
  • 400 retries (5 min delay) until a definitive response
  • High availability requirements

Read the complete guide: Rollback and Retry Logic


Implementation Checklist

Core Integration (Required)

  • Implement endpoint with your URL structure
  • Handle core transaction types: deposit, withdrawal, rollback
  • Implement idempotency check (same id = same response)
  • Store transaction snapshots for at least 3 days (including failed transactions)
  • Return correct balance structure in response
  • Handle core reason codes: bet, settle, resettle, cancelsettle, sport rollback
  • Route sport rollback and cancelsettle through separate handlers — a rollback reverses a placement and validates parentId, a cancel settle reverses a settlement and has no parent
  • Implement proper error responses with correct error codes
  • ⚠️ CRITICAL: Read and implement Rollback and Retry Logic
  • Test retry scenarios for each operation type
  • Configure X-Brand and X-Operator-Id header handling
  • Notify GR8 Tech of your endpoint URL
  • Test with GR8 Tech team before going live

Bonus Integration (Optional)

Only required if integrating with GR8 Tech Bonus System:

  • Handle bonus transaction types: award, release, retract
  • Implement bonus balance tracking (bonus, locked, retract)
  • Handle bonus reason codes: bonus award, bonus release, bonus limit release
  • Handle freebet reason codes: freebet award, freebet, settle freebet, etc.
  • Test bonus award and release flows
  • Test freebet flows (if applicable)
  • Review Sport Bonus Amount documentation
  • Review Freebet documentation (if applicable)

Core Wallet Integration

Bonus System Integration (Optional)