From a70d5fc1a21e1f6ceda509dfcac51d0848a42350 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 2 Mar 2026 18:10:04 -0800 Subject: [PATCH] fix: use c.req.json() for Transak webhook body parsing Hono consumes the request body upstream, so c.req.raw.clone().text() returns empty. Use c.req.json() directly and re-serialize for HMAC. Co-Authored-By: Claude Opus 4.6 --- modules/rfunds/mod.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/rfunds/mod.ts b/modules/rfunds/mod.ts index d5c0c4b7..1490f382 100644 --- a/modules/rfunds/mod.ts +++ b/modules/rfunds/mod.ts @@ -155,23 +155,22 @@ routes.get("/api/transak/config", (c) => { }); routes.post("/api/transak/webhook", async (c) => { - // Clone request so we can read body twice (once for HMAC, once for JSON) - const rawBody = await c.req.raw.clone().text(); + let body: any; + try { body = await c.req.json(); } catch { return c.json({ error: "Invalid JSON" }, 400); } // HMAC verification — if TRANSAK_WEBHOOK_SECRET is set, validate signature const webhookSecret = process.env.TRANSAK_WEBHOOK_SECRET; if (webhookSecret) { const signature = c.req.header("x-transak-signature") || ""; const { createHmac } = await import("crypto"); - const expected = createHmac("sha256", webhookSecret).update(rawBody).digest("hex"); + // Re-serialize for HMAC (Transak signs the raw JSON body) + const expected = createHmac("sha256", webhookSecret).update(JSON.stringify(body)).digest("hex"); if (signature !== expected) { console.error("[Transak] Invalid webhook signature"); return c.json({ error: "Invalid signature" }, 401); } } - let body: any; - try { body = rawBody ? JSON.parse(rawBody) : await c.req.json(); } catch { return c.json({ error: "Invalid JSON" }, 400); } const { webhookData } = body; // Ack non-completion events (Transak sends multiple status updates)