BEFORE YOU START

Integrating high-security Webhooks like Stripe or payment gateways, but your HMAC SHA256 signature verification always fails? This is the most common trap for developers: 90% of the time, it's because you parsed the JSON payload before verifying the signature, destroying the original Raw Body.

FOLLOW ALONGOpen the live Webhook Tester workspace
01

Stop Formatting, Capture the Raw Body

Signature verification is unforgiving. A single missing space or newline completely changes the hash. Many web frameworks (like Gin or Express) automatically parse the JSON body. Once parsed, the original byte stream is lost. You must intercept and read the untouched, unformatted Raw Body bytes before any middleware touches it.

EXAMPLE
// Go example: Read Raw Body
bodyBytes, err := io.ReadAll(c.Request.Body)
// Must write the body back so subsequent handlers can read it
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
02

Extract Signature and Timestamp from Headers

Secure Webhooks inject a signature and timestamp into the HTTP Headers. Read the official documentation carefully—common header names are X-Signature or Stripe-Signature . Extract these key values for verification.

03

Concatenate the Signed String Strictly

Check the provider's concatenation rules. The most common format is Timestamp + "." + Raw Body . Never serialize your parsed Struct back to a JSON string for this step—the ordering and spacing will inevitably differ from the original, causing verification failure.

04

Compute Hash using HMAC SHA256

Use the Webhook Secret (obtained from your provider's dashboard) as the Key, and the concatenated string from the previous step as the Message. Compute the HMAC SHA256 hash. The result is typically encoded as a Hexadecimal string or Base64.

EXAMPLE
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write([]byte(signedPayload))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
05

Prevent Timing Attacks with Constant-Time Comparison

When comparing your computed signature against the header signature, never use the standard == operator. This exposes your endpoint to Timing Attacks. In Go, you must use hmac.Equal to perform a secure, constant-time comparison.

EXAMPLE
if !hmac.Equal([]byte(expectedSignature), []byte(headerSignature)) {
    return errors.New("signature mismatch")
}
06

Verify Time Window to Block Replay Attacks

A valid signature doesn't guarantee absolute security. Hackers can intercept a legitimate old request and resend it (Replay Attack). You must compare the header timestamp against your server's current time. If the difference exceeds the Tolerance Window (usually 5 minutes), reject the request immediately.

Key takeaways
  • Always use the untouched Raw Body bytes for signature computation
  • Remember to write the Raw Body back into the Request stream after reading
  • Use hmac.Equal for string comparison to prevent Timing Attacks
  • Enforce a strict timestamp tolerance (e.g., 5 minutes) to block Replay Attacks