What Is HMAC and Why Crypto Payment Webhooks Must Be Signed

An unsigned webhook is just an HTTP POST from an unknown source. HMAC signing is the cryptographic control that stops attackers from faking payment confirmations to your server.

April 28, 2026About 6 MinAIO Research Team
What Is HMAC and Why Crypto Payment Webhooks Must Be Signed

An unsigned webhook is just an HTTP POST request arriving at your server from an unverified source. Any attacker who knows your webhook endpoint can send a POST claiming a payment has been confirmed, and if your server processes it without verification, they receive goods or credits without paying anything. HMAC signing is the cryptographic control that prevents this. The payment gateway signs every webhook payload with a shared secret. Your server verifies the signature before processing anything. No valid signature means no processing.

What to Know
  • HMAC (Hash-based Message Authentication Code) lets your server verify that a webhook came from the payment gateway and was not modified in transit.
  • Without HMAC verification, your webhook endpoint is an unauthenticated HTTP endpoint that any attacker can POST fake payment confirmations to.
  • Implementing HMAC verification correctly takes under 20 lines of code in any language and is the single highest-impact security control in a payment integration.

What Is HMAC

HMAC is a standard cryptographic algorithm that produces a fixed-length authentication code from two inputs: a message, which is the webhook payload, and a secret key shared between the sender and recipient. The reason it works as an authentication mechanism is that hash functions are one-way: given the same message and the same key, the output is always identical, but you cannot reconstruct the key from the output. That means the resulting code, the HMAC signature, can only be reproduced by someone who holds the same secret key. Change one byte of the message or use the wrong key, and the output changes completely.

For payment webhooks, HMAC-SHA256 is the standard construction: the HMAC algorithm applied with SHA-256 as the underlying hash function, producing a 256-bit signature. This is computationally infeasible to forge without the secret key.

What a Forged Webhook Attack Looks Like

Your payment gateway sends a POST to https://yourstore.com/webhooks/payment when a transaction is confirmed. The payload looks like this:

{
  "event": "payment.confirmed",
  "invoice_id": "inv_abc123",
  "amount": "250.00",
  "currency": "USDC",
  "status": "confirmed"
}

If your webhook handler accepts any inbound POST and marks the invoice as paid, an attacker needs only to do the following:

  1. Discover your webhook endpoint URL through trial-and-error, a leaked log, or public repository search.
  2. Craft a JSON payload matching the expected format for any invoice ID in your system.
  3. POST it to your endpoint.

Your server marks inv_abc123 as paid. The attacker receives whatever that invoice was for. The actual blockchain transaction was zero.

This attack requires no cryptographic skill. It requires knowing your endpoint and the payload schema, both of which are much easier to obtain than you might expect. Endpoint paths are often predictable, and payload schemas are sometimes documented publicly.

How HMAC Stops It

With HMAC signing, the payment gateway does the following before sending each webhook:

  1. Takes the raw request body, the JSON payload as a string.
  2. Computes HMAC-SHA256(payload, secret_key).
  3. Attaches the resulting signature to the request as an HTTP header, typically X-Signature or X-HMAC-SHA256.

Your server, upon receiving the webhook:

  1. Reads the raw request body before parsing it.
  2. Independently computes HMAC-SHA256(raw_body, your_secret_key).
  3. Compares your computed signature to the one in the header using a constant-time comparison function.
  4. If signatures match: process the event. If not: return 400 and log the attempt.

An attacker cannot forge a valid signature without the secret key. Even if they capture a legitimate webhook and replay it verbatim, you can reject replays by also validating a timestamp included in the payload.

How to Implement HMAC Verification

The implementation is the same conceptually in every language. These are the exact steps:

// Step 1: Read the RAW request body as bytes/string
//         Do not parse JSON first — JSON serialisers may reorder keys
raw_body = request.raw_body()

// Step 2: Read the signature from the header
received_sig = request.headers["X-Signature"]

// Step 3: Recompute the expected signature
expected_sig = HMAC_SHA256(key=your_webhook_secret, message=raw_body)

// Step 4: Compare using constant-time comparison (prevents timing attacks)
if NOT constant_time_equals(received_sig, expected_sig):
    return HTTP 400  // Reject

// Step 5: Optionally validate timestamp to prevent replay attacks
payload = parse_json(raw_body)
if abs(now() - payload.timestamp) > 300:  // 5-minute window
    return HTTP 400  // Reject replay

// Step 6: Process the event
handle_payment_event(payload)

Two mistakes are common enough to call out explicitly:

  • Parsing JSON before computing HMAC. JSON serialisers do not guarantee key order. If you parse the payload to a dict or object and re-serialize it before computing the HMAC, your output may differ from the gateway's. Always compute HMAC on the raw body bytes.
  • Using a non-constant-time comparison. Standard string equality short-circuits on the first differing character, leaking timing information that can help an attacker brute-force the signature. Use your language's built-in constant-time comparison function, which is hmac.compare_digest() in Python and crypto.timingSafeEqual() in Node.js.

AIO's HMAC Implementation

AIO signs all payment callback events with HMAC-SHA256. The secret key is unique per integration and is set during API configuration. Key rotation is available via API, allowing zero-downtime secret updates without requiring a redeployment.

When a webhook delivery fails because your server returns a non-2xx response or times out, AIO's retry pool queues the event for redelivery with exponential backoff. This means your server must implement idempotency: processing the same trace ID twice should produce the same outcome, not duplicate fulfillments. The trace ID in the payload is the correct idempotency key.

The full lifecycle, from the initial payment event through retry delivery, is recorded against the trace ID in AIO's 30-day audit log. If a webhook delivery dispute ever arises, the log shows exactly what was sent, when, and what your server returned. For the operational context around audit logs and trace IDs, see What Is a Trace ID in Payment Processing?

HMAC webhook verification is one control in a broader security stack. For the complete picture, covering authentication, access controls, key management, and compliance, read the full Crypto Payment Security Guide for Merchants and Developers.

If you are ready to implement HMAC verification for your AIO integration, the API documentation at aio.cash provides the exact header names, signature encoding, and example verification code.

Frequently Asked Questions

What hashing algorithm does HMAC use for payment webhooks?

Most payment gateways use HMAC-SHA256, combining the HMAC construction with the SHA-256 hash function. This produces a 256-bit signature that is computationally infeasible to forge without the shared secret key.

Can an attacker replay a valid HMAC-signed webhook?

Yes — if your verification only checks the signature and not a timestamp or nonce. To prevent replay attacks, also validate that the webhook timestamp falls within an acceptable window (typically 5 minutes) of your server's current time. Reject requests outside that window even if the HMAC signature is valid.

What happens if I rotate my HMAC secret key?

Any webhooks signed with the old key will fail verification after rotation. To rotate without downtime, briefly accept both the old and new secrets during a transition window, update your secret in production, then stop accepting the old secret. AIO supports secret key rotation via API.

Related News

Continue exploring the latest updates and insights from our blog.