Payments are experiencing issues due to temporary restrictions in Russia. If your payment does not go through, please submit a support request.Our support team is available 24/7 — we are always here to help with hosting and server issues.We are now accepting requests for dedicated server rental and colocation services in our data center.Reminder: we recommend enabling backups for additional data protection.A new VPS/VDS lineup with NVMe storage and improved performance is now available.Maintenance work on some servers has been completed. All services are operating normally.
Article5 min readViews2

Webhook Signature Mismatch: Check the Raw JSON Bytes

Two JSON payloads with identical data can have different HMAC signatures. We show a verified example with spaces and the boundaries for authenticating incoming notifications.

Comments 0

Sealed envelope with a whole purple seal and a yellow thread: preserving the integrity of the original message
In this article

The webhook arrives from the expected service, the data looks correct, but the signature does not match. The developer outputs the JSON, compares the fields, and finds no difference. The cause may lie between receiving the request and the check: the application parsed the body, then reconstructed the JSON. The values were preserved, but the original bytes were not.

For a scheme that signs the original request body, you must verify that exact body. First, preserve access to the original bytes using your framework's prescribed method, then verify the signature against the provider's contract. Parsing data for business logic must not replace the message being verified with a new serialization.

Identical data does not mean an identical message

Two sample bodies contain the same order ID and the same payment flag:

{"order_id":42,"paid":true}

{"order_id": 42, "paid": true}

For JSON, these are identical field values. The second body includes extra spaces. In local Python testing, parsing both bodies yielded equal objects, but HMAC-SHA256 with a single test key produced different results. The first body's signature passed verification on the original bytes but failed on the second variant. This is a small, reproducible test of byte-level properties, not a test of a specific store's handler.

Similar changes can be introduced by re-serialization: different field order, character representation, or a trailing newline. You cannot rely on converting an object back to JSON to restore the exact byte stream that the sender signed. If the protocol explicitly defines a canonical representation, follow its rules; self-imposed normalization for convenience does not become such a rule.

What HMAC Actually Verifies

HMAC uses a message, a secret key, and a chosen hash function. The recipient computes the expected value and compares it with the transmitted one. If the shared secret is kept secure, this allows verifying the authenticity and integrity of the message within this scheme. A standard hash without a secret key cannot solve this task on its own.

For a specific example, refer to the GitHub documentation: the recommended webhook signature is passed in the X-Hub-Signature-256 header and uses HMAC-SHA256. The original body is verified; the documentation separately warns about changes to the payload and headers by intermediaries. This is a GitHub contract; its header name cannot be automatically transferred to a payment or logistics service.

For another provider, the signed string may include the timestamp, request path, or other elements. First, determine the message composition, encoding, algorithm, and signature format. Then use a supported library or the official SDK. Guessing based on string length and brute-forcing the algorithm until a match is found does not replace a contract.

To compare secret-dependent values, use a function provided by the cryptographic library, such as hmac.compare_digest in Python. This avoids early termination of the comparison based on content. You must still validate input types and formats according to the documentation; the function name alone does not automatically make the entire handler secure.

How to Find Where the Body Changed

On test anonymized data, compare the bytes entering the handler with the bytes passed to the signature calculation. Independently verify the stages of body reading, JSON parsing, and re-serialization. The request stream may have already been read by an intermediate handler; the specific method for re-accessing it depends on the framework.

Do not log the working secret or copy full notifications containing buyer data for diagnostics. For local reproduction, an artificial body and a separate test key are sufficient. If you need to verify a live event, use a coordinated secure process, not a public signature converter.

Acceptance testing requires at least: the original body with a valid signature, a modified body with an old signature, a missing signature, and a signature from a different test key. For the scheme signing original bytes, change only a space; a rejection in this case is expected and confirms that the message content is checked, not object similarity.

A Valid Signature Does Not Cancel Other Checks

The same valid signature can accompany a re-delivery of an unchanged message. Therefore, authenticity and duplicate handling are distinct tasks. The service must recognize a repeated event in the manner specified by the contract and must not perform the business action again. GitHub uses a separate identifier for tracking deliveries; rules for other senders are checked separately.

After verifying authenticity, the remaining concerns are the event type, the link to the correct object, state transition validity, and actions already performed. For a payment notification, it is important not only to trust the source but also to correctly associate the event with the order. A signed request does not authorize applying any of the transmitted data to any object.

If the signature no longer matches after changing the handler, the first question to check is: 'Which exact bytes are we now passing into the calculation?' Disabling verification hides the answer and removes an important trust boundary. Restoring the original message allows you to find the defect while preserving that boundary.

Discussion 0

Share your experience and ask questions. Comments without links appear after editorial review.

No comments yet. Start the discussion.