#
HMAC Authentication for Quable App
By

The Quable Team
#
Overview
All requests sent from Quable PIM to your application can be authenticated using an HMAC (Hash-based Message Authentication Code) mechanism with SHA-256.
#
How It Works
Authentication relies on two custom HTTP headers:
X-Timestamp
Unix timestamp of the request timeX-Signature
HMAC signature encoded in base64
#
Generating the HMAC Signature
#
1. Building the String to Sign
The signature is calculated from a string constructed with the following elements, separated by the pipe character |
:
{METHOD}|{ENDPOINT}|{TIMESTAMP}|{PAYLOAD}
Component Details:
Example string to sign:
POST|/api/v1|1727712000|{"object":{"type":"product","ids":["PROD1"]},"slot":"document.page.tab"}
#
2. Computing the HMAC-SHA256
The signature is calculated using:
- Algorithm: HMAC-SHA256
- Secret key: The shared secret provided by Quable during app configuration
- Message: The string built in step 1
- Format: Binary output (raw output)
#
3. Base64 Encoding
The binary HMAC result is then encoded in base64 to be transmitted in the HTTP header.
#
Validating Requests in Your Application
Your application must validate each incoming request:
#
Validation Steps
- Extract headers
X-Timestamp
andX-Signature
- Verify timestamp validity (±5 minutes tolerance recommended to avoid rejections due to clock drift)
- Rebuild the string to sign with the same parameters
- Calculate the expected HMAC with your secret
- Compare signatures securely (use constant-time comparison to prevent timing attacks)
#
Validation Example (PHP)
public function validateRequest($method, $endpoint, $payload, $receivedTimestamp, $receivedSignature) {
// Check timestamp (5 minutes tolerance)
$currentTime = time();
$timeDiff = abs($currentTime - (int)$receivedTimestamp);
if ($timeDiff > 300) {
throw new Exception('Request timestamp expired');
}
// Rebuild the string to sign
$string_to_sign = implode('|', array(
strtoupper($method),
$endpoint,
$receivedTimestamp,
$payload
));
// Calculate expected HMAC
$expectedSignature = base64_encode(
hash_hmac('sha256', $string_to_sign, $this->getSecret(), true)
);
// Secure signature comparison
if (!hash_equals($expectedSignature, $receivedSignature)) {
throw new Exception('Invalid signature');
}
return true;
}
#
Best Practices
✅ Do:
- Store the secret securely (environment variables, vault)
- Use constant-time comparison (
hash_equals
in PHP) - Implement a timestamp tolerance window (5 minutes recommended)
- Log failed authentication attempts
- Reject requests without authentication headers
❌ Don't:
- Hardcode the secret in your code
- Compare signatures with
==
(vulnerable to timing attacks) - Accept timestamps that are too old or in the future
- Ignore validation errors
#
Troubleshooting
#
Error: "Invalid signature"
Possible causes:
- Incorrect or misconfigured secret
- Wrong order of components in the string to sign
- Different character encoding (watch out for UTF-8)
- Modified or malformed payload
- HTTP method in wrong case
Solution: Log the string to sign on both sides for comparison
#
Error: "Request timestamp expired"
Possible causes:
- Clocks out of sync between servers
- Replayed request (replay attack)
- High network latency
Solution: Check NTP synchronization, adjust tolerance window