What you'll learn
- What HMAC is and why APIs use request signing
- How to build a canonical string so signatures match on both sides
- How to compute and verify HMAC (with timestamps and nonces)
- How to avoid common pitfalls like encoding mismatches and replay attacks
Why this matters
As an API Engineer, you will secure internal calls and webhooks, protect admin endpoints, and verify third-party callbacks. Request signing with HMAC gives you authenticity (it came from someone who knows the secret) and integrity (it was not modified in transit). You will design signing schemes, implement verifiers, and debug signature mismatches across languages and networks.
Concept explained simply
HMAC (Hash-based Message Authentication Code) is a way to prove a message wasn't changed and came from someone who knows a shared secret key. Both client and server share the same secret. The client signs a canonical representation of the request with HMAC using a hashing algorithm (commonly SHA-256). The server recomputes that signature and compares them.
Mental model
Imagine stamping a sealed envelope with a unique ink blend that only you and your teammate have. If the stamp matches, you know your teammate sealed it and no one tampered with it. The "ink" is the secret key; the "stamp" is the HMAC over a standardized description (canonical string) of the request.
HMAC vs hash vs digital signature
- Hash: one-way fingerprint of data. Anyone can compute it; no identity.
- HMAC: hash with a secret key. Proves data integrity + shared-secret authenticity.
- Digital signature (asymmetric): uses private/public keys. No shared secret. Verifier only needs the public key.
Core workflow
Pick exactly what fields go into the signature: method, path, sorted query, selected headers, timestamp, nonce, and body hash.
Normalize each piece: trim, lowercase header names, sort keys, and join with newlines. No ambiguity.
Use HMAC-SHA256 with the shared secret over the canonical string. Encode as hex or base64 (document which one).
Include signature, timestamp, and nonce in headers. Example: Authorization: HMAC keyId=abc,ts=...,nonce=...,sig=...
Server reconstructs the canonical string from the received request, recomputes HMAC, checks constant-time equality, timestamp window, and nonce uniqueness.
Worked examples
Example 1: Canonical string
Inputs:
- Method:
POST - Path:
/v1/payments - Query params (unsorted):
currency=usd&customer_id=42&amount=100 - Headers to sign:
host: api.example.com,x-date: 2024-01-01T12:00:00Z - Body SHA-256 (hex) of raw bytes:
c0ffee000000000000000000000000000000000000000000000000000000babe
Rules:
- Sort query keys by name (ASCII): amount, currency, customer_id
- Lowercase header names; single space after colon; trim values
- Join with newlines exactly
Canonical string:
POST
/v1/payments
amount=100¤cy=usd&customer_id=42
host: api.example.com
x-date: 2024-01-01T12:00:00Z
body-sha256: c0ffee000000000000000000000000000000000000000000000000000000babeExample 2: Compute HMAC (known test vector)
Use HMAC-SHA256 with:
- Key: 20 bytes of 0x0b
- Message:
Hi There
Result (hex): b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
This shows how HMAC digests look and why we must agree on the exact algorithm.
Example 3: Verify with timestamp and nonce
Verifier settings:
- Allowed clock skew/window: ±5 minutes
- Nonce store: remembers nonces for 10 minutes
Given headers:
x-date: 2024-01-01T12:00:03Z
x-nonce: 1d0a4d20
authorization: HMAC keyId=svcA,alg=hmac-sha256,sig=BASE64...
Verification steps:
- Rebuild canonical string from the incoming request
- Compute HMAC using key for
keyId=svcA - Compare signatures using a constant-time compare
- Check timestamp within ±5 minutes
- Check nonce has not been seen in last 10 minutes
If any check fails, reject with 401/403. Otherwise accept.
Constant-time compare snippet (pseudo)
function constantTimeEquals(a, b) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
return diff === 0;
}Who this is for
- API Engineers implementing service-to-service auth
- Backend developers building or consuming webhooks
- Platform engineers standardizing internal request signing
Prerequisites
- Basic HTTP (methods, headers, query string)
- Familiarity with hashing and encodings (hex, base64)
- Comfort with one programming language to implement HMAC
Learning path
- Understand HMAC purpose and properties
- Define canonicalization rules (exact sorting/format)
- Add timestamp and nonce checks
- Implement constant-time verification
- Roll out key rotation and secret management practices
- Apply to real flows: internal APIs, webhooks, and signed uploads
Common mistakes and self-check
- Mismatch encodings (hex vs base64). Self-check: Does your doc specify the exact output format of the signature?
- Ignoring raw-body bytes. Self-check: Are you hashing the exact byte stream received (before JSON parsing or reformatting)?
- Forgetting query ordering. Self-check: Are query parameters sorted consistently with stable separators and encoding?
- Case/whitespace drift in headers. Self-check: Do you lowercase header names and normalize spaces exactly once?
- Not preventing replays. Self-check: Do you enforce a time window and store nonces temporarily?
- Plain equality compare. Self-check: Do you use constant-time comparison for signatures?
How to self-diagnose signature mismatches
- Log the canonical string on both sides and diff them line by line
- Log the byte length of the raw body that was hashed
- Print the algorithm and encoding used for the signature
Exercises
These mirror the interactive exercises below. Try to solve them before expanding solutions.
- Build a canonical string for a POST request, given method, path, query, headers, and provided body hash. Output must match exactly (newlines, sorting, lowercased header names).
- Decide accept or reject given a timestamp and nonce set. Explain your decision referencing the rules.
- Checklist before submitting:
- Sorted query params
- Lowercased header names
- Exact newlines and no extra spaces
- Correct timestamp window rule applied
- Nonce uniqueness correctly evaluated
Practical projects
- Build an HMAC middleware: Verify signed requests for one endpoint, including raw-body hashing, timestamp window, and nonce store.
- CLI signer: Small tool that reads a JSON file and prints the canonical string and signature for quick debugging.
- Key rotation drill: Support two active secrets per client; verify against both; deprecate the old one after rollout.
Mini challenge
Design a signing spec for a webhook you send to partners. In 6 lines or less, specify: algorithm, fields included, canonicalization rules, time window, nonce format, and signature header structure.
Next steps
- Extend your scheme to include response signing (server signs response body)
- Study AWS Signature Version 4 and compare its canonicalization rules
- Explore asymmetric alternatives for third-party distribution (e.g., public-key verification)
Quick Test
Everyone can take the test. Your progress is saved if you are logged in.