{"id":84230,"date":"2022-08-03T22:50:08","date_gmt":"2022-08-03T14:50:08","guid":{"rendered":"https:\/\/www.ahhhhfs.com\/84230\/"},"modified":"2026-10-17T00:28:49","modified_gmt":"2026-10-16T16:28:49","slug":"what-is-hmac-how-to-sign-api-requests-with-hmac-sha256","status":"publish","type":"post","link":"https:\/\/www.ahhhhfs.com\/en\/84230\/","title":{"rendered":"What Is HMAC? How to Sign API Requests with HMAC-SHA256"},"content":{"rendered":"<p><strong>HMAC authentication<\/strong> is widely used for API request signing, webhook verification, and server-to-server communication. Its job is not to encrypt a request. It lets a server verify that a message was created by someone who holds the shared secret, and that the signed parts of the request were not changed in transit.<\/p>\n<p>A common design is to build a canonical request from the HTTP method, path, query string, selected headers, a payload hash, a timestamp, and a nonce. The client signs that canonical representation with HMAC-SHA256. The server rebuilds the same input, calculates its own HMAC, and compares the result with the signature sent by the client.<\/p>\n<p>The HMAC calculation itself is straightforward. Most implementation mistakes happen around canonicalization, replay protection, secret handling, and deciding exactly which request fields are covered by the signature.<\/p>\n<h2>What is HMAC authentication?<\/h2>\n<p><strong>HMAC<\/strong> stands for Hash-based Message Authentication Code. RFC 2104 defines it as a message authentication mechanism built from a cryptographic hash function and a shared secret key.<\/p>\n<p>At a high level:<\/p>\n<pre><code>HMAC = Hash Function + Secret Key + Message<\/code><\/pre>\n<p>A plain SHA-256 hash only needs the message:<\/p>\n<pre><code>SHA256(message)<\/code><\/pre>\n<p>HMAC also requires a secret key:<\/p>\n<pre><code>HMAC-SHA256(secret_key, message)<\/code><\/pre>\n<p>Anyone who has the message can calculate a normal SHA-256 hash. Calculating the correct HMAC requires the shared secret as well.<\/p>\n<p>That gives HMAC two useful properties for API protocols: it can detect changes to signed data, and it can verify that the message was produced by a party that knows the shared secret.<\/p>\n<p>There is an important limitation. Both sides hold the same secret. HMAC therefore does not provide the same non-repudiation properties as an asymmetric digital signature such as RSA or ECDSA. Either holder of the shared key could have produced the HMAC.<\/p>\n<h2>How HMAC-SHA256 API request signing works<\/h2>\n<p>Signing only the request body is usually not enough. The server needs to know which request was authorized, not just whether a block of JSON stayed unchanged.<\/p>\n<p>Suppose a client sends:<\/p>\n<pre><code>POST \/api\/orders?currency=USD<\/code><\/pre>\n<p>with this JSON body:<\/p>\n<pre><code>{\r\n  \"product_id\": \"A1024\",\r\n  \"quantity\": 2\r\n}<\/code><\/pre>\n<p>The client can start by hashing the request body:<\/p>\n<pre><code>body_hash = SHA256(request_body)<\/code><\/pre>\n<p>It then builds a canonical request. A simplified format might look like this:<\/p>\n<pre><code>POST\r\n\/api\/orders\r\ncurrency=USD\r\ncontent-type:application\/json\r\nx-client-id:client_123\r\nx-timestamp:1712345678\r\nx-nonce:550e8400-e29b-41d4-a716-446655440000\r\ncontent-type;x-client-id;x-timestamp;x-nonce\r\n&lt;body_sha256&gt;<\/code><\/pre>\n<p>This example covers:<\/p>\n<ul>\n<li>HTTP method<\/li>\n<li>canonical request path<\/li>\n<li>canonical query string<\/li>\n<li>headers included in the signature<\/li>\n<li>the signed-headers list<\/li>\n<li>timestamp<\/li>\n<li>nonce<\/li>\n<li>payload digest<\/li>\n<\/ul>\n<p>This is not a universal HMAC format. An API can define its own signing rules. What matters is that the client and server agree on <strong>exactly which fields are signed and exactly how those fields are normalized<\/strong>.<\/p>\n<p>If a query parameter changes the meaning of the request but is not covered by the signature, an attacker may be able to modify that parameter without invalidating the HMAC. The same applies to headers that affect authorization or business logic.<\/p>\n<p>The client calculates the signature:<\/p>\n<pre><code>signature = HMAC-SHA256(secret_key, canonical_request)<\/code><\/pre>\n<p>It then sends the metadata required for verification:<\/p>\n<pre><code>X-Client-Id: client_123\r\nX-Timestamp: 1712345678\r\nX-Nonce: 550e8400-e29b-41d4-a716-446655440000\r\nX-Signature: &lt;signature&gt;<\/code><\/pre>\n<p><a href=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow.jpg\"><br \/>\n<picture><source srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow.webp 1440w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow-267x200.webp 267w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow-768x576.webp 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\" type=\"image\/webp\"><img decoding=\"async\" class=\"alignnone size-full wp-image-84226 sp-no-webp\" src=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow.jpg\" alt=\"HMAC API request signing and server-side verification workflow\" width=\"1440\" height=\"1080\" title srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow.jpg 1440w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow-267x200.jpg 267w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-workflow-768x576.jpg 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\"><\/picture><br \/>\n<\/a><\/p>\n<p><strong>The secret key itself should never be sent with the request.<\/strong><\/p>\n<p>A Client ID or Key ID only tells the server which secret to look up. It is not a public key. HMAC is still a shared-secret scheme, not a public\/private-key signature system.<\/p>\n<h2>Should you sign the raw request body or canonical JSON?<\/h2>\n<p>This is one of the easiest places to break HMAC verification.<\/p>\n<p>One approach is to hash the exact bytes received over HTTP:<\/p>\n<pre><code>SHA256(raw_request_body_bytes)<\/code><\/pre>\n<p>If that is your protocol, the server should verify those same raw bytes. It should not parse the JSON and serialize it again before hashing.<\/p>\n<p>These two payloads may represent the same JSON values:<\/p>\n<pre><code>{\"a\":1,\"b\":2}<\/code><\/pre>\n<p>and:<\/p>\n<pre><code>{\r\n  \"a\": 1,\r\n  \"b\": 2\r\n}<\/code><\/pre>\n<p>But the byte sequences are different, so their SHA-256 digests are different.<\/p>\n<p>Another design is to require canonical JSON before signing. That can work too, but the protocol has to define character encoding, key ordering, number formatting, escaping, whitespace rules, and serialization behavior.<\/p>\n<p>Signature verification operates on deterministic bytes. \u201cEquivalent JSON\u201d is not enough unless the protocol defines how equivalent data is converted into the same byte sequence.<\/p>\n<h2>How a server verifies an HMAC signature<\/h2>\n<h3>1. Look up the shared secret<\/h3>\n<p>The server uses the Client ID, Key ID, or another credential identifier to find the secret associated with that client.<\/p>\n<h3>2. Reject obviously stale timestamps<\/h3>\n<p>The server can reject requests whose timestamps are clearly outside the allowed time window before doing more work.<\/p>\n<p>The right window depends on the application. A very long window gives captured requests more time to be replayed. A very short one can cause legitimate failures because of clock drift.<\/p>\n<h3>3. Check whether the nonce has already appeared<\/h3>\n<p>A nonce should be unique for each signed request. The server can check whether the same client has already used that nonce during the active signature window.<\/p>\n<p>Do not permanently mark an attacker-supplied nonce as used before the request has been authenticated. Forged requests should not be able to poison replay-protection state.<\/p>\n<p>A safer design ties nonce checking to signature verification, then records or consumes the nonce atomically after the request is authenticated.<\/p>\n<h3>4. Recalculate the payload hash<\/h3>\n<pre><code>body_hash = SHA256(raw_request_body_bytes)<\/code><\/pre>\n<p>If the protocol uses canonical JSON instead, the server must apply exactly the same canonicalization rules as the client.<\/p>\n<h3>5. Rebuild the canonical request<\/h3>\n<p>The server has to use the same rules for:<\/p>\n<ul>\n<li>HTTP method<\/li>\n<li>URI and path normalization<\/li>\n<li>query-string encoding and sorting<\/li>\n<li>signed headers<\/li>\n<li>header casing and whitespace<\/li>\n<li>character encoding<\/li>\n<li>field order<\/li>\n<li>line endings<\/li>\n<li>payload handling<\/li>\n<\/ul>\n<p>A small mismatch is enough to produce a different HMAC.<\/p>\n<h3>6. Calculate the expected HMAC<\/h3>\n<pre><code>expected_signature =\r\nHMAC-SHA256(secret_key, canonical_request)<\/code><\/pre>\n<h3>7. Compare signatures in constant time<\/h3>\n<p>The server compares its expected signature with the signature supplied by the client.<\/p>\n<p>Use a constant-time comparison function from the language runtime or cryptographic library rather than writing a normal string comparison loop. This reduces the risk of leaking useful information through timing differences.<\/p>\n<h3>8. Consume the nonce<\/h3>\n<p>After successful verification, mark the nonce as used with an expiry that matches the signature lifetime.<\/p>\n<p>In a multi-instance API, replay state has to be shared across instances. A common implementation uses a data store with atomic \u201cset if absent\u201d behavior and TTL support instead of keeping nonce state independently in each API server.<\/p>\n<h3>9. Perform authorization checks<\/h3>\n<p>A valid HMAC does not mean the client can access every resource.<\/p>\n<p>The server still needs to enforce:<\/p>\n<ul>\n<li>allowed endpoints<\/li>\n<li>allowed HTTP methods<\/li>\n<li>account or resource boundaries<\/li>\n<li>scopes and permissions<\/li>\n<li>rate limits and quotas<\/li>\n<\/ul>\n<p><strong>Authentication is not authorization.<\/strong><\/p>\n<h2>How HMAC detects request tampering<\/h2>\n<p>Assume the client signs this payload:<\/p>\n<pre><code>{\r\n  \"amount\": 100\r\n}<\/code><\/pre>\n<p>The SHA-256 digest of the request body becomes part of the canonical request.<\/p>\n<p>If someone changes the payload to:<\/p>\n<pre><code>{\r\n  \"amount\": 10000\r\n}<\/code><\/pre>\n<p>the payload hash changes. That changes the canonical request, which changes the HMAC calculated by the server:<\/p>\n<pre><code>client_signature != server_signature<\/code><\/pre>\n<p><a href=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification.jpg\"><br \/>\n<picture><source srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification.webp 1440w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification-267x200.webp 267w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification-768x576.webp 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\" type=\"image\/webp\"><img decoding=\"async\" class=\"alignnone size-full wp-image-84227 sp-no-webp\" src=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification.jpg\" alt=\"How HMAC verifies API request data integrity\" width=\"1440\" height=\"1080\" title srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification.jpg 1440w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification-267x200.jpg 267w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-data-integrity-verification-768x576.jpg 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\"><\/picture><br \/>\n<\/a><\/p>\n<p>The server can then reject the request.<\/p>\n<p>There is one important rule here: <strong>HMAC only protects the fields that were actually signed.<\/strong><\/p>\n<p>If the signature covers the body but not the path or an important query parameter, those unsigned parts are outside that integrity check.<\/p>\n<p>RFC 9421 makes the same point for HTTP Message Signatures: a verifier can only rely on message components that are actually covered by the signature.<\/p>\n<h2>Why HMAC signatures often include a timestamp<\/h2>\n<p>A correct signature proves that the signed request was valid when it was created. It does not prove that the request is new.<\/p>\n<p>Imagine an attacker captures this valid payment request:<\/p>\n<pre><code>POST \/api\/payment\r\nX-Timestamp: 1712345678\r\nX-Nonce: a8f3e1d2\r\nX-Signature: valid-signature<\/code><\/pre>\n<p>The attacker may not know the shared secret and may be unable to change the amount. But they can still send the entire captured request again.<\/p>\n<p>That is a replay attack.<\/p>\n<p>A timestamp limits how long a captured signature remains usable:<\/p>\n<pre><code>abs(server_time - request_timestamp) &lt; allowed_window<\/code><\/pre>\n<p>Once the request falls outside that window, the server rejects it even if the original HMAC is still mathematically valid.<\/p>\n<h2>Why a timestamp alone does not stop replay attacks<\/h2>\n<p>A timestamp narrows the replay window. It does not remove it.<\/p>\n<p>If the server accepts several minutes of clock skew, an attacker may replay the same valid request immediately and still pass the timestamp check.<\/p>\n<p>That is why replay-sensitive APIs often include a unique nonce in the signed data.<\/p>\n<pre><code>client_id + nonce + expiry<\/code><\/pre>\n<p>The server tracks nonces that have already been accepted during the active signature window. Seeing the same nonce again indicates that the request is not new.<\/p>\n<p>The two controls solve different problems:<\/p>\n<ul>\n<li><strong>Timestamp:<\/strong> limits how long a captured request can remain valid.<\/li>\n<li><strong>Nonce:<\/strong> detects reuse of the same request inside that valid period.<\/li>\n<\/ul>\n<p><a href=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection.jpg\"><br \/>\n<picture><source srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection.webp 1440w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection-267x200.webp 267w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection-768x576.webp 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\" type=\"image\/webp\"><img decoding=\"async\" class=\"alignnone size-full wp-image-84228 sp-no-webp\" src=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection.jpg\" alt=\"How timestamps and nonces help prevent API replay attacks\" width=\"1440\" height=\"1080\" title srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection.jpg 1440w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection-267x200.jpg 267w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-timestamp-nonce-replay-protection-768x576.jpg 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\"><\/picture><br \/>\n<\/a><\/p>\n<p>For a distributed API, nonce state has to be shared by all instances. Redis is one common option because it supports TTLs and atomic operations, but the storage technology itself is an implementation choice.<\/p>\n<p>For payment, order creation, coupon issuance, and similar write operations, an <strong>idempotency key<\/strong> can solve a related business problem.<\/p>\n<p>A nonce is mainly a security control for replay detection. An idempotency key prevents the same business action from being executed twice when a legitimate client retries after a timeout.<\/p>\n<h2>HMAC vs API keys, bearer tokens, and OAuth 2.0<\/h2>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>Main purpose<\/th>\n<th>Request signing<\/th>\n<th>Detects tampering with signed data<\/th>\n<th>Common use cases<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>API Key<\/td>\n<td>Client identification and access control<\/td>\n<td>Usually no when used alone<\/td>\n<td>Usually no when used alone<\/td>\n<td>Simple APIs, client identification, quotas<\/td>\n<\/tr>\n<tr>\n<td>Bearer Token<\/td>\n<td>Carries access credentials and authorization<\/td>\n<td>Usually no<\/td>\n<td>Mainly depends on HTTPS and protocol design<\/td>\n<td>Web APIs, OAuth 2.0 access tokens<\/td>\n<\/tr>\n<tr>\n<td>HMAC<\/td>\n<td>Message authentication and integrity<\/td>\n<td>Yes<\/td>\n<td>Yes, for components covered by the signature<\/td>\n<td>Server-to-server APIs, webhooks, partner APIs<\/td>\n<\/tr>\n<tr>\n<td>OAuth 2.0<\/td>\n<td>Authorization framework<\/td>\n<td>Depends on the scheme<\/td>\n<td>Depends on token and protocol design<\/td>\n<td>Third-party application and user authorization<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><a href=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison.jpg\"><br \/>\n<picture><source srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison.webp 1440w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison-267x200.webp 267w,https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison-768x576.webp 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\" type=\"image\/webp\"><img decoding=\"async\" class=\"alignnone size-full wp-image-84224 sp-no-webp\" src=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison.jpg\" alt=\"Comparison of HMAC, API keys, bearer tokens, and OAuth 2.0\" width=\"1440\" height=\"1080\" title srcset=\"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison.jpg 1440w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison-267x200.jpg 267w, https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-authentication-comparison-768x576.jpg 768w\" sizes=\"(max-width: 1440px) 100vw, 1440px\"><\/picture><br \/>\n<\/a><\/p>\n<p>These mechanisms are not mutually exclusive.<\/p>\n<p>An API key may simply identify a client, or it may act as the Key ID used to look up the secret for an HMAC scheme. OAuth 2.0 may handle authorization while another mechanism protects message integrity for a specific high-risk request.<\/p>\n<h2>Is HMAC the same as JWT?<\/h2>\n<p>No.<\/p>\n<p><strong>HMAC is a message authentication construction. JWT is a token format.<\/strong><\/p>\n<p>A JWT can use different algorithms to protect its integrity. HS256, for example, uses HMAC-SHA256.<\/p>\n<p>But using a JWT does not automatically mean every HTTP method, path, query string, timestamp, header, and payload is individually covered by an API request signature.<\/p>\n<p>They operate at different layers and solve different problems.<\/p>\n<h2>Real-world HMAC signing examples<\/h2>\n<h3>AWS Signature Version 4<\/h3>\n<p>AWS Signature Version 4 is a good example of strict request canonicalization.<\/p>\n<p>Its Canonical Request includes the HTTP method, canonical URI, canonical query string, canonical headers, signed headers, and a hashed payload. Query parameters are encoded and sorted, while signed headers follow defined normalization rules.<\/p>\n<p>The implementation is more involved than the simplified example in this article, but the underlying lesson is the same: <strong>both sides must calculate the signature from the same message components using the same canonicalization rules.<\/strong><\/p>\n<h3>GitHub webhooks<\/h3>\n<p>GitHub uses HMAC-SHA256 to verify webhook deliveries when a webhook secret is configured.<\/p>\n<p>GitHub calculates an HMAC from the secret token and webhook payload and sends the result in the <code>X-Hub-Signature-256<\/code> header.<\/p>\n<p>The receiving application calculates the HMAC again using the same secret and the received payload, then compares the two values.<\/p>\n<p>GitHub&#8217;s documentation also recommends secure comparison helpers rather than a normal <code>==<\/code> string comparison.<\/p>\n<h2>Does an HMAC-signed API still need HTTPS?<\/h2>\n<p><strong>Yes.<\/strong><\/p>\n<p>HMAC does not replace HTTPS.<\/p>\n<p>HMAC provides application-level authentication and integrity for the data included in the signature. HTTPS\/TLS protects the transport itself, including request bodies, API keys, bearer tokens, and other sensitive data from passive interception. TLS also lets the client authenticate the server it is connecting to.<\/p>\n<p>OWASP recommends exposing secure REST services over HTTPS only.<\/p>\n<p>A valid HMAC signature is not a reason to send sensitive API traffic over plaintext HTTP.<\/p>\n<h2>Why use HMAC-SHA256 instead of HMAC-MD5?<\/h2>\n<p>Older API documentation may still mention HMAC-MD5 or other legacy combinations.<\/p>\n<p>RFC 2104 was published in 1997 and discussed hash functions that were common at the time, including MD5 and SHA-1. RFC 6151 later updated the security guidance around MD5 and states that new protocol designs should not use HMAC-MD5.<\/p>\n<p>For a new API signing protocol, <strong>HMAC-SHA256<\/strong> is a much more sensible default when it fits the environment and requirements.<\/p>\n<p>There is also little reason to implement HMAC yourself. Use the implementation provided by a mature cryptographic library or standard runtime.<\/p>\n<h2>When HMAC request signing is a good fit<\/h2>\n<p>HMAC works well when both parties can safely keep the same shared secret, for example:<\/p>\n<ul>\n<li>server-to-server API calls<\/li>\n<li>partner APIs<\/li>\n<li>webhook verification<\/li>\n<li>payment or order APIs that need request-integrity checks<\/li>\n<li>selected internal service-to-service protocols<\/li>\n<\/ul>\n<p>It is a poorer fit when the long-lived secret has to be shipped to an untrusted client such as browser JavaScript, a public desktop application, or a normal mobile app.<\/p>\n<p>Once the secret is distributed to software running on a device controlled by the user, assume that a determined user may eventually extract it.<\/p>\n<p>Code obfuscation does not turn a client-side secret into a server-side secret.<\/p>\n<h2>Common HMAC API signing mistakes<\/h2>\n<h3>Putting the secret in the URL<\/h3>\n<p>URLs can leak into browser history, reverse-proxy logs, server logs, monitoring systems, and analytics tools.<\/p>\n<h3>Signing only the request body<\/h3>\n<p>If the HTTP method, path, query string, or selected headers change the meaning of the request, they may need to be covered as well.<\/p>\n<h3>Leaving canonicalization underspecified<\/h3>\n<p>\u201cConcatenate these fields and sign them\u201d is not precise enough. URI encoding, query sorting, header whitespace, casing, line endings, and payload bytes all need clear rules.<\/p>\n<h3>Using a timestamp without replay state<\/h3>\n<p>A timestamp limits the replay period, but it does not stop the same request from being reused during that period.<\/p>\n<h3>Keeping nonce state in one API instance<\/h3>\n<p>A request may hit Server A the first time and Server B during a replay. Replay state has to work across the full deployment.<\/p>\n<h3>Consuming an unauthenticated nonce too early<\/h3>\n<p>Do not let forged requests permanently poison nonce state. Coordinate nonce checking, signature verification, and nonce consumption carefully, preferably with atomic operations.<\/p>\n<h3>Treating the Client ID as a secret<\/h3>\n<p>The Client ID identifies the credential. The secret key is the sensitive value.<\/p>\n<h3>Treating HMAC as encryption<\/h3>\n<p>HMAC does not hide the request body.<\/p>\n<pre><code>amount=100<\/code><\/pre>\n<p>The value is still readable on the wire unless the transport is encrypted. Confidentiality comes from HTTPS\/TLS, not from HMAC.<\/p>\n<h3>Having no key-rotation plan<\/h3>\n<p>Production systems should plan for:<\/p>\n<ul>\n<li>Key IDs<\/li>\n<li>short overlap periods for old and new keys<\/li>\n<li>key revocation<\/li>\n<li>key rotation<\/li>\n<li>incident response after a secret leak<\/li>\n<\/ul>\n<p>A leaked secret is much harder to replace cleanly if rotation was never designed into the protocol.<\/p>\n<h3>Skipping rate limiting<\/h3>\n<p>A correctly signed client can still overload an API.<\/p>\n<p>Apply rate limits and quotas based on the client, account, API key, or resource cost, and return HTTP 429 where appropriate.<\/p>\n<h2>HMAC API security checklist<\/h2>\n<ul>\n<li>Serve the API over HTTPS only<\/li>\n<li>Use a modern HMAC construction such as HMAC-SHA256 when appropriate<\/li>\n<li>Never send the secret key with the request<\/li>\n<li>Do not hard-code long-lived secrets in public clients<\/li>\n<li>Define the Canonical Request format precisely<\/li>\n<li>Define query-string encoding and sorting rules<\/li>\n<li>Define which headers are signed<\/li>\n<li>Decide whether the payload hash covers raw bytes or canonical serialization<\/li>\n<li>Sign every field that materially affects request semantics<\/li>\n<li>Include a timestamp and enforce a validity window<\/li>\n<li>Use a nonce for replay-sensitive endpoints<\/li>\n<li>Share replay state across API instances<\/li>\n<li>Consume accepted nonces atomically<\/li>\n<li>Use idempotency keys for high-value write operations where appropriate<\/li>\n<li>Compare signatures in constant time<\/li>\n<li>Run authorization checks after authentication<\/li>\n<li>Apply rate limits<\/li>\n<li>Support key rotation and revocation<\/li>\n<li>Keep secrets and sensitive credentials out of logs<\/li>\n<\/ul>\n<h2>HMAC authentication FAQ<\/h2>\n<h3>What is the difference between HMAC and SHA-256?<\/h3>\n<p>SHA-256 is a cryptographic hash function. Anyone with the input can calculate the hash. HMAC-SHA256 combines SHA-256 with a shared secret, so producing the correct message authentication code also requires the secret key.<\/p>\n<h3>Do I still need a token if my API uses HMAC?<\/h3>\n<p>It depends on the design. HMAC is commonly used for message authentication and integrity, while tokens often carry identity, access, or authorization information. An API can use both.<\/p>\n<h3>Can I put an HMAC secret in browser JavaScript?<\/h3>\n<p>Usually not if the value is expected to remain secret. Any long-lived secret shipped to browser code, a desktop application, or a mobile app should be treated as potentially extractable. HMAC works best when both parties can protect the shared key.<\/p>\n<h3>Can a timestamp completely prevent replay attacks?<\/h3>\n<p>No. A timestamp only limits the time during which a captured request can be reused. A replay-sensitive API normally needs another control such as a nonce, unique request identifier, or application-level idempotency mechanism.<\/p>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc2104.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 2104 \u2014 HMAC: Keyed-Hashing for Message Authentication<\/a><\/li>\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc6151.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 6151 \u2014 Updated Security Considerations for MD5 and HMAC-MD5<\/a><\/li>\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc9421.html\" target=\"_blank\" rel=\"noopener noreferrer\">RFC 9421 \u2014 HTTP Message Signatures<\/a><\/li>\n<li><a href=\"https:\/\/docs.aws.amazon.com\/IAM\/latest\/UserGuide\/reference_sigv-create-signed-request.html\" target=\"_blank\" rel=\"noopener noreferrer\">AWS \u2014 Create a signed AWS API request<\/a><\/li>\n<li><a href=\"https:\/\/docs.github.com\/en\/webhooks\/using-webhooks\/validating-webhook-deliveries\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs \u2014 Validating webhook deliveries<\/a><\/li>\n<li><a href=\"https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/REST_Security_Cheat_Sheet.html\" target=\"_blank\" rel=\"noopener noreferrer\">OWASP Cheat Sheet Series \u2014 REST Security<\/a><\/li>\n<\/ul>\n<h2>A practical rule for HMAC API design<\/h2>\n<p>The hard part of HMAC request signing is rarely the HMAC calculation itself. The hard part is defining the protocol around it.<\/p>\n<p>A production design needs clear rules for method, path, query parameters, headers, payload bytes, canonicalization, timestamps, replay protection, authorization, rate limiting, and the lifecycle of the shared secret.<\/p>\n<p>For server-to-server APIs, webhooks, and partner integrations, HMAC-SHA256 remains a practical option. But adding a custom signature layer to an API that already has a well-designed authentication and authorization protocol does not automatically make it safer.<\/p>\n<p>Start with the threat you are trying to stop\u2014tampering, credential theft, replay, unauthorized access, or abuse\u2014and choose controls that address that specific problem.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>HMAC authentication is widely used for API request signing, webhook verification, and server-to-server communication. Its job is not to encrypt a request. It lets a server verify that a message was created by someone who holds the shared secret, and that the signed parts of the request were not changed in transit. A common design<\/p>\n","protected":false},"author":1,"featured_media":84232,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[9244],"tags":[9304],"series":[],"class_list":["post-84230","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technology-guides","tag-data-security"],"jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/pdcHox-lUy","jetpack_featured_media_url":"https:\/\/www.ahhhhfs.com\/wp-content\/uploads\/2022\/08\/hmac-api-request-signing-guide.jpg","_links":{"self":[{"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/posts\/84230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/comments?post=84230"}],"version-history":[{"count":2,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/posts\/84230\/revisions"}],"predecessor-version":[{"id":84237,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/posts\/84230\/revisions\/84237"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/media\/84232"}],"wp:attachment":[{"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/media?parent=84230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/categories?post=84230"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/tags?post=84230"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/www.ahhhhfs.com\/en\/wp-json\/wp\/v2\/series?post=84230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}