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