Skip to content
GreenSnippets

Sign API requests with HMAC-SHA256

Create and verify a timestamped signature over the method, path and body so requests cannot be forged or replayed.

Python
import hashlib
import hmac
import time


def sign_request(secret, method, path, body, timestamp=None):
    timestamp = str(int(time.time()) if timestamp is None else timestamp)
    message = b".".join([timestamp.encode(), method.upper().encode(), path.encode(), body])
    signature = hmac.new(secret, message, hashlib.sha256).hexdigest()
    return {"X-Timestamp": timestamp, "X-Signature": signature}


def verify_request(secret, method, path, body, headers, tolerance=300, now=None):
    try:
        timestamp = int(headers["X-Timestamp"])
        supplied = headers["X-Signature"]
    except (KeyError, ValueError):
        return False
    if abs((time.time() if now is None else now) - timestamp) > tolerance:
        return False
    expected = sign_request(secret, method, path, body, timestamp)["X-Signature"]
    return hmac.compare_digest(expected, supplied)

Example / expected output

Output
headers = sign_request(b"secret", "POST", "/v1/orders", b'{"id": 1}')
verify_request(b"secret", "POST", "/v1/orders", b'{"id": 1}', headers)  # True

How it works

The sender signs timestamp.method.path.body with a shared secret. The receiver recomputes the signature, compares it in constant time and rejects timestamps outside a small window, which stops replay of captured requests. Keep the secret out of source control and rotate it periodically.