Signature Validation
Signature Validation
Overview
For enhanced security, Groove can sign transaction API requests using HMAC-SHA256. Your platform should validate these signatures to ensure requests are authentic and haven’t been tampered with.
Prerequisites
Before validating signatures, you’ll need:
- Access Key Value: Provided by Groove during integration setup
- Secret: Base64 decoded version of the Access Key Value
Secret = base64_decode(<Access Key Value>)Note: The Access Key is a pseudo-random HS512 secret provided by Groove.
Authorization Header
Each signed request from Groove includes an Authorization header with the following format:
Authorization: HMAC-SHA256 Signature={Signature}Header Syntax
| Component | Description | Required |
|---|---|---|
HMAC-SHA256 |
Authorization scheme | Optional (but recommended to verify) |
Signature |
Base64 encoded HMAC-SHA256 hash | Required |
Signature Calculation
The signature is calculated using:
Signature = base64_encode(HMACSHA256(Path-And-Query, Secret))Where:
- Path-And-Query: The canonical representation of the request path and query string
- Secret: The base64 decoded Access Key Value
Path-And-Query Format
The Path-And-Query is the concatenation of:
- The request’s absolute URI path
- The query string (including the
?)
Example
For a request to:
https://your-domain.com/groove?request=getbalance&gamesessionid=123&accountid=456The Path-And-Query would be:
/groove?request=getbalance&gamesessionid=123&accountid=456Implementation Example
PHP Implementation
function validateGrooveSignature($request, $accessKeyValue) {
// Get the Authorization header
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
// Extract signature from header
if (!preg_match('/HMAC-SHA256\s+Signature=(.+)/', $authHeader, $matches)) {
return false;
}
$providedSignature = $matches[1];
// Decode the secret
$secret = base64_decode($accessKeyValue);
// Build Path-And-Query
$pathAndQuery = $_SERVER['REQUEST_URI'];
// Calculate expected signature
$hash = hash_hmac('sha256', $pathAndQuery, $secret, true);
$expectedSignature = base64_encode($hash);
// Compare signatures (constant-time comparison)
return hash_equals($expectedSignature, $providedSignature);
}
// Usage
$accessKeyValue = "your_base64_access_key_from_groove";
if (validateGrooveSignature($_REQUEST, $accessKeyValue)) {
// Process the valid request
processTransaction();
} else {
// Reject invalid signature
http_response_code(401);
echo json_encode([
"code" => 401,
"status" => "Unauthorized",
"message" => "Invalid signature"
]);
}Node.js Implementation
const crypto = require('crypto');
function validateGrooveSignature(req, accessKeyValue) {
// Get the Authorization header
const authHeader = req.headers['authorization'] || '';
// Extract signature from header
const match = authHeader.match(/HMAC-SHA256\s+Signature=(.+)/);
if (!match) {
return false;
}
const providedSignature = match[1];
// Decode the secret
const secret = Buffer.from(accessKeyValue, 'base64');
// Build Path-And-Query
const pathAndQuery = req.originalUrl;
// Calculate expected signature
const hash = crypto
.createHmac('sha256', secret)
.update(pathAndQuery)
.digest('base64');
// Compare signatures (constant-time comparison)
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(providedSignature)
);
}
// Usage in Express.js
app.get('/groove', (req, res) => {
const accessKeyValue = process.env.GROOVE_ACCESS_KEY;
if (!validateGrooveSignature(req, accessKeyValue)) {
return res.status(401).json({
code: 401,
status: "Unauthorized",
message: "Invalid signature"
});
}
// Process valid request
processTransaction(req, res);
});Python Implementation
import hmac
import hashlib
import base64
from flask import request, jsonify
def validate_groove_signature(access_key_value):
# Get the Authorization header
auth_header = request.headers.get('Authorization', '')
# Extract signature from header
import re
match = re.search(r'HMAC-SHA256\s+Signature=(.+)', auth_header)
if not match:
return False
provided_signature = match.group(1)
# Decode the secret
secret = base64.b64decode(access_key_value)
# Build Path-And-Query
path_and_query = request.full_path
if path_and_query.endswith('?'):
path_and_query = path_and_query[:-1]
# Calculate expected signature
hash_obj = hmac.new(
secret,
path_and_query.encode('utf-8'),
hashlib.sha256
)
expected_signature = base64.b64encode(hash_obj.digest()).decode('utf-8')
# Compare signatures (constant-time comparison)
return hmac.compare_digest(expected_signature, provided_signature)
# Usage in Flask
@app.route('/groove')
def handle_groove_request():
access_key_value = os.environ.get('GROOVE_ACCESS_KEY')
if not validate_groove_signature(access_key_value):
return jsonify({
'code': 401,
'status': 'Unauthorized',
'message': 'Invalid signature'
}), 401
# Process valid request
return process_transaction()Testing Signature Validation
Test Example
Given:
- Access Key Value:
dGVzdF9zZWNyZXRfa2V5XzEyMw== - Request URL:
/groove?request=getbalance&accountid=123 - Expected Path-And-Query:
/groove?request=getbalance&accountid=123
Steps:
- Decode secret:
base64_decode("dGVzdF9zZWNyZXRfa2V5XzEyMw==") - Calculate HMAC-SHA256 of Path-And-Query with secret
- Base64 encode the hash
- Compare with provided signature
Security Best Practices
- Always validate signatures in production environments
- Use constant-time comparison to prevent timing attacks
- Store Access Key securely (environment variables, secure vault)
- Log validation failures for security monitoring
- Reject requests with invalid or missing signatures
- Rotate Access Keys periodically as per security policy
- Use HTTPS for all communications
Common Issues and Solutions
Issue 1: Signature Mismatch
Cause: Incorrect Path-And-Query construction Solution: Ensure you’re using the exact request URI including all query parameters
Issue 2: Encoding Problems
Cause: Incorrect base64 decoding of Access Key Solution: Verify the Access Key is properly base64 decoded before use
Issue 3: Missing Authorization Header
Cause: Header not properly forwarded by proxy/load balancer Solution: Configure your infrastructure to forward Authorization headers
Issue 4: Query Parameter Order
Cause: Parameters reordered by framework Solution: Use the raw request URI as received, not reconstructed
Response for Invalid Signatures
When signature validation fails, return:
{
"code": 401,
"status": "Unauthorized",
"message": "Invalid signature",
"apiversion": "1.2"
}Integration Checklist
- Receive Access Key Value from Groove
- Implement signature validation in your chosen language
- Test with sample requests from Groove
- Handle both signed and unsigned requests (during transition)
- Configure proper error responses
- Set up monitoring for validation failures
- Document your validation endpoint
Note on Optional Implementation
While signature validation is highly recommended for production environments, it may be optional during initial integration and testing phases. Coordinate with Groove to determine when signature validation should be enabled for your integration.