HMAC-SHA256 Authentication Standard
How to authenticate requests to the SmallPict API using cryptographic HMAC-SHA256 signatures.
HMAC-SHA256 Authentication Standard
SmallPict secures all API traffic using cryptographic HMAC-SHA256 request signing.
This standard guarantees:
- Payload Integrity: The request body and URI path cannot be altered in transit.
- Replay Attack Immunity: The timestamp drift window blocks old or intercepted requests.
- Zero Secret Exposure: Your secret key is never sent over the wire.
📋 Required HTTP Headers
Every authenticated API request must include the following 3 HTTP headers:
X-API-Key: sp_live_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b
X-Timestamp: 1716301234
X-Signature: 3a9f8b2c4d6e8a0f1b3c5d7e9f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1d3e5f7a
| Header | Type | Description |
|---|---|---|
X-API-Key | String | Your customer API key (e.g., sp_live_..., sp_test_..., sp_sdk_...) |
X-Timestamp | Integer String | Unix epoch timestamp in seconds at the time of sending |
X-Signature | Hex String | Hex-encoded HMAC-SHA256 signature generated with your secret key |
🔐 Signature Algorithm
Step 1: Construct the Canonical String-To-Sign
Concatenate the HTTP method, path, timestamp, and SHA-256 hash of the request body with newline (\n) delimiters:
{HTTP_METHOD}\n{PATH}\n{TIMESTAMP}\n{BODY_SHA256_HEX}
{HTTP_METHOD}: Uppercase HTTP verb (e.g.POST,GET,DELETE).{PATH}: Exact request URI path without query parameters (e.g./v1/optimize,/v1/quota).{TIMESTAMP}: Unix epoch timestamp in seconds (integer string).{BODY_SHA256_HEX}: Hex-encoded SHA-256 hash of the raw request body. ForGETrequests or empty bodies, use the SHA-256 of an empty string:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
Step 2: Compute the HMAC-SHA256 Hash
Signature = hex(HMAC_SHA256(SecretKey, StringToSign))
💻 Signature Implementation Examples
Node.js (TypeScript)
import crypto from 'node:crypto';
export function signRequest(
method: string,
path: string,
secretKey: string,
bodyString: string = ''
) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash('sha256').update(bodyString).digest('hex');
const stringToSign = `${method.toUpperCase()}\n${path}\n${timestamp}\n${bodyHash}`;
const signature = crypto
.createHmac('sha256', secretKey)
.update(stringToSign)
.digest('hex');
return { timestamp, signature };
}
Python 3
import hmac
import hashlib
import time
def sign_request(method: str, path: str, secret_key: str, body_bytes: bytes = b"") -> tuple[str, str]:
timestamp = str(int(time.time()))
body_hash = hashlib.sha256(body_bytes).hexdigest()
string_to_sign = f"{method.upper()}\n{path}\n{timestamp}\n{body_hash}"
signature = hmac.new(
secret_key.encode("utf-8"),
string_to_sign.encode("utf-8"),
hashlib.sha256
).hexdigest()
return timestamp, signature
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
)
func SignRequest(method, path, secretKey string, body []byte) (timestamp string, signature string) {
ts := strconv.FormatInt(time.Now().Unix(), 10)
h := sha256.Sum256(body)
bodyHash := hex.EncodeToString(h[:])
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", method, path, ts, bodyHash)
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(stringToSign))
return ts, hex.EncodeToString(mac.Sum(nil))
}
⏱️ Timestamp Drift Window
To prevent replay attacks where an eavesdropper captures and resends an authenticated request, SmallPict enforces a ±300-second (5-minute) drift window.
Requests with |server_time - X-Timestamp| > 300 will be rejected with 401 Unauthorized (ERR_TIMESTAMP_DRIFT). Ensure your server clocks are synchronized with NTP.
