Signature Validation

Game Transaction Security Header

Overview

The Transaction API supports an optional security feature using HMAC-SHA256 cryptographic signatures to ensure request authenticity and prevent tampering. When enabled, all transaction requests must include a valid signature in the X-Groove-Signature header.

Info

This security feature is optional and must be enabled by your account manager. Once enabled, all transaction API requests must include the signature header.

Supported Endpoints

The signature validation applies to all transaction endpoints:

  • GetAccount - Validate player session
  • GetBalance - Retrieve player balance
  • Wager - Process bet placement
  • Result - Process game outcome
  • WagerAndResult - Combined bet and result
  • Jackpot - Process jackpot wins
  • Rollback - Reverse transactions
  • ReverseWin - Reverse win transactions
  • RollbackRollback - Reverse rollback operations
  • WagerByBatch - Process batch betting (POST request)

Setup

1. Security Key Provision

Your account manager will provide a unique security key for generating HMAC-SHA256 signatures. This key must be kept secure and never exposed in client-side code.

2. Header Inclusion

All requests must include the signature in the request header:

X-Groove-Signature: <generated_signature>

Hash Creation Process

Step 1: Parameter Concatenation

Concatenate all query parameter values (not names) in lexicographical order based on the parameter names:

Warning

**Important Rules:** - Sort parameters alphabetically by their **names** - Concatenate only the **values** (not the names) - **Exclude** the `request` query parameter - Treat `nogsgameid` as `gameid` for sorting purposes - Include `frbId` parameter for free spin requests (Result, Wager, WagerAndResult) - For WagerByBatch (POST), use only query parameters, not the request body

Step 2: Generate HMAC-SHA256

Use the concatenated string and your security key to generate the HMAC-SHA256 signature:

signature = HMAC_SHA256(concatenated_values, security_key)

Signature Validation Flow

flowchart TD A[Game sends request with X-Groove-Signature header] --> B[Groove receives request] B --> C[Extract query parameters] C --> D[Sort parameters alphabetically] D --> E[Concatenate values] E --> F[Generate HMAC-SHA256 with security key] F --> G{Signature matches header?} G -->|Yes| H[Process transaction] G -->|No| I[Return error 1001] H --> J[Return success response] I --> K[Invalid signature response] style I fill:#f96 style K fill:#f96 style H fill:#9f6 style J fill:#9f6

Error Response

If signature validation fails, the server returns:

{
    "code": 1001,
    "status": "Invalid signature",
    "message": "invalid signature"
}

Implementation Examples

Python

import hmac
import hashlib
from urllib.parse import urlparse, parse_qs

def generate_signature(url, security_key):
    # Parse URL and extract query parameters
    parsed = urlparse(url)
    params = parse_qs(parsed.query)
    
    # Create parameter dictionary (handle single values)
    param_dict = {}
    for key, value_list in params.items():
        # Skip 'request' parameter
        if key == 'request':
            continue
        # Treat 'nogsgameid' as 'gameid' for sorting
        sort_key = 'gameid' if key == 'nogsgameid' else key
        param_dict[sort_key] = value_list[0] if value_list else ''
    
    # Sort parameters alphabetically and concatenate values
    sorted_params = sorted(param_dict.items())
    concatenated = ''.join([value for key, value in sorted_params])
    
    # Generate HMAC-SHA256
    signature = hmac.new(
        security_key.encode('utf-8'),
        concatenated.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return signature

# Example usage
security_key = "test_key"
url = "/groove?request=wager&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&betamount=10.0&roundid=nc8n4nd87&transactionid=trx_id"
signature = generate_signature(url, security_key)
print(f"X-Groove-Signature: {signature}")

Golang

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "net/url"
    "sort"
    "strings"
)

func generateSignature(requestURL string, securityKey string) string {
    // Parse URL
    u, _ := url.Parse(requestURL)
    params := u.Query()
    
    // Create parameter map for sorting
    paramMap := make(map[string]string)
    for key, values := range params {
        // Skip 'request' parameter
        if key == "request" {
            continue
        }
        // Treat 'nogsgameid' as 'gameid' for sorting
        sortKey := key
        if key == "nogsgameid" {
            sortKey = "gameid"
        }
        if len(values) > 0 {
            paramMap[sortKey] = values[0]
        }
    }
    
    // Sort keys alphabetically
    var keys []string
    for k := range paramMap {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    
    // Concatenate values in sorted order
    var concatenated strings.Builder
    for _, key := range keys {
        concatenated.WriteString(paramMap[key])
    }
    
    // Generate HMAC-SHA256
    h := hmac.New(sha256.New, []byte(securityKey))
    h.Write([]byte(concatenated.String()))
    signature := hex.EncodeToString(h.Sum(nil))
    
    return signature
}

// Example usage
func main() {
    securityKey := "test_key"
    url := "/groove?request=wager&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&betamount=10.0&roundid=nc8n4nd87&transactionid=trx_id"
    signature := generateSignature(url, securityKey)
    fmt.Printf("X-Groove-Signature: %s\n", signature)
}

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class GrooveSignature {
    
    public static String generateSignature(String url, String securityKey) throws Exception {
        // Parse query parameters
        String query = url.substring(url.indexOf("?") + 1);
        Map<String, String> params = new LinkedHashMap<>();
        
        for (String param : query.split("&")) {
            String[] keyValue = param.split("=");
            String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8.name());
            String value = keyValue.length > 1 ? 
                URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8.name()) : "";
            
            // Skip 'request' parameter
            if (!key.equals("request")) {
                // Treat 'nogsgameid' as 'gameid' for sorting
                String sortKey = key.equals("nogsgameid") ? "gameid" : key;
                params.put(sortKey, value);
            }
        }
        
        // Sort parameters alphabetically
        Map<String, String> sortedParams = new TreeMap<>(params);
        
        // Concatenate values
        StringBuilder concatenated = new StringBuilder();
        for (String value : sortedParams.values()) {
            concatenated.append(value);
        }
        
        // Generate HMAC-SHA256
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(
            securityKey.getBytes(StandardCharsets.UTF_8), 
            "HmacSHA256"
        );
        mac.init(secretKey);
        byte[] hash = mac.doFinal(concatenated.toString().getBytes(StandardCharsets.UTF_8));
        
        // Convert to hex string
        StringBuilder hexString = new StringBuilder();
        for (byte b : hash) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        
        return hexString.toString();
    }
    
    // Example usage
    public static void main(String[] args) throws Exception {
        String securityKey = "test_key";
        String url = "/groove?request=wager&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&betamount=10.0&roundid=nc8n4nd87&transactionid=trx_id";
        String signature = generateSignature(url, securityKey);
        System.out.println("X-Groove-Signature: " + signature);
    }
}

Example Signatures

Using security key: "test_key"

Request Type URL Concatenated Values Signature
GetAccount /groove?request=getaccount&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&apiversion=1.2 1111.2desktop123_jdhdujdk be426d042cd71743970779cd6ee7881d71d1f0eb769cbe14a0081c29c8ef2a09
GetBalance /groove?request=getbalance&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&nogsgameid=80102&apiversion=1.2 1111.2desktop80102123_jdhdujdk 434e2b4545299886c8891faadd86593ad8cbf79e5cd20a6755411d1d3822abba
Wager /groove?request=wager&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&betamount=10.0&roundid=nc8n4nd87&transactionid=trx_id 1111.210.0desktop80102123_jdhdujdkwagernc8n4nd87trx_id f6d980dfe7866b6676e6565ccca239f527979d702106233bb6f72a654931b3bc
WagerAndResult /groove?request=wagerAndResult&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&result=10.0&roundid=nc8n4nd87&transactionid=trx_id 1111.2desktop80102123_jdhdujdkwagerAndResult10.0nc8n4nd87trx_id bba4df598cf50ec69ebe144c696c0305e32f1eef76eb32091585f056fafd9079
Result /groove?request=result&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&result=10.0&roundid=nc8n4nd87&transactionid=trx_id 1111.2desktop80102123_jdhdujdkresult10.0nc8n4nd87trx_id d9655083f60cfd490f0ad882cb01ca2f9af61e669601bbb1dcced8a5dca1820f
Rollback /groove?request=rollback&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&rollbackamount=10.0&roundid=nc8n4nd87&transactionid=trx_id 1111.2desktop80102123_jdhdujdkrollback10.0nc8n4nd87trx_id 5ecbc1d5c6bd0ad172c859da01cb90746a61942bdf6f878793a80af7539719e5
Jackpot /groove?request=jackpot&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&apiversion=1.2&amount=10.0&roundid=nc8n4nd87&transactionid=trx_id 11110.01.2desktop80102123_jdhdujdkjackpotnc8n4nd87trx_id d4cc7c2a2ed2f33657e2c24e0c32c5ead980f793e2ce81eb00316f0544a45048
ReverseWin /groove?request=reversewin&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&amount=10.0&roundid=nc8n4nd87&transactionid=trx_id&wintransactionid=win_trx_id&apiversion=1.2 11110.01.2desktop80102123_jdhdujdkreversewinnc8n4nd87trx_idwin_trx_id 0e96af62a1fee9e6dfbdbda06bc068a6cf2eb18152e02e39c3af70aecb5d04d7
RollbackRollback /groove?request=rollbackrollback&gamesessionid=123_jdhdujdk&accountid=111&device=desktop&gameid=80102&rollbackAmount=10.0&roundid=nc8n4nd87&transactionid=trx_id&apiversion=1.2 1111.2desktop80102123_jdhdujdkrollbackrollback10.0nc8n4nd87trx_id ecaeae75702f548f788c92c06804e59d11719a70302704b36ef72d607e180327
WagerByBatch POST /groove?request=wagerbybatch&request_id=batch_001&gamesessionid=1501_xyz&gameid=82602&apiversion=1.2 1.2826021501_xyzbatch_001 8a5d4e9f3b2c1a7e6d5c4b3a2918f7e6d5c4b3a2918f7e6d5c4b3a2918f7e6
Tip

**Free Spin Requests**: For Result, Wager, and WagerAndResult endpoints, when processing free spin requests, include the `frbId` parameter in the signature calculation. **WagerByBatch**: This is a POST request. Only query parameters are used for signature calculation - the JSON request body is NOT included in the signature.

Security Best Practices

  1. Key Management

    • Store security keys in secure configuration management systems
    • Never hardcode keys in source code
    • Rotate keys periodically
    • Use different keys for different environments (staging, production)
  2. Implementation Security

    • Always validate signatures server-side
    • Implement request timeout mechanisms
    • Log all signature validation failures for security monitoring
    • Use HTTPS for all API communications
  3. Error Handling

    • Never expose security keys in error messages
    • Log detailed errors server-side for debugging
    • Return generic error messages to clients
  4. Testing

    • Test with the provided example signatures
    • Verify parameter sorting logic thoroughly
    • Test edge cases (empty values, special characters)
    • Validate handling of the nogsgameid to gameid conversion

Troubleshooting

Common Issues

Issue Solution
Signature mismatch Verify parameter sorting order and that ‘request’ parameter is excluded
Special characters Ensure proper URL decoding before concatenation
nogsgameid parameter Remember to treat as ‘gameid’ for sorting
Empty parameter values Include empty strings in concatenation
Case sensitivity Parameter names are case-sensitive

Debugging Steps

  1. Log the concatenated string before hashing
  2. Verify the security key is correct
  3. Check parameter extraction and sorting
  4. Ensure proper character encoding (UTF-8)
  5. Validate the HMAC-SHA256 implementation

Migration Guide

If you’re migrating from an unsecured implementation:

  1. Phase 1: Implement signature generation in your application
  2. Phase 2: Test with provided examples in staging environment
  3. Phase 3: Enable signature validation with your account manager
  4. Phase 4: Deploy to production with monitoring
  5. Phase 5: Monitor error rates and adjust as needed
Warning

Once signature validation is enabled, all requests without valid signatures will be rejected. Ensure your implementation is thoroughly tested before enabling this feature in production.