Chapter 6

Tournaments

Tournaments

The Tournaments feature provides operators with comprehensive tournament management capabilities, including tournament listing, leaderboard access, and player ranking systems.

Tournament Configuration

Tournaments are configured and managed through the Sinatra back office. All tournament settings, rules, and parameters must be set up there before they can be accessed via these APIs.

Authorization Required

All tournament API requests must be authorized using the Authorization header with a Bearer token generated from your Sinatra credentials. Unauthorized requests will be rejected.

Token Format:

Authorization: Bearer <base64_encoded_credentials>

Token Generation:

base64_encoded_credentials = base64encode(sinatrauser:sinatrapassword)

Replace sinatrauser and sinatrapassword with your actual Sinatra back office credentials.

Available APIs

Key Features

  • Tournament Management: Filter and retrieve tournaments by operator, brand, provider, and status
  • Advanced Filtering: Search by game IDs, game names, and tournament names
  • Leaderboard System: Player rankings sorted by points in descending order
  • Prize Distribution: Automated prize distribution to tournament winners
  • Pagination Support: Efficient data retrieval with configurable page sizes
  • Multi-format Support: Player + Brand + Tournament format combinations

Subsections of Tournaments

Get Tournaments

Get Tournaments

Retrieve a list of tournaments with comprehensive filtering options.

Endpoint

GET .../tournaments

Request Parameters

Parameter Type Description
Operator Drop-down Filter by operator
Brand Drop-down Filter by brand
Provider Drop-down Filter by provider
Game ID’s / Game Name Text input Filter by specific game identifiers or names
Tournament Status Drop-down Filter by status: 0=Pending, 1=Active, 2=Complete, 3=Inactive
Tournament Name Text input Filter by tournament name

Pagination

This endpoint supports pagination for efficient data retrieval.

Parameter Type Description
page Integer Page number (starting from 1)
page_size Integer Number of items per page

Example Request

curl --location 'https://stgsinatragateway.groovegaming.com/tournaments/1/?page=1&page_size=10&status=0&operators=6&brands=55' \
--header 'Authorization: Bearer bm9mYXJAZ21haWwuY29tOmRkZA=='

Request URL Parameters

  • page=1 - First page
  • page_size=10 - 10 items per page
  • status=0 - Tournament status filter (0=Pending)
  • operators=6 - Operator ID filter
  • brands=55 - Brand ID filter

Response

Example Response

{
    "total": 3,
    "data": [
        {
            "auto_win": true,
            "created_at": "2025-09-15T07:58:44Z",
            "created_by": 27687,
            "end_date": "2025-11-11 09:23:00",
            "multiplier": 1.5,
            "operator_brand_config": {
                "6": {
                    "brands": [
                        "55"
                    ]
                }
            },
            "operator_template_id": "EXT-123456",
            "participation_scope": 0,
            "prize_type": 1,
            "prizes": {
                "SpecificPositionPrize": [
                    {
                        "position": 1,
                        "prize": 200
                    },
                    {
                        "position": 2,
                        "prize": 100
                    },
                    {
                        "position": 3,
                        "prize": 50
                    }
                ]
            },
            "providers": {
                "26": {
                    "games": [
                        "82685"
                    ]
                }
            },
            "result_url": "https://example.com/tournament/result",
            "rules": {
                "BetToPointsConversion": [
                    {
                        "points_to_add": 5,
                        "required_wager": 50
                    }
                ],
                "MinimumBetAmount": [
                    {
                        "required_wager": 10
                    }
                ],
                "MinimumBetsOrRoundsRequired": [
                    {
                        "required_bets": 10,
                        "required_rounds": 1
                    }
                ],
                "WinToPointsConversion": [
                    {
                        "points_to_add": 10,
                        "required_win": 100
                    }
                ]
            },
            "start_date": "2025-11-09 08:23:00",
            "status": 2,
            "tournament_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
            "tournament_name": "tournament test version 11",
            "updated_at": "2025-09-15T08:09:40Z",
            "updated_by": 27687
        },
        {
            "auto_win": true,
            "created_at": "2025-09-14T19:13:04Z",
            "created_by": 3,
            "end_date": "2025-10-11 09:23:00",
            "multiplier": 1.5,
            "operator_brand_config": {
                "6": {
                    "brands": [
                        "55"
                    ]
                }
            },
            "operator_template_id": "EXT-123456",
            "participation_scope": 0,
            "prize_type": 1,
            "prizes": {
                "SpecificPositionPrize": [
                    {
                        "position": 1,
                        "prize": 200
                    },
                    {
                        "position": 2,
                        "prize": 100
                    },
                    {
                        "position": 3,
                        "prize": 50
                    }
                ]
            },
            "providers": {
                "26": {
                    "games": [
                        "82685"
                    ]
                }
            },
            "result_url": "https://example.com/tournament/result",
            "rules": {
                "BetToPointsConversion": [
                    {
                        "points_to_add": 5,
                        "required_wager": 50
                    }
                ],
                "MinimumBetAmount": [
                    {
                        "required_wager": 10
                    }
                ],
                "MinimumBetsOrRoundsRequired": [
                    {
                        "required_bets": 10,
                        "required_rounds": 1
                    }
                ],
                "WinToPointsConversion": [
                    {
                        "points_to_add": 10,
                        "required_win": 100
                    }
                ]
            },
            "start_date": "2025-10-09 08:23:00",
            "status": 2,
            "tournament_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
            "tournament_name": "avi ac11",
            "updated_at": "2025-09-15T08:13:07Z",
            "updated_by": 27687
        },
        {
            "auto_win": true,
            "created_at": "2025-09-01T07:09:44Z",
            "created_by": 27687,
            "end_date": "2025-09-11 09:24:00",
            "multiplier": 1.5,
            "operator_brand_config": {
                "6": {
                    "brands": [
                        "55"
                    ]
                }
            },
            "operator_template_id": "EXT-123456",
            "participation_scope": 0,
            "prize_type": 1,
            "prizes": {
                "SpecificPositionPrize": [
                    {
                        "position": 1,
                        "prize": 200
                    },
                    {
                        "position": 2,
                        "prize": 100
                    },
                    {
                        "position": 3,
                        "prize": 50
                    }
                ]
            },
            "providers": {
                "26": {
                    "games": [
                        "82685"
                    ]
                }
            },
            "result_url": "https://example.com/tournament/result",
            "rules": {
                "BetToPointsConversion": [
                    {
                        "points_to_add": 5,
                        "required_wager": 50
                    }
                ],
                "MinimumBetAmount": [
                    {
                        "required_wager": 10
                    }
                ],
                "MinimumBetsOrRoundsRequired": [
                    {
                        "required_bets": 10,
                        "required_rounds": 1
                    }
                ],
                "WinToPointsConversion": [
                    {
                        "points_to_add": 10,
                        "required_win": 100
                    }
                ]
            },
            "start_date": "2025-09-09 08:23:00",
            "status": 2,
            "tournament_id": "550e8400-e29b-41d4-a716-446655440000",
            "tournament_name": "tournament test version 11",
            "updated_at": "2025-09-01T07:13:09Z",
            "updated_by": 27687
        }
    ],
    "message": ""
}

Response Fields

Field Type Description
total Integer Total number of tournaments available (used for pagination)
data Array Array of tournament objects
message String Additional message or error information (empty on success)

Tournament Object Fields

Field Type Description
tournament_id String (UUID) Unique identifier for the tournament
tournament_name String Display name of the tournament
status Integer Tournament status: 0=Pending, 1=Active, 2=Complete, 3=Inactive
start_date String Tournament start date and time (YYYY-MM-DD HH:MM:SS)
end_date String Tournament end date and time (YYYY-MM-DD HH:MM:SS)
auto_win Boolean Whether auto-win is enabled for this tournament
multiplier Number Point multiplier applied to tournament scoring
participation_scope Integer Defines who can participate (0=All players, other values for restrictions)
prize_type Integer Type of prizes awarded (1=Specific position prizes)
operator_template_id String External template identifier for the operator
result_url String URL endpoint for tournament results
created_at String Tournament creation timestamp (ISO 8601 format)
updated_at String Last update timestamp (ISO 8601 format)
created_by Integer User ID who created the tournament
updated_by Integer User ID who last updated the tournament

Complex Object Fields

operator_brand_config

Defines which operators and brands can participate in the tournament.

{
    "6": {          // Operator ID
        "brands": [
            "55"    // Array of brand IDs under this operator
        ]
    }
}

providers

Specifies which game providers and specific games are included.

{
    "26": {         // Provider ID
        "games": [
            "82685" // Array of game IDs from this provider
        ]
    }
}

prizes

Defines the prize structure for tournament winners.

{
    "SpecificPositionPrize": [
        {
            "position": 1,    // Leaderboard position
            "prize": 200      // Prize amount for this position
        }
    ]
}

rules

Tournament rules that define how players earn points and participation requirements.

Rule Type Description
BetToPointsConversion Points awarded based on wager amounts
WinToPointsConversion Points awarded based on win amounts
MinimumBetAmount Minimum wager required to participate
MinimumBetsOrRoundsRequired Minimum number of bets/rounds required

BetToPointsConversion Example:

{
    "points_to_add": 5,      // Points awarded
    "required_wager": 50     // Wager amount needed to earn these points
}

WinToPointsConversion Example:

{
    "points_to_add": 10,     // Points awarded
    "required_win": 100      // Win amount needed to earn these points
}

MinimumBetAmount Example:

{
    "required_wager": 10     // Minimum bet amount to participate
}

MinimumBetsOrRoundsRequired Example:

{
    "required_bets": 10,     // Minimum number of bets required
    "required_rounds": 1     // Minimum number of rounds required
}

Authentication

Authorization Required

This endpoint requires Bearer token authentication using your Sinatra back office credentials. All requests must include a valid Authorization header. Unauthorized requests will be rejected.

Required Header:

Authorization: Bearer <base64_encoded_credentials>

Token Generation:

base64_encoded_credentials = base64encode(sinatrauser:sinatrapassword)

Example:

  • Sinatra Username: myuser@example.com
  • Sinatra Password: mypassword
  • Credentials String: myuser@example.com:mypassword
  • Base64 Encoded: bXl1c2VyQGV4YW1wbGUuY29tOm15cGFzc3dvcmQ=
  • Final Header: Authorization: Bearer bXl1c2VyQGV4YW1wbGUuY29tOm15cGFzc3dvcmQ=

Replace with your actual Sinatra back office credentials.

Get Leaderboard

Get Leaderboard

Retrieve tournament leaderboard with player rankings sorted by points in descending order.

Endpoint

GET /tournaments/:version/leaderboard

Path Parameters

Parameter Type Description
version Integer API version number

Query Parameters

Parameter Type Required Description
player_id String No Filter by specific player ID
brand_id Integer No Filter by specific brand ID
tournament_id String No Filter by specific tournament ID
tournament_status String No Filter by tournament status: 0=Pending, 1=Active, 2=Complete, 3=Inactive
tournament_end_date_from String No Filter tournaments ending from this date (format: YYYY-MM-DD)
tournament_end_date_to String No Filter tournaments ending until this date (format: YYYY-MM-DD)
format String No Response format (e.g., csv) - TBD
page Integer No Page number for pagination
page_size Integer No Number of items per page
require_totals Boolean No Whether to include total counts in response
Default Date Range

Note: When searching by brand + player without specifying date filters, only leaderboards from the last 3 months are returned by default.

Filter Application Rules

Note: Status and date filters (tournament_status, tournament_end_date_from, tournament_end_date_to) are only applied when searching by brand + player. If only tournament_id is provided, these filters are ignored and only pagination parameters (page, page_size) are respected.

Tournament ID Validation

Important: If a tournament_id is provided along with brand + player filters, and the tournament does not match the other filter criteria (status, date range, etc.), the API will return an error indicating that the tournament does not exist.

Response Format

Player + Brand + Tournament + format

Sorting

Results are automatically sorted by points in descending order (highest points first).

Pagination

This endpoint supports pagination for efficient data retrieval.

Example Request

curl --location 'https://stgsinatragateway.groovegaming.com/tournaments/1/leaderboard?brand_id=3435&player_id=217850&tournament_status=1&tournament_end_date_from=2025-01-01&tournament_end_date_to=2025-12-31&page=1&page_size=10' \
--header 'Authorization: Bearer bm9mYXJAZ21haWwuY29tOmRkZA=='

Request URL Parameters

  • 1 - API version in the URL path
  • brand_id=3435 - Filter by brand ID (optional)
  • player_id=217850 - Filter by player ID (optional)
  • tournament_status=1 - Filter for Active tournaments (optional)
  • tournament_end_date_from=2025-01-01 - Filter tournaments ending from this date (optional)
  • tournament_end_date_to=2025-12-31 - Filter tournaments ending until this date (optional)
  • page=1 - First page (optional)
  • page_size=10 - 10 items per page (optional)

Response

Example Response

{
    "total": 3,
    "data": [
        {
            "tournament_id": "2eab1af5-81e7-4d85-ad2e-fd4a23fae68a",
            "brand_id": 3435,
            "player_id": "217850",
            "start_multiplier": 1,
            "multiplier": 1,
            "eligible": true,
            "points": 250,
            "position": 1,
            "total_bets": 1001,
            "total_wins": 500.5,
            "total_rounds": 1,
            "created_at": "2025-09-22T14:08:00Z"
        },
        {
            "tournament_id": "2eab1af5-81e7-4d85-ad2e-fd4a23fae68a",
            "brand_id": 3435,
            "player_id": "217849",
            "start_multiplier": 1,
            "multiplier": 1,
            "eligible": true,
            "points": 140,
            "position": 2,
            "total_bets": 601.5,
            "total_wins": 200.5,
            "total_rounds": 1,
            "created_at": "2025-09-22T14:08:00Z"
        },
        {
            "tournament_id": "2eab1af5-81e7-4d85-ad2e-fd4a23fae68a",
            "brand_id": 3435,
            "player_id": "217848",
            "start_multiplier": 1,
            "multiplier": 1,
            "eligible": true,
            "points": 22,
            "position": 3,
            "total_bets": 102.5,
            "total_wins": 20.5,
            "total_rounds": 1,
            "created_at": "2025-09-22T14:08:00Z"
        }
    ],
    "message": ""
}

Response Fields

Field Type Description
total Integer Total number of players in the leaderboard (used for pagination)
data Array Array of leaderboard entry objects sorted by points (highest first)
message String Additional message or error information (empty on success)

Leaderboard Entry Fields

Field Type Description
tournament_id String (UUID) Unique identifier of the tournament this entry belongs to
brand_id Integer Brand identifier where the player is registered
player_id String Unique identifier of the player in the leaderboard
start_multiplier Number Initial multiplier value when player joined the tournament
multiplier Number Current multiplier applied to the player’s points
eligible Boolean Whether the player is eligible for prizes (true=eligible, false=ineligible)
points Integer Total points earned by the player in the tournament
position Integer Player’s current ranking position in the leaderboard (1=first place)
total_bets Number Total amount of bets placed by the player in the tournament
total_wins Number Total amount of wins earned by the player in the tournament
total_rounds Integer Total number of game rounds played by the player in the tournament
created_at String Timestamp when the player joined the tournament (ISO 8601 format)

Key Features

  • Automatic Sorting: Results are automatically sorted by points in descending order (highest points first)
  • Player + Brand + Tournament Format: Each entry represents a unique combination of player, brand, and tournament
  • Eligibility Tracking: The eligible field indicates whether players qualify for prizes
  • Multiplier Support: Both starting and current multipliers are tracked for each player
  • Point Precision: Points are tracked as integers for accurate leaderboard positioning

Authentication

Authorization Required

This endpoint requires Bearer token authentication using your Sinatra back office credentials. All requests must include a valid Authorization header. Unauthorized requests will be rejected.

Required Header:

Authorization: Bearer <base64_encoded_credentials>

Token Generation:

base64_encoded_credentials = base64encode(sinatrauser:sinatrapassword)

Example:

  • Sinatra Username: myuser@example.com
  • Sinatra Password: mypassword
  • Credentials String: myuser@example.com:mypassword
  • Base64 Encoded: bXl1c2VyQGV4YW1wbGUuY29tOm15cGFzc3dvcmQ=
  • Final Header: Authorization: Bearer bXl1c2VyQGV4YW1wbGUuY29tOm15cGFzc3dvcmQ=

Replace with your actual Sinatra back office credentials.

Use Cases

  • Display tournament rankings to players
  • Show leaderboard on tournament pages
  • Track player performance across tournaments
  • Generate tournament reports

Tournament Prize

Tournament Prize Distribution

Info

The Tournament Prize API is used to distribute prizes to winners when a tournament concludes. This endpoint handles the prize allocation from Groove to the casino operator.

Overview

When a tournament ends and winners are determined, Groove sends Tournament Prize requests to the casino operator to distribute the prizes. The casino processes these requests by validating the tournament details, checking for duplicates, and updating the player’s balance accordingly.

Warning

**Important:** Tournament Prize requests implement idempotency controls. This means the same prize request should only be processed once. If a duplicate request is received, the system must return the original response with status code 200 and the "Success - Duplicate" status.

Flow Diagram

sequenceDiagram participant Groove as Groove Platform participant Casino as Casino Operator participant Player as Player Account Groove->>Casino: Tournament Prize Request Casino->>Casino: Validate Signature Casino->>Casino: Check Idempotency Casino->>Casino: Validate Tournament ID Casino->>Casino: Validate Player ID Casino->>Casino: Process Prize alt Prize Type: Amount Casino->>Player: Credit Prize Amount else Prize Type: Other Casino->>Casino: Process Non-Monetary Prize end Casino->>Groove: Prize Response

Casino Responsibilities

The casino platform must perform the following operations when processing a Tournament Prize request:

  1. Signature Validation

    • Validate the X-Groove-Signature header
    • Recalculate the HMAC-SHA256 signature from the request body fields (instead of query parameters) and compare it to the header
    • Reject the request if signature validation fails
  2. Idempotency Check

    • Check if a request with the same transaction_id has been processed before
    • If duplicate, return the original response with status “Success - Duplicate”
  3. Tournament Validation

    • Verify the tournament exists and is valid
    • Confirm the player participated in the tournament
    • Validate prize details match tournament configuration
  4. Prize Processing

    • For monetary prizes (prize_type = 1): Credit the prize amount to player’s balance
    • For non-monetary prizes (prize_type = 2): Process according to operator’s implementation
    • Store transaction details for auditing

Request Details

Endpoint

The casino URL can be either:

  1. The URL specified in the tournament definition
  2. If the tournament URL is empty, use the default wallet endpoint URL (same as regular game play)

{casino_URL}?request=tournamentWin&version=1.2

Request Method

POST

Request Headers

Header Required Description
X-Groove-Signature Yes HMAC-SHA256 signature of the request body. See Signature Calculation
Content-Type Yes application/json
Original-Brand-ID No The operator’s original brand identifier, set by Groove

Request Body Parameters

Parameter Data Type Required Description
tournament_id String(UUID) Yes Unique tournament identifier
Example: 123456-13456-123456-123456
tournament_start_date DateTime Yes Tournament start date and time (UTC)
Format: YYYY-MM-DD HH:MM:SS
tournament_end_date DateTime Yes Tournament end date and time (UTC)
Format: YYYY-MM-DD HH:MM:SS
tournament_position Integer Yes Player’s final position in the tournament
Example: 1 (for first place)
tournament_name String Yes Tournament name
Example: Summer Slots Championship
template_id String No Operator template ID set on tournament configuration; may be empty
Example: 1
prize_type Integer Yes Prize type:
1 - Monetary amount
2 - Other (non-monetary)
player_id String Yes Player’s unique identifier
Example: 12345
casino_id Integer Yes Casino/Brand identifier
Example: 12345
points Integer Yes Player’s accumulated tournament points
Example: 1500
prize_value Decimal(32,10) Yes Prize amount; 0 for non-monetary prizes
Example: 100.5
prize_name String No Prize name (used mainly for non-monetary prizes); may be empty
Example: Grand Prize
total_rounds Integer Yes Total number of rounds played in the tournament
Example: 10
total_bets Decimal(32,10) Yes Total amount wagered during the tournament
Example: 100
total_wins Decimal(32,10) Yes Total amount won during the tournament
Example: 2
timestamp DateTime Yes Request timestamp (UTC)
Format: YYYY-MM-DD HH:MM:SS
transaction_id String(255) Yes Unique transaction identifier
Format: {tournament_id}_{player_id}_{casino_id}
Example: 123456-13456-123456-123456_12345_12345
Info

Groove always sends **all** of the fields above. Fields marked optional may contain an empty string (or `0` for `prize_value`). The `X-Groove-Signature` is computed over **every field present in the body** — see [Signature Calculation](#signature-calculation).

Example Request

POST {casino_URL}?request=tournamentWin&version=1.2
Content-Type: application/json
X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c
Original-Brand-ID: 12345

{
  "tournament_id":         "123456-13456-123456-123456",
  "tournament_start_date": "2025-08-27 00:00:00",
  "tournament_end_date":   "2025-08-27 00:00:00",
  "tournament_position":   1,
  "tournament_name":       "Summer Slots Championship",
  "template_id":           "1",
  "prize_type":            1,
  "player_id":             "12345",
  "casino_id":             12345,
  "points":                1500,
  "prize_value":           100.5,
  "prize_name":            "Grand Prize",
  "total_rounds":          10,
  "total_bets":            100,
  "total_wins":            2,
  "timestamp":             "2025-08-27 00:00:00",
  "transaction_id":        "123456-13456-123456-123456_12345_12345"
}

Response Details

Response Parameters

Parameter Data Type Required Description
transaction_id String(UUID) Yes Unique transaction identifier
Example: 11111-11111-11111-11111
code Integer Yes Response code (see below)
Example: 200
status String Yes Response status message
Example: Success - Duplicate
timestamp DateTime Yes Response timestamp
Format: YYYY-MM-DD HH:MM:SS

Example Success Response

{
  "transaction_id": "11111-11111-11111-11111",
  "code": 200,
  "status": "Success",
  "timestamp": "2025-08-27 00:00:00"
}

Example Duplicate Response

{
  "transaction_id": "11111-11111-11111-11111",
  "code": 200,
  "status": "Success - Duplicate",
  "timestamp": "2025-08-27 00:00:00"
}

Response Codes

Success Codes

Code Status Description
200 Success Prize successfully processed
200 Success - Duplicate Prize already processed (idempotent response)

Error Codes

Code Status Description
1 Internal Error Internal server error or general processing error
110 Operation not allowed Operation not permitted for various reasons:
• Invalid tournament
• Player not eligible
• Invalid prize details
• Account restrictions
Tip

Error codes follow the same standard as other transaction endpoints. For additional error codes that may apply, refer to [Appendix A: Transaction Response Status Codes](/appendix-a-transaction-response-status-codes/).

Implementation Notes

  1. Idempotency Handling:

    • Always store transaction IDs to identify duplicate requests
    • Return the original response for duplicate transactions with the same transaction ID
    • Maintain prize distribution records for auditing
  2. Signature Validation:

    • Tournament Prize uses the same HMAC-SHA256 algorithm as the standard Signature Validation, but the signed values are taken from the request body fields instead of the URL query parameters
    • Sort the body field names alphabetically, concatenate their values only, and compute HMAC-SHA256(concatenated_values, security_key)
    • See Signature Calculation for the exact steps and code samples
  3. Prize Processing:

    • Monetary prizes should be credited to the player’s real balance
    • Non-monetary prizes require custom implementation based on operator requirements
    • Always validate prize amounts match tournament configuration
  4. Error Handling:

    • Return appropriate error codes based on validation results
    • Log all prize distribution attempts for auditing purposes
    • Include detailed error messages for troubleshooting

Security

Signature Calculation

Tournament Prize requests are signed with the same HMAC-SHA256 mechanism as the standard Transaction API Signature Validation. The only difference is that the signed values come from the request body fields instead of the URL query string.

To compute (and validate) the signature:

  1. Take all fields from the JSON request body. Exclude the request field and treat nogsgameid as gameid for sorting — these are shared rules from the standard algorithm and do not apply to this endpoint’s body, but the logic is identical.
  2. Sort the field names alphabetically (ascending).
  3. Concatenate the field values only (not the names), in that sorted order, with no separators. Each value uses its textual form exactly as transmitted in the JSON — numbers without quotes and without trailing zeros (e.g. 100.50100.5), strings without surrounding quotes.
  4. Compute HMAC-SHA256(concatenated_values, security_key) using the casino’s security key.
  5. Hex-encode (lowercase) the result and send it in the X-Groove-Signature header.
signature = HMAC_SHA256(concatenated_body_values, security_key)
Warning

**The signature covers every field present in the body.** Build the concatenated string by iterating over the fields you actually receive — do not hardcode a fixed subset, so that any future fields are handled automatically.

Signature Example

Using the example request above and security key test_key.

After sorting the field names alphabetically and concatenating the values:

# Field Value
1 casino_id 12345
2 player_id 12345
3 points 1500
4 prize_name Grand Prize
5 prize_type 1
6 prize_value 100.5
7 template_id 1
8 timestamp 2025-08-27 00:00:00
9 total_bets 100
10 total_rounds 10
11 total_wins 2
12 tournament_end_date 2025-08-27 00:00:00
13 tournament_id 123456-13456-123456-123456
14 tournament_name Summer Slots Championship
15 tournament_position 1
16 tournament_start_date 2025-08-27 00:00:00
17 transaction_id 123456-13456-123456-123456_12345_12345

Concatenated string (the exact value passed to HMAC-SHA256):

12345123451500Grand Prize1100.512025-08-27 00:00:001001022025-08-27 00:00:00123456-13456-123456-123456Summer Slots Championship12025-08-27 00:00:00123456-13456-123456-123456_12345_12345

Result:

X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c

Implementation Examples

Each example below produces the signature a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c for the request body above with security key test_key.

Python

import hmac
import hashlib


def compute_tournament_signature(body: dict, security_key: str) -> str:
    param_map = {}
    for key, value in body.items():
        if key == "request":  # shared rule; not present in this body
            continue
        sort_key = "gameid" if key == "nogsgameid" else key
        param_map[sort_key] = _stringify(value)

    # Sort by field name, then concatenate the values only
    concatenated = "".join(param_map[k] for k in sorted(param_map))

    return hmac.new(
        security_key.encode("utf-8"),
        concatenated.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()


def _stringify(value) -> str:
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, float) and value.is_integer():
        return str(int(value))  # 100.0 -> "100"
    return str(value)           # 100.5 -> "100.5", ints/strings as-is


# Example usage
security_key = "test_key"
body = {
    "tournament_id": "123456-13456-123456-123456",
    "tournament_start_date": "2025-08-27 00:00:00",
    "tournament_end_date": "2025-08-27 00:00:00",
    "tournament_position": 1,
    "tournament_name": "Summer Slots Championship",
    "template_id": "1",
    "prize_type": 1,
    "player_id": "12345",
    "casino_id": 12345,
    "points": 1500,
    "prize_value": 100.5,
    "prize_name": "Grand Prize",
    "total_rounds": 10,
    "total_bets": 100,
    "total_wins": 2,
    "timestamp": "2025-08-27 00:00:00",
    "transaction_id": "123456-13456-123456-123456_12345_12345",
}
print("X-Groove-Signature:", compute_tournament_signature(body, security_key))
# X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c

Golang

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
)

func computeTournamentSignature(body []byte, securityKey string) (string, error) {
	var fields map[string]interface{}
	if err := json.Unmarshal(body, &fields); err != nil {
		return "", err
	}

	paramMap := make(map[string]string)
	for key, value := range fields {
		if key == "request" { // shared rule; not present in this body
			continue
		}
		sortKey := key
		if key == "nogsgameid" {
			sortKey = "gameid"
		}
		paramMap[sortKey] = fmt.Sprintf("%v", value)
	}

	// Sort by field name, then concatenate the values only
	keys := make([]string, 0, len(paramMap))
	for k := range paramMap {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	var concatenated strings.Builder
	for _, k := range keys {
		concatenated.WriteString(paramMap[k])
	}

	h := hmac.New(sha256.New, []byte(securityKey))
	h.Write([]byte(concatenated.String()))
	return hex.EncodeToString(h.Sum(nil)), nil
}

func main() {
	securityKey := "test_key"
	body := []byte(`{"tournament_id":"123456-13456-123456-123456","tournament_start_date":"2025-08-27 00:00:00","tournament_end_date":"2025-08-27 00:00:00","tournament_position":1,"tournament_name":"Summer Slots Championship","template_id":"1","prize_type":1,"player_id":"12345","casino_id":12345,"points":1500,"prize_value":100.5,"prize_name":"Grand Prize","total_rounds":10,"total_bets":100,"total_wins":2,"timestamp":"2025-08-27 00:00:00","transaction_id":"123456-13456-123456-123456_12345_12345"}`)
	signature, _ := computeTournamentSignature(body, securityKey)
	fmt.Printf("X-Groove-Signature: %s\n", signature)
	// X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c
}

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;

public class GrooveTournamentSignature {

    public static String computeSignature(Map<String, Object> body, String securityKey) throws Exception {
        // TreeMap keeps the field names sorted alphabetically
        TreeMap<String, String> params = new TreeMap<>();
        for (Map.Entry<String, Object> entry : body.entrySet()) {
            String key = entry.getKey();
            if (key.equals("request")) {        // shared rule; not present in this body
                continue;
            }
            if (key.equals("nogsgameid")) {
                key = "gameid";
            }
            params.put(key, stringify(entry.getValue()));
        }

        // Concatenate the values only
        StringBuilder concatenated = new StringBuilder();
        for (String value : params.values()) {
            concatenated.append(value);
        }

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(securityKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(concatenated.toString().getBytes(StandardCharsets.UTF_8));

        StringBuilder hex = new StringBuilder();
        for (byte b : hash) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }

    private static String stringify(Object value) {
        if (value == null) {
            return "";
        }
        if (value instanceof Boolean) {
            return (Boolean) value ? "true" : "false";
        }
        if (value instanceof Double || value instanceof Float) {
            // numbers without trailing zeros: 100.0 -> "100", 100.5 -> "100.5"
            return BigDecimal.valueOf(((Number) value).doubleValue()).stripTrailingZeros().toPlainString();
        }
        return value.toString();
    }

    public static void main(String[] args) throws Exception {
        String securityKey = "test_key";
        Map<String, Object> body = new TreeMap<>();
        body.put("tournament_id", "123456-13456-123456-123456");
        body.put("tournament_start_date", "2025-08-27 00:00:00");
        body.put("tournament_end_date", "2025-08-27 00:00:00");
        body.put("tournament_position", 1);
        body.put("tournament_name", "Summer Slots Championship");
        body.put("template_id", "1");
        body.put("prize_type", 1);
        body.put("player_id", "12345");
        body.put("casino_id", 12345);
        body.put("points", 1500);
        body.put("prize_value", 100.5);
        body.put("prize_name", "Grand Prize");
        body.put("total_rounds", 10);
        body.put("total_bets", 100);
        body.put("total_wins", 2);
        body.put("timestamp", "2025-08-27 00:00:00");
        body.put("transaction_id", "123456-13456-123456-123456_12345_12345");

        System.out.println("X-Groove-Signature: " + computeSignature(body, securityKey));
        // X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c
    }
}

TypeScript

import { createHmac } from "crypto";

function computeTournamentSignature(body: Record<string, unknown>, securityKey: string): string {
  const paramMap: Record<string, string> = {};
  for (const [key, value] of Object.entries(body)) {
    if (key === "request") continue; // shared rule; not present in this body
    const sortKey = key === "nogsgameid" ? "gameid" : key;
    paramMap[sortKey] = stringify(value);
  }

  // Sort by field name, then concatenate the values only
  const concatenated = Object.keys(paramMap)
    .sort()
    .map((k) => paramMap[k])
    .join("");

  return createHmac("sha256", securityKey).update(concatenated, "utf8").digest("hex");
}

function stringify(value: unknown): string {
  if (typeof value === "boolean") return value ? "true" : "false";
  return String(value); // numbers: no quotes, no trailing zeros; strings: as-is
}

// Example usage
const securityKey = "test_key";
const body = {
  tournament_id: "123456-13456-123456-123456",
  tournament_start_date: "2025-08-27 00:00:00",
  tournament_end_date: "2025-08-27 00:00:00",
  tournament_position: 1,
  tournament_name: "Summer Slots Championship",
  template_id: "1",
  prize_type: 1,
  player_id: "12345",
  casino_id: 12345,
  points: 1500,
  prize_value: 100.5,
  prize_name: "Grand Prize",
  total_rounds: 10,
  total_bets: 100,
  total_wins: 2,
  timestamp: "2025-08-27 00:00:00",
  transaction_id: "123456-13456-123456-123456_12345_12345",
};
console.log("X-Groove-Signature:", computeTournamentSignature(body, securityKey));
// X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096c