Tournament Prize
Tournament Prize Distribution
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.
**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
Casino Responsibilities
The casino platform must perform the following operations when processing a Tournament Prize request:
-
Signature Validation
- Validate the
X-Groove-Signatureheader - 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
- Validate the
-
Idempotency Check
- Check if a request with the same
transaction_idhas been processed before - If duplicate, return the original response with status “Success - Duplicate”
- Check if a request with the same
-
Tournament Validation
- Verify the tournament exists and is valid
- Confirm the player participated in the tournament
- Validate prize details match tournament configuration
-
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:
- The URL specified in the tournament definition
- 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 amount2 - 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 prizesExample: 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 |
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 |
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
-
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
-
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
-
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
-
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:
- Take all fields from the JSON request body. Exclude the
requestfield and treatnogsgameidasgameidfor sorting — these are shared rules from the standard algorithm and do not apply to this endpoint’s body, but the logic is identical. - Sort the field names alphabetically (ascending).
- 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.50→100.5), strings without surrounding quotes. - Compute
HMAC-SHA256(concatenated_values, security_key)using the casino’s security key. - Hex-encode (lowercase) the result and send it in the
X-Groove-Signatureheader.
signature = HMAC_SHA256(concatenated_body_values, security_key)**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_12345Result:
X-Groove-Signature: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096cImplementation 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: a268e60d3179de79196db9552d234c90715c9b9359b7c4ae3dca62afa1bf096cGolang
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
Related Endpoints
- Get Tournaments - Retrieve list of available tournaments
- Get Leaderboard - Access tournament rankings and standings
- Transaction API - Standard transaction processing endpoints