vercel / nextjs-subscription-payments

Clone, deploy, and fully customize a SaaS subscription application with Next.js.
https://subscription-payments.vercel.app/
MIT License
5.77k stars 1.18k forks source link

Fix: webhook error #314

Open joaoxmb opened 3 months ago

joaoxmb commented 3 months ago

Following the current project, when I deploy to Vercel and the webhook is triggered, the following error is displayed:

❌ Error message: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? 
 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.

Learn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing

Reading the documentation, I discovered that it could be related to the payload passed to the stripe.webhooks.constructEvent() function.

Currently:

...
export async function POST(req: Request) {
    const body = await req.text();
    const sig = req.headers.get('stripe-signature') as string;
    const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
    let event: Stripe.Event;

    try {
        if (!sig || !webhookSecret)
            return new Response('Webhook secret not found.', { status: 400 });

        event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
        console.log(`🔔 Webhook received: ${event.type}`);
    } catch (err: any) {
        console.log(`❌ Error message: ${err.message}`);
        return new Response(`Webhook Error: ${err.message}`, { status: 400 });
    }
...

Solution:

...
import { NextRequest } from 'next/server';
import { headers } from 'next/headers';

export async function POST(req: NextRequest) {
    const body = await req.text();
    const sig = headers().get('stripe-signature') as string;
...

I changed the request type to NextRequest, and also used headers from Next for reading the header. With this change, I finally achieved success.

Note: When tested locally with Stripe CLI, it works perfectly; the error only occurs when deployed.

vercel[bot] commented 3 months ago

@joaoxmb is attempting to deploy a commit to the Vercel Solutions Team on Vercel.

A member of the Team first needs to authorize it.

e-roy commented 3 months ago

I tried this and it didn't seem to work for me. I did look at the same stripe documentation suggested above and changed my api/webhooks route to:

export async function POST(req: NextRequest) {
  const body = await req.text();
  // const sig = req.headers.get('stripe-signature') as string;
  const sig = headers().get('stripe-signature') as string;
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET as string;
  let event: Stripe.Event;
  console.log('🔔  Webhook received body:', body);
  console.log('🔔  Webhook received sig:', sig);
  console.log('🔔  Webhook received secret:', webhookSecret);

  const header = stripe.webhooks.generateTestHeaderString({
    payload: body,
    secret: webhookSecret
  });

  try {
    if (!sig || !webhookSecret)
      return new Response('Webhook secret not found.', { status: 400 });
    // event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
    event = stripe.webhooks.constructEvent(body, header, webhookSecret);
    console.log(`🔔  Webhook received: ${event.type}`);
  } catch (err: any) {
    console.log(`❌ Error message: ${err.message}`);
    return new Response(`Webhook Error: ${err.message}`, { status: 400 });
  }

and it worked bypassing sig... which is NOT a good solution.

Doesn't this error suggest the problem is in the request body?

ovy9086 commented 1 month ago

weird things is that running this locally does not produce an error. I forwarded my Stripe webhooks to my local machine and then I don't this error 😑

maybe some Vercel middleware intercepts the Stripe Payload and then forwards it to our webhook... and that's causing a signature miss-match ? 🤷

help ?