Skip to content
smallPict
Start Free

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:

  1. Integrity: The request body and URI path cannot be altered in transit.
  2. Replay protection: The timestamp window rejects old or captured requests.
  3. 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:

HTTP
X-API-Key: sp_sdk_9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0bX-Timestamp: 1716301234X-Signature: 3a9f8b2c4d6e8a0f1b3c5d7e9f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1d3e5f7a
HeaderTypeDescription
X-API-KeyStringYour customer API key (e.g., sp_wp_..., sp_test_..., sp_sdk_...)
X-TimestampInteger StringUnix epoch timestamp in seconds at the time of sending
X-SignatureHex StringHex-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:

TEXT
{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. For GET requests or empty bodies, use the SHA-256 of an empty string: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

Step 2: Compute the HMAC-SHA256

TEXT
Signature = hex(HMAC_SHA256(SecretKey, StringToSign))

Examples

Node.js (TypeScript)

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

Python
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, signature

Go

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 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.