Upon successfully checking out my cart using the built-in Stripe page, I am redirected to the successUrl route. During this process, my local test webhook is triggered as expected. However, I encountered some errors when attempting to verify that the request originated from Stripe.
Below is my webhook setup in NextJS
import { stripe } from "@/lib/stripe"
export async function POST(req: Request){
const payload = await req.json()
const sig = req.headers.get('stripe-signature') as string
console.log("sig: ", sig)
let event;
const endpointSecret = process.env.WEBHOOK_SECRET_LOCAL as string
try {
console.log("constructing event...")
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret)
} catch (error) {
console.error(error)
return new Response(`Webhook error: ${error}`, {
status: 400,
})
}
return new Response("payment confirmation route received", {
status: 200,
})
}
I've utilized the Stripe CLI to listen for the success event
stripe listen --forward-to localhost:3000/api/checkout/payment-confirmation
After completing my cart checkout, the following events are triggered...
2023-03-23 15:51:28 --> checkout.session.completed [evt_1MowyjE7TMviQOgJNbXch10w] 2023-03-23 15:51:28 --> charge.succeeded [evt_3MowyhE7TMviQOgJ09e3Q7Hu] 2023-03-23 15:51:29 --> payment_intent.succeeded [evt_3MowyhE7TMviQOgJ0OIjeNoH] 2023-03-23 15:51:29 --> payment_intent.created [evt_3MowyhE7TMviQOgJ0ovhouJM] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_3MowyhE7TMviQOgJ09e3Q7Hu] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_3MowyhE7TMviQOgJ0OIjeNoH] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_1MowyjE7TMviQOgJNbXch10w] 2023-03-23 15:51:32 <-- [400] POST http://localhost:3000/api/checkout/payment-confirmation [evt_3MowyhE7TMviQOgJ0ovhouJM]
Upon inspecting the NextJS logs, I noticed...
StripeSignatureVerificationError: 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. Signature verification is impossible without access to the original signed material. Learn more about webhook signing and explore webhook integration examples for various frameworks at https://github.com/stripe/stripe-node#webhook-signing
My interpretation of this error led me to believe that I needed to manipulate the payload argument. I attempted payload.toString()
, JSON.stringify(payload)
, and using const buf = Buffer.from(payload)
, among other methods, in an effort to convert the object into a string or buffer. However, I am still struggling to grasp the issue. Any assistance would be greatly appreciated!
--Update--
I have made adjustments to the code, but I am still encountering the generic error...
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
My response to Stripe: "I believe so?!?"
I also experimented with importing 'micro' or 'raw-body' as observed in other implementations, but encountered various errors. This prompted me to revert to the node approach. Interestingly, when passing the req.body as the first argument in constructEvent, I encountered the error message:
Argument of type 'ReadableStream | null' is not assignable to parameter of type 'string | Buffer'
👆
import { stripe } from "@/lib/stripe"
import { NextRequest } from "next/server";
import { Buffer } from "node:buffer";
export async function POST(req: NextRequest){
const buf = Buffer.from(req.toString())
// <Buffer 5b 6f 62 6a 65 63 74 20 52 65 71 75 65 73 74 5d>
const headers = req.headers
const sig = req.headers.get("stripe-signature") as string | string[]
// t=1679...,v1=888050e17...,v0=cc9b94a...
let event;
const endpointSecret = process.env.WEBHOOK_SECRET_LOCAL as string
// whsec_...
try {
console.log("constructing event...")
event = stripe.webhooks.constructEvent(buf, sig, endpointSecret)
} catch (error) {
console.error(error)
return new Response(`Webhook error: ${error}`, {
status: 400,
})
}
return new Response("payment confirmation route received", {
status: 200,
})
}
export const config = {
api: {
bodyParser: false,
},
};