Skip to main content

Signature Verification

If webhook signing is enabled for your integration, signature verification is mandatory.

Do not treat it as an optional hardening step. It is part of the public webhook contract.

Signing inputs

Red Cloud sends these headers:

  • X-RedCloud-Timestamp
  • X-RedCloud-Signature

Verification formula:

HMAC_SHA256(secret, "${timestamp}.${rawBody}")

Where:

  • secret is your webhook signing secret
  • timestamp is the exact value of X-RedCloud-Timestamp
  • rawBody is the exact request body bytes delivered to your endpoint

Verification rules

Your handler should:

  1. read the raw body before mutating or re-serializing it
  2. extract X-RedCloud-Timestamp
  3. extract X-RedCloud-Signature
  4. compute the expected HMAC from timestamp.rawBody
  5. compare signatures using constant-time comparison
  6. reject the request if the signature is invalid

Common implementation mistake

The most common failure is verifying against parsed JSON instead of the raw body.

Wrong:

Parse JSON -> stringify object -> compute HMAC

Correct:

Read raw bytes -> compute HMAC -> compare -> then parse JSON

Timestamp handling

The timestamp is part of the signature input. Treat it as a required header.

Recommended handling:

  • reject requests with a missing timestamp
  • reject requests with a missing signature
  • optionally enforce a replay window in your own system if your security model requires it

If you enforce a replay window, keep your server clocks in sync and make sure the policy is documented internally for support teams.

Node.js example

import crypto from "node:crypto";

function verifyWebhookSignature({ rawBody, timestamp, signature, secret }) {
const payload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");

const actual = Buffer.from(signature, "utf8");
const wanted = Buffer.from(expected, "utf8");

if (actual.length !== wanted.length) {
return false;
}

return crypto.timingSafeEqual(actual, wanted);
}

Python example

import hashlib
import hmac


def verify_webhook_signature(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
payload = timestamp.encode("utf-8") + b"." + raw_body
expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)

Handler sequencing recommendation

For a production webhook handler:

  1. capture raw body
  2. verify signature
  3. parse JSON
  4. validate required fields
  5. apply idempotent event handling
  6. persist the accepted event
  7. return 200

Do not return 200 before the event is accepted by your internal processing path.

Troubleshooting signature failures

Check:

  • the raw body is actually raw and unmodified
  • the correct secret is being used
  • the timestamp header is included in the signed payload
  • hex encoding matches what your verifier expects
  • no middleware rewrites the body before verification

Likely causes:

  • body parser runs before signature verification
  • wrong secret in the environment
  • accidental whitespace or newline mutation
  • signature compared against parsed JSON instead of the raw body