英文:
Error Verifying Request Signature - Next.JS 13 + Stripe
问题
I'm trying to verify the request signature in my Stripe webhook endpoint and I keep getting error:
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
I've tried a multitude of solutions, multiple ways of parsing the raw request body. I've also tried manually verifying the HMAC sig. Nothing has worked.
My envinronment:
Next.js 13.3.2
Stripe SDK 12.4.0
Steps to reproduce:
- Create an endpoint at: /pages/api/p/test
Code:
import Stripe from 'stripe';
import { NextApiRequest, NextApiResponse } from 'next';
import safe from 'colors/safe';
import { buffer } from 'micro';
const handler = async (
req: NextApiRequest,
res: NextApiResponse
): Promise<void> => {
const stripe = new Stripe(process.env.STRIPE_SECRET as string, {
apiVersion: '2022-11-15',
});
const webhookSecret: string = "whsec_33244....";
if (req.method === 'POST') {
const sig = req.headers['stripe-signature'] as string;
const buf = await buffer(req);
const body = buf.toString();
let event: Stripe.Event;
try {
console.log(safe.bgGreen(body))
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
} catch (err) {
// On error, log and return the error message
console.log(`❌ Error message: ${err.message}`);
res.status(400).send(`Webhook Error: ${err.message}`);
return;
}
// Successfully constructed event
console.log('✅ Success:', event.id);
// Cast event data to Stripe object
if (event.type === 'payment_intent.succeeded') {
const stripeObject: Stripe.PaymentIntent = event.data
.object as Stripe.PaymentIntent;
console.log(`🔶 PaymentIntent status: ${stripeObject.status}`);
} else if (event.type === 'charge.succeeded') {
const charge = event.data.object as Stripe.Charge;
console.log(`🔵 Charge id: ${charge.id}`);
} else {
console.warn(`⚠️ Unhandled event type: ${event.type}`);
}
// Return a response to acknowledge receipt of the event
res.json({ received: true });
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
};
export const config = {
api: {
bodyParser: false,
},
};
export default handler;
- Start dev server and start a stripe listener
stripe listen --forward-to localhost:3000/api/p/test
-
Copy the webhook signing secret generated and paste in code
-
Initiate a simulated event
stripe trigger payment_intent.succeeded
I've scoured the internet and tried everything. I'm totally stumped. I'm using buffer
from microservices package but I've tried the buffer function in the example in Stripe's docs as well. It seems to be parsing the body correctly:
console.log(body)
{
"id": "evt_3NIzhvEcQkKxoTiV1xjEMh02",
"object": "event",
"api_version": "2022-11-15",
"created": 1686772219,
"data": {
"object": {
"id": "pi_3NIzhvEcQkKxoTiV1kuXEmH6",
"object": "payment_intent",
"amount": 2000,
"amount_capturable": 0,
"amount_details": {
"tip": {
}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"client_secret": "pi_3NIzhvEcQkKxoTiV1kuXEmH6_secret_ZRnfaga3uRF5EhNXE0KwZpDpp",
"confirmation_method": "automatic",
"created": 1686772219,
"currency": "usd",
"customer": null,
"description": "(created by Stripe CLI)",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
},
"next_action": null,
"on_behalf_of": null,
"payment_method": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate,": null,
"network": null,
"request_three_d_secure,": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": {
"address": {
"city": "San Francisco",
"country": "US",
"line1": "510 Townsend",
"line2": null,
"postal_code": "94103",
"state": "CA"
},
"carrier": null,
"name": "Jenny Rosen",
"phone": null,
"tracking_number": null
},
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"transfer_data": null,
"transfer_group": null
}
},
"livemode": false,
"pending_webhooks": 2,
"request": {
"id": "req_MfPOwrw4MZsykJ",
"idempotency_key": "10108c32-75db-4e1e-abb3-b3b2673df025"
},
"type": "payment_intent.created"
}
If anyone has ANY ides, I would be most grateful!
英文:
I'm trying to verify the request signature in my Stripe webhook endpoint and I keep getting error:
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
I've tried a multitude of solutions, multiple ways of parsing the raw request body. I've also tried manually verifying the HMAC sig. Nothing has worked.
My envinronment:
Next.js 13.3.2
Stripe SDK 12.4.0
Steps to reproduce:
- Create an endpoint at: /pages/api/p/test
Code:
import Stripe from 'stripe';
import { NextApiRequest, NextApiResponse } from 'next';
import safe from 'colors/safe';
import { buffer } from 'micro';
const handler = async (
req: NextApiRequest,
res: NextApiResponse
): Promise<void> => {
const stripe = new Stripe(process.env.STRIPE_SECRET as string, {
apiVersion: '2022-11-15',
});
const webhookSecret: string = "whsec_33244....";
if (req.method === 'POST') {
const sig = req.headers['stripe-signature'] as string;
const buf = await buffer(req);
const body = buf.toString();
let event: Stripe.Event;
try {
console.log(safe.bgGreen(body))
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
} catch (err) {
// On error, log and return the error message
console.log(`❌ Error message: ${err.message}`);
res.status(400).send(`Webhook Error: ${err.message}`);
return;
}
// Successfully constructed event
console.log('✅ Success:', event.id);
// Cast event data to Stripe object
if (event.type === 'payment_intent.succeeded') {
const stripeObject: Stripe.PaymentIntent = event.data
.object as Stripe.PaymentIntent;
console.log(`💰 PaymentIntent status: ${stripeObject.status}`);
} else if (event.type === 'charge.succeeded') {
const charge = event.data.object as Stripe.Charge;
console.log(`💵 Charge id: ${charge.id}`);
} else {
console.warn(`🤷♀️ Unhandled event type: ${event.type}`);
}
// Return a response to acknowledge receipt of the event
res.json({ received: true });
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
};
export const config = {
api: {
bodyParser: false,
},
};
export default handler;
- Start dev server and start a stripe listener
stripe listen --forward-to localhost:3000/api/p/test
-
Copy the webhook signing secret generated and paste in code
-
Initiate a simulated event
stripe trigger payment_intent.succeeded
I've scoured the internet and tried everything. I'm totally stumped. I'm using buffer
from microservices package but I've tried the buffer function in the example in Stripe's docs as well. It seems to be parsing the body correctly:
console.log(body)
{
"id": "evt_3NIzhvEcQkKxoTiV1xjEMh02",
"object": "event",
"api_version": "2022-11-15",
"created": 1686772219,
"data": {
"object": {
"id": "pi_3NIzhvEcQkKxoTiV1kuXEmH6",
"object": "payment_intent",
"amount": 2000,
"amount_capturable": 0,
"amount_details": {
"tip": {
}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"client_secret": "pi_3NIzhvEcQkKxoTiV1kuXEmH6_secret_ZRnfaga3uRF5EhNXE0KwZpDpp",
"confirmation_method": "automatic",
"created": 1686772219,
"currency": "usd",
"customer": null,
"description": "(created by Stripe CLI)",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
},
"next_action": null,
"on_behalf_of": null,
"payment_method": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": {
"address": {
"city": "San Francisco",
"country": "US",
"line1": "510 Townsend St",
"line2": null,
"postal_code": "94103",
"state": "CA"
},
"carrier": null,
"name": "Jenny Rosen",
"phone": null,
"tracking_number": null
},
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"transfer_data": null,
"transfer_group": null
}
},
"livemode": false,
"pending_webhooks": 2,
"request": {
"id": "req_MfPOwrw4MZsykJ",
"idempotency_key": "10108c32-75db-4e1e-abb3-b3b2673df025"
},
"type": "payment_intent.created"
}
If anyone has ANY ides, I would be most grateful!
答案1
得分: 0
问题在于Next.JS有一个中间件,用来操作请求的原始主体。尝试为您的路由关闭bodyParser:https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config,否则您需要确保获取原始请求。
当您在将body
传递给constructEvent()
之前记录端点中的body
时,您应该看到一堆二进制数据(这是缓冲数据类型的样子)。这表示您实际上有原始主体。如果您看到JSON或字符串,则说明原始主体已被修改,您的Webhook签名验证将失败。
英文:
The issue is that Next.JS has middleware that manipulates the raw body of the request. Try turning off bodyParser for your route: https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config, otherwise you are going to need to make sure you get the raw request.
When you log the body
in your endpoint before passing it to constructEvent()
you should see a bunch of binary (which is what a buffer data type looks like). That is an indication you actually have the raw body. If you see JSON or a string then you know that the raw body has been manipulated and your Webhook signature verification will fail.
答案2
得分: 0
问题是我在测试密钥的末尾缺少一个字符。
英文:
My problem was that I had a character missing from the end of my test key.
答案3
得分: 0
由于Stripe Webhook有两个密钥,一个用于本地主机,另一个用于生产环境,所以您可能会遇到密钥问题。您可以获取这个密钥,进入开发者选项,然后选择Webhook,再选择签名密钥。
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论