#steven_webhooks

1 messages · Page 1 of 1 (latest)

limber rainBOT
#

👋 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.

sage tulip
#

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

weak charm
#

Could you share the raw error message in the catch block?

sage tulip
#

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"

GitHub

Node.js library for the Stripe API. . Contribute to stripe/stripe-node development by creating an account on GitHub.

weak charm
#

In your code, you didn't pass the request body in raw, i.e. JSON.stringify(req.body) modifies the original content

sage tulip
#

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

GitHub

Node.js library for the Stripe API. . Contribute to stripe/stripe-node development by creating an account on GitHub.

weak charm
sage tulip
#

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

weak charm
#

Could you share the full code including the path routing?

sage tulip
#
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"

GitHub

Node.js library for the Stripe API. . Contribute to stripe/stripe-node development by creating an account on GitHub.

weak charm
#

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());
sage tulip
#

Yes, I do have, my other code depends on it

weak charm
#

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);
  }
});
sage tulip
#

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"

GitHub

Node.js library for the Stripe API. . Contribute to stripe/stripe-node development by creating an account on GitHub.

weak charm
#

Could you comment out this app.use(..) route first to check whether your webhook endpoint works?

sage tulip
#

Do you mean
// app.use(express.json()); // To parse UTF-8 encoded JSON requests

#

?

#

tried both commented or not, the same result

weak charm
#

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?'

sage tulip
#

yes

weak charm
#

Is there any route declaration that potentially modifies the request body?

sage tulip
#

I'm comment it out and check

weak charm
#

It looks like somewhere else still modifies the request body

sage tulip
#

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

weak charm
sage tulip
#

I see,thanks

sage tulip
#

BTW, I have currency USD, YEN, Is there any API for checking subunit?

#

i.g 1.00 USD => 100, 1Yen => 1

weak charm
#

Stripe doesn’t have an API for the minor unit of a currency. This should be implemented by your system

sage tulip
#

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

weak charm
#

Stripe doesn't have an API for the smallest unit of a currency. You shouldn't use country spec API for this