HMAC-SHA256 Authentication Standard
How to authenticate requests to the SmallPict API using cryptographic HMAC-SHA256 signatures.
Every API request is signed with HMAC-SHA256.
Signing provides:
- Integrity: The request body and URI path cannot be altered in transit.
- Replay protection: The timestamp window rejects old or captured requests.
- No secret in transit: Your secret key is never sent over the network.
Required headers
Every authenticated API request must include the following 3 HTTP headers:
X-API-Key: sp_sdk_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0bX-Timestamp: 1716301234X-Signature: 3a9f8b2c4d6e8a0f1b3c5d7e9f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1d3e5f7a| Header | Type | Description |
|---|---|---|
X-API-Key | String | Your customer API key (e.g., sp_wp_..., 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: Build the 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
Signature = hex(HMAC_SHA256(SecretKey, StringToSign))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 hmacimport hashlibimport 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, signatureGo
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 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.