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.
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:
**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
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 |
**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
-
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)
-
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
-
Error Handling
- Never expose security keys in error messages
- Log detailed errors server-side for debugging
- Return generic error messages to clients
-
Testing
- Test with the provided example signatures
- Verify parameter sorting logic thoroughly
- Test edge cases (empty values, special characters)
- Validate handling of the
nogsgameidtogameidconversion
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
- Log the concatenated string before hashing
- Verify the security key is correct
- Check parameter extraction and sorting
- Ensure proper character encoding (UTF-8)
- Validate the HMAC-SHA256 implementation
Migration Guide
If you’re migrating from an unsecured implementation:
- Phase 1: Implement signature generation in your application
- Phase 2: Test with provided examples in staging environment
- Phase 3: Enable signature validation with your account manager
- Phase 4: Deploy to production with monitoring
- Phase 5: Monitor error rates and adjust as needed
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.