Skip to content

Perform Transaction

Overview

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.

Key points:

  • You implement this endpoint with your own URL
  • Notify GR8 Tech about your endpoint URL for configuration
  • Must guarantee idempotency (same transaction ID = same response)
  • Get balance is called after each transaction by default

On this page:


Quick Reference

Transaction Types Overview

Type Direction Required Common Use Cases
withdrawal Money out Yes Bets, cancel settlements, negative adjustments
deposit Money in Yes Settlements, winnings, positive adjustments
rollback Reverse Yes Bet cancellations, reversals

Bonus Integration

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

Quick Navigation


Endpoint Configuration

URL Format

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

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.

Multi-Tenant Setup

Each brand must use exactly one wallet endpoint. If you operate multiple brands, you can either:

  • Provide different endpoint URLs per brand
  • Use one endpoint URL with routing logic based on X-Brand and X-Operator-Id headers

Request Parameters

Parameter Location Type Required Description
playerId Path string Yes Unique player identifier
X-Brand Header string Yes Tenant identifier representing site and operator (configured during setup, cannot be changed)
X-Operator-Id Header string Yes Tenant identifier representing site and operator (configured during setup, cannot be changed)

Request Structure

Core Fields

Field Type Required Description
id string Yes Unique transaction identifier. Used for idempotency - same ID = same result
currency string Yes ISO 4217 currency code (USD, EUR, KES, etc.)
platform string Yes Platform identifier (typically "sport" or "CRM")
type string Yes Transaction type: deposit, withdrawal, rollback, award, release, retract
initiatedAt string Yes ISO 8601 timestamp when transaction was initiated
createdAt string Yes ISO 8601 timestamp when transaction was created

Context Object

Field Type Required Description
product string Yes Product identifier (sportsbook, crm)
reason string Yes Transaction reason code (see Reference)
channel string No Bet related channel for bet transaction (placement + settlement)
betId string No Bet identifier (present for bet-related transactions)
parentId string No Parent transaction ID (required for rollback transactions)
sessionId string No Player session identifier. Present in bet placement transactions when the operator passes sessionId as a query parameter in the iFrame URL
language string No Player language at the moment of bet placement (taken from the iFrame X-Language header). Sent only on bet placement transactionsreason = bet, bet with tax, or freebet. Use it to localize the error message when rejecting a bet with decline.client.custom.
sportBonusOfferId string No Activity ID from Journey Builder (for bonus transactions)
sportBonusPlayerOfferId string No Player-specific bonus activation ID (for bonus transactions)

Amount Breakdown

Field Type Description
cash string Cash amount as decimal string (e.g., "100.50")
bonus string Bonus amount as decimal string
locked string Locked amount as decimal string (used for wagering requirements)
retract string Retract 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 sessionId as a query parameter in the iFrame URL"
        },
        "language": {
          "type": "string",
          "description": "Player language at the moment of bet placement (taken from the iFrame X-Language header). 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."
        }
      },
      "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

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 in the iFrame 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 in the iFrame. 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

Error Code HTTP Status Description
decline.lowbalance 400 Insufficient funds. May optionally include current balances in the response to skip the follow-up get-user-balance call (see Returning balances in error responses).
decline.parent.notfound 400 Parent transaction not found (for rollbacks)
decline.parent.failed 400 Parent transaction failed (for rollbacks)
error.user.not-found 400 Player not found
decline.player.blocked 400 Player account is blocked
decline.currency.mismatch 400 Currency mismatch
decline.client.custom 400 Operator-specific decline. The reason text is provided in error.details.localizedMessage and shown to the player verbatim (see Custom decline with localized message).

For a complete list of error codes, see 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 iFrame 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 iFrame 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 iFrame 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 iFrame.
  • 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

Critical: Must Read

Rollback handling has special requirements including:

  • Specific error codes (decline.parent.notfound, decline.parent.failed)
  • Infinite retries until 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
  • [ ] 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)