#steven_webhooks
1 messages · Page 1 of 1 (latest)
👋 Welcome to your new thread!
⏲️ We'll be here soon! We typically respond in a few minutes, but in some cases we might need a bit more time (e.g., server's busy, you've got a complex question, etc.).
⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can start a new thread if you have another question.
🔗 This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1260029718963425392
📝 Have more to share? Add details, code, screenshots, videos, etc. below.
steventang@Stevens-Mac-mini prodweb_dev % stripe listen --forward-to localhost:8888/orders/stripe_webhook
Ready! You are using Stripe API Version [2024-06-20]. Your webhook signing secret is whsec_8f023975ac2051a6f3e762ef5249b6fd6cc218ab699db8e0ce58d74366583294 (^C to quit)
The code is like
/*
try {
let webhook_secret = "whsec_8f023975ac2051a6f3e762ef5249b6fd6cc218ab699db8e0ce58d74366583294";
event = stripe.webhooks.constructEvent(
JSON.stringify(req.body),
sig,
webhook_secret
);
res.sendStatus(200);
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
return;
}*/
it is always failing, I comment it out
Could you share the raw error message in the catch block?
sure
let me capture that
"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"
In your code, you didn't pass the request body in raw, i.e. JSON.stringify(req.body) modifies the original content
whole err:
{
type: "StripeSignatureVerificationError",
raw: {
message: "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",
},
rawType: undefined,
code: undefined,
doc_url: undefined,
param: undefined,
detail: undefined,
headers: undefined,
requestId: undefined,
statusCode: undefined,
charge: undefined,
decline_code: undefined,
payment_intent: undefined,
payment_method: undefined,
payment_method_type: undefined,
setup_intent: undefined,
source: undefined,
header: "t=1720485219,v1=c76b770a31c085b61871bec485dbde3971557d7e8703fc0e24a7dc3341d64d4b,v0=c7ea662c7d42f9c57f32fb874102e939704e00009a7cdf8cbdd714b7033cc40d",
payload: "{"id":"evt_3PaRzzH4UK1ezOVR0geNL5hi","object":"event","api_version":"2024-06-20","created":1720485219,"data":{"object":{"id":"pi_3PaRzzH4UK1ezOVR0PufEeCb","object":"payment_intent","amount":500,"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_async","client_secret":"pi_3P
I'd recommend following the guide here to pass the request body in its raw form: https://docs.stripe.com/webhooks/quickstart?lang=node
More specifically this line:
app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
mine is router.post('/stripe_webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event = req.body;
...
used to pass body and it didn't work
Could you share the full code including the path routing?
router.post('/stripe_webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event = req.body;
// enable below on production
// let webhook_secret = config.api.stripe.webhook_secret; // production
try {
// For testing
let webhook_secret = "whsec_8f023975ac2051a6f3e762ef5249b6fd6cc218ab699db8e0ce58d74366583294";
event = stripe.webhooks.constructEvent(
req.body,
sig,
webhook_secret
);
res.sendStatus(200);
} catch (err) {
res.status(400).send(`Webhook Error: ${err.message}`);
return;
}
res.sendStatus(200);
switch(event.type) {
case 'payment_intent.succeeded':
{
those sendStatus(200)s were commented out
it won't matter since failed to catch
those were happen when I commented out the constructEvent
if with raw body:
"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"
Is there anywhere else in your Express application parse the request body in other form prior to your webhook route? For example,
app.use(express.json());
Yes, I do have, my other code depends on it
That is the issue. This will parse the requests in all JSON, not in raw form before reaching to your webhook function
I'd recommend making a check to ensure that /webhook path is exempted from being parsed into JSON
For example, having webhook path route declared before app.use(express.json()) or
app.use((req, res, next) => {
if (req.originalUrl === '/webhook') {
express.raw({type: 'application/json',})(req, res, next);
} else {
express.json()(req, res, next);
}
});
It is already get parsed, if I don't call constructEvent, the body is already is event, is there any problem I use it directly?
I see
let me try
Changed to
app.use((req, res, next) => {
if (req.originalUrl === '/stripe_webhook') {
express.raw({type: 'application/json',})(req, res, next);
} else {
express.json()(req, res, next);
}
});
still fail
"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"
Could you comment out this app.use(..) route first to check whether your webhook endpoint works?
Do you mean
// app.use(express.json()); // To parse UTF-8 encoded JSON requests
?
tried both commented or not, the same result
Yes! This is just to check whether app.use(express.json()) is the one causing problem. You should also ensure that the webhook path has request parsed in the raw form after commenting out the app.use(express.json()):
router.post('/stripe_webhook', express.raw({ type: 'application/json' }), async (req, res) => {
Do you still keep express.raw({ type: 'application/json' }) in the webhook path?'
yes
Is there any route declaration that potentially modifies the request body?
I'm comment it out and check
It looks like somewhere else still modifies the request body
the same, anyway, I want to ignore it since the second question is more important, I try to calculate fee and net in
case 'payment_intent.succeeded':
case 'payment_intent.succeeded':
{
try {
// const paymentIntent = event.data.object;
const paymentIntent = event.data.object;
var fee = "";
var netAmount = "";
if (paymentIntent.charges && paymentIntent.charges.data.length > 0) {
const chargeId = paymentIntent.charges.data[0].id;
const charge = await stripe.charges.retrieve(chargeId);
const balanceTransaction = await stripe.balanceTransactions.retrieve(charge.balance_transaction);
fee = balanceTransaction.fee;
netAmount = balanceTransaction.net;
}
it won't success due to there is no charges.data[0], any idea how to fix this?
I suppose it is succeeded and everything are done, the charge object shall be ready
charges property is only available in API version 2022-08-01 or order. For newer API version. Charge ID can be found under latest_charge property. I'd recommend checking this guide about retrieving the Stripe fee: https://docs.stripe.com/expand/use-cases#stripe-fee-for-payment
I see,thanks
BTW, I have currency USD, YEN, Is there any API for checking subunit?
i.g 1.00 USD => 100, 1Yen => 1
Stripe doesn’t have an API for the minor unit of a currency. This should be implemented by your system
I found:
currency = 'usd' # Example for USD
currency_details = stripe.CountrySpec.retrieve('US')
Check if the currency has a subunit
subunit = currency_details['supported_payment_currencies'][currency]['zero_decimal']
Is it correct?
but I hope I don't need to convert to country first, pass currency directly
Stripe doesn't have an API for the smallest unit of a currency. You shouldn't use country spec API for this