honojs / hono

Web framework built on Web Standards
https://hono.dev
MIT License
16.87k stars 469 forks source link

Does hono alter the #3083

Open lane711 opened 2 days ago

lane711 commented 2 days ago

What version of Hono are you using?

3.11.7

What runtime/platform is your app running on?

cloudflare workers

What steps can reproduce the bug?

stripe listener

What is the expected behavior?

un altered post body

What do you see instead?

.

Additional information

I am trying to setup a stripe web hook listener using hono like so:

stripeApi.post(`/stripe-webhook`, async (ctx) => {

  const stipeSecret = ctx.env.STRIPE_ENDPOINT_SECRET;

  const sig = ctx.req.header('stripe-signature');
  let event;

  try {
    const stripe = require('stripe')(ctx.env.STRIPE_KEY);
    const text = await ctx.req.text()
    event = await stripe.webhooks.constructEventAsync(text, sig, stipeSecret);
    console.log(event);
  } catch (err) {
    return ctx.json(`Webhook Error: ${err.message}`, 400);
  }

// process the event here...

});

the constructEventAsync method expects the post body to be sent exactly in the format that it was received but I keep getting this error message from the constructEventAsync method: "Webhook Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? \n If a webhook request is being forwarded by a third-party tool, ensure that the exact request body, including JSON formatting and new line style, is preserved.\n\nLearn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing\n

So my question is if I call await ctx.req.text(), should I expect that am I getting the exact post body or is it altered in honor?

And I get another error if I try to use const text = await ctx.req.json();

"Webhook Error: Webhook payload must be provided as a string or a Buffer (https://nodejs.org/api/buffer.html) instance representing the _raw_ request body.Payload was provided as a parsed JavaScript object instead. \nSignature verification is impossible without access to the original signed material. \n\nLearn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing\n"

Meess commented 2 days ago

In short, try:

const bodyArrayBuffer = await ctx.req.arrayBuffer()
const bodyRaw = Buffer.from(new Uint8Array(stripeWebhookBodyArrayBuffer))
event = await stripe.webhooks.constructEventAsync(bodyRaw, sig, stipeSecret);

Longer answer: This is an error from the strip library, which nicely asks you "Are you passing the raw request body you received from Stripe?" ;).

With .text() Hono parses the body text, and similar with .json() as json, of which neither is the raw request body. I haven't worked with the stripe-node packge but the documentation for "constructEvent](https://github.com/stripe/stripe-node?tab=readme-ov-file#webhook-signing)" states:

"Please note that you must pass the raw request body, exactly as received from Stripe, to the constructEvent() function; this will not work with a parsed (i.e., JSON) request body."

So it doesn't make sense to interpret it as .text() or .json() as you need the raw body (the Buffer is guess), so I would advice to use .arrayBuffer() and make it a Node Buffer before parsing it to the contructEvent.

You can see similar examples in the examples documentation of the stripe-node library, where each framework either uses the raw body directly or extracts it as a buffer: https://github.com/stripe/stripe-node/blob/master/examples/webhook-signing/express/main.ts

lane711 commented 2 days ago

Thanks @Meess I was hoping that would have done the trick, but unfortunately, I now get this from the constructEventAsync method: Webhook Error: Buffer is not defined

The buffer does apprat to be constructed properly from what I can tell:

Screenshot 2024-07-02 at 1 53 28 PM
Meess commented 2 days ago

Hmm interesting, not sure what's going on. Maybe as you don't need to edit the the buffer you could try to use .blob(), i.e.:

const bodyRaw = await ctx.req.blob()
event = await stripe.webhooks.constructEventAsync(bodyRaw, sig, stipeSecret);
lane711 commented 2 days ago

Thx, tried that and i back to : Webhook Error: Webhook payload must be provided as a string or a Buffer ( https://nodejs.org/api/buffer.html) instance representing the raw request body.Payload was provided as a parsed JavaScript object instead. \nSignature verification is impossible without access to the original signed material. \n\nLearn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing\n

btw, here is an article explaining how to do it with plain workers: https://blog.cloudflare.com/announcing-stripe-support-in-workers

export default { async fetch(request, env) { const stripe = getStripe({ env
}) const body = await request.text() const sig = request.headers.get(
'stripe-signature') const event = await stripe.webhooks.constructEventAsync(
body sig, env.STRIPE_ENDPOINT_SECRET, undefined, webCrypto );
lane711 commented 1 day ago

Is it possible to bypass Hono for a single route and just use standard workers fetch?

I'm pretty blocked by this issue right now and trying to find any solution beyond creating a totally separate project

Meess commented 1 day ago

Disclaimer, I've never worked with Cloudflare workers.

But I read that cloudflare workers don't support Buffer, and you might want to import it from the node api, i.e. import { Buffer } from 'node:buffer';, see: https://developers.cloudflare.com/workers/runtime-apis/nodejs/buffer/#:~:text=A%20Buffer%20extends%20from%20Uint8Array,response%20%3D%20new%20Response(Buffer.

Coming back to your snippet:

btw, here is an article explaining how to do it with plain workers: https://blog.cloudflare.com/announcing-stripe-support-in-workers export default { async fetch(request, env) { const stripe = getStripe({ env }) const body = await request.text() const sig = request.headers.get( 'stripe-signature') const event = await stripe.webhooks.constructEventAsync( body sig, env.STRIPE_ENDPOINT_SECRET, undefined, webCrypto );

You can access the raw request object in hono (i.e. the 'request' in your code snippet above which you mentioned worked), via ctx.raw i.e. see https://hono.dev/docs/api/request#raw

so you could try

 const body = ctx.req.raw.text()

Although in the docs they also say ctx.raw.cf?. ... (I think related to cloudflare but as mentioned, haven't worked on Cloudflare and Hono combination yet).

Hope that gives you some new directions to explore the issue.

Meess commented 1 day ago

Btw to make sure there are the least bugs in the version of Hono you're using it's always advised to stay on the latest version, I see you're on 3.11.7 while 4.4.11 is the current latest version.

Meess commented 1 day ago

Also there is actually a hono page for stripe implementation 😶‍🌫️ : https://hono.dev/examples/stripe-webhook