Request Data's raw body text mutates incoming webhook body — HMAC signature verification fails (Razorpay)

I’m receiving webhooks from Razorpay in a Bubble backend workflow and validating them with HMAC-SHA256, as their docs require: hash the raw request body with the webhook secret and compare against the X-Razorpay-Signature header.

Verification works for some events but consistently fails for others. I’ve narrowed down the cause:

Razorpay signs a JSON body with escaped forward slashes, e.g.:slight_smile:

"product_id":"gid:\/\/shopify\/Product\/1234567891234"

But Request Data's raw body text in my backend workflow gives me:

"product_id":"gid://shopify/Product/1234567891234"

So Bubble appears to decode the \/ escape sequences before exposing the “raw” body — the bytes I hash are not the bytes Razorpay signed, and the signature can never match. Events whose payloads contain no slashes verify fine, which is why it looked intermittent at first.

I confirmed this outside Bubble: pointing the same webhook at a small FastAPI server that hashes the true wire bytes, every event (including the previously failing ones) validates with the same secret. So the secret and the Razorpay side are fine.

Questions:

  1. Is there any way in Bubble to access the true, unmodified raw bytes of an incoming request body in a backend workflow? Or is raw body text always post-processed?
  2. Has anyone solved HMAC webhook verification on Bubble for providers that escape slashes (Razorpay, and I believe some PHP-based APIs do the same)? Did you find a reliable workaround inside Bubble itself?

You’ll need to use a middleware to properly validate HMAC signature.

Building a Cloudflare Worker is probably the best option, in my opinion, cause its free, fast and serverless. Instead of sending the webhook directly to Bubble, send it to Cloudflare worker’s URL, validade it and only when validation passes, send its payload to Bubble.

You could also make it work on n8n (if you’re already familiar with it and if you also have it already running) or even use hookdeck.

Then make sure to use an Authorization token between Cloudflare Worker and Bubble