{"post":{"slug":"hmac-request-signing-2026","title":"HMAC Request Signing: Proving a Request Is Genuine","excerpt":"A shared secret in a header proves the sender knows the secret. Signing the request proves the request itself was not altered, which is a considerably stronger claim.","category":"code","date":"January 10, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">A</span>n API key in a header proves the sender knows the key. It says nothing about whether the request body arrived as sent, and it means anyone who captures the request has everything needed to make more.\n\nHMAC signing computes a cryptographic hash of the request content using a shared secret. The receiver recomputes it and compares. A match proves both that the sender knew the secret and that the content is exactly what was signed.\n\n## What changed in 2026\n\n- **Signing became standard for webhooks.** Providers sending webhooks overwhelmingly sign them, and consumers increasingly verify.\n- **Timestamp requirements became universal.** Replay windows moved from good practice to specification.\n- **Timing-safe comparison guidance spread.** The requirement became widely known rather than specialist.\n- **Raw-body handling got documented.** Framework middleware consuming the body before verification became a well-known failure.\n\n## Identity versus integrity\n\n| | Bearer token | HMAC signature |\n| --- | --- | --- |\n| Proves sender knows the secret | Yes | Yes |\n| Proves the body was not altered | No | Yes |\n| Reusable if captured | Yes, entirely | Only within the replay window |\n| Secret transmitted | Yes, every request | No — only the signature |\n| Per-request | No | Yes |\n\nThe row that matters most is the last-but-one. A bearer token is sent with every request, so anyone who observes one request has the credential. An HMAC signature is derived from the secret without revealing it — observing a request gives you a signature valid only for that exact content.\n\n## Timestamps stop replay\n\nA signature alone proves the request was genuine at some point. It does not prevent someone capturing a valid request and sending it again.\n\nFor an operation like \"transfer funds\" or \"delete resource\", replay is the whole attack. The solution is to include a timestamp in the signed content and reject requests whose timestamp is outside a short window.\n\nThat bounds replay to the window — typically a few minutes, enough to tolerate clock skew and network delay without leaving a useful attack surface.\n\nThe timestamp must be **inside the signed content**. A timestamp header that is not signed can be modified by an attacker, which defeats the purpose entirely. This is a common implementation error.\n\nFor operations that must not repeat even within the window, add an idempotency key so a replayed request is recognised and ignored — see [idempotency explained](/blog/idempotency-explained-2026).\n\n## Two implementation traps\n\n**Constant-time comparison.** Comparing signatures with ordinary string equality returns as soon as it finds a mismatched byte. That means comparison takes slightly longer for a signature matching more leading bytes, and an attacker measuring response times can determine the correct signature byte by byte.\n\nEvery language provides a timing-safe comparison function. Use it. This is not a theoretical concern.\n\n**Raw body bytes.** The signature covers exact bytes. If your framework parses the body into an object and your verification code re-serialises it, the bytes differ — key order, whitespace, number formatting — and verification fails for legitimate requests, intermittently and confusingly.\n\nCapture the raw body before any parsing middleware touches it, and verify against that. This is the most common practical failure in webhook verification, and it produces a bug that appears to depend on the content.\n\n## Common mistakes\n\n- **Ordinary string comparison.** Leaks the signature through timing.\n- **No timestamp.** Captured requests replay indefinitely.\n- **Timestamp outside the signed content.** Attacker-modifiable.\n- **Verifying against re-serialised body.** Intermittent verification failures.\n- **Logging the secret.** It ends up in log aggregation.\n- **No secret rotation path.** Accept two secrets during rotation, or you cannot rotate without downtime.\n- **Inventing a scheme when the provider specifies one.** Follow the spec exactly.\n\n## FAQ\n\n**Is this better than mutual TLS?**\nDifferent layer. Mutual TLS authenticates the connection; signing authenticates the request. Signing survives proxies and load balancers that terminate TLS, which is why webhooks use it.\n\n**How long a replay window?**\nA few minutes — long enough for clock skew and retries, short enough to limit exposure. Requiring tighter clock synchronisation than your senders can achieve produces spurious failures.\n\n**How do I rotate the secret?**\nAccept signatures from either the old or new secret during an overlap period, then retire the old one. Without that, rotation means a window where requests fail.\n\n**What should be signed?**\nWhatever the specification says, and typically the timestamp, the raw body, and often the method and path. Signing the body alone allows an attacker to replay it against a different endpoint.\n\n## Where to go next\n\nFor the webhook-specific application, read [webhook signing](/blog/webhook-signing-explained-2026). For preventing duplicate processing, [idempotency explained](/blog/idempotency-explained-2026), and for the rate-limiting layer alongside, [API rate limiting](/blog/api-rate-limiting-2026).","hue":310,"metaTitle":"HMAC Request Signing: Proving a Request Is Genuine","metaDescription":"How HMAC signing proves request integrity rather than just identity, why timestamps prevent replay, and the constant-time comparison requirement.","keywords":["hmac signing","request signature verification","webhook security","replay attack prevention","timing safe comparison","api authentication","payload integrity","api security 2026"],"tldr":["**Sign the payload, not just send a token** — a token proves identity, a signature proves integrity.","**Include a timestamp and reject old requests** — without it, a captured request replays forever.","**Compare signatures in constant time** — ordinary string comparison leaks information through timing.","**Sign the raw body bytes** — re-serialising before verification breaks signatures unpredictably.","**Skip:** rolling your own scheme when the provider specifies one. Follow their spec exactly."],"readTime":"4 min read","wordCount":789},"related":[{"slug":"webhooks-deep-dive-explained-2026","title":"Webhooks deep dive in 2026: delivery, security, and reliability","excerpt":"Webhooks are HTTP callbacks that push events to your server instead of making you poll. This 2026 guide covers delivery guarantees, signature verification, and the failure modes teams hit in production.","category":"code","date":"April 21, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":314},{"slug":"webhook-signing-explained-2026","title":"Webhook Signing: Verifying What Arrived at Your Endpoint","excerpt":"Your webhook endpoint is a public URL anyone can post to. Without signature verification, anyone can tell your system that a payment succeeded.","category":"code","date":"January 4, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":308},{"slug":"webhook-retry-and-idempotency-design-2026","title":"Webhook Retry and Idempotency Design in 2026","excerpt":"Webhooks are delivered at-least-once, which means every consumer must handle retries and duplicates safely. This guide covers event ID dedup, fast acknowledgment, and idempotent handler design for 2026.","category":"code","date":"August 10, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":304}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"gpu-procurement-guide-2026","title":"GPU Procurement: Buying Compute Without Overbuying","excerpt":"Memory capacity usually decides which GPU you need, not raw compute. Getting that backwards is how teams end up with fast hardware that cannot hold their model.","category":"ai","date":"January 10, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":196},"older":{"slug":"qbi-deduction-explained-2026","title":"The QBI Deduction: A Break for Business Owners With Cliffs","excerpt":"A deduction of up to a fifth of qualified business income, with income thresholds, wage tests, and a category of professions treated less favourably than others.","category":"finance","date":"January 10, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":295}}