#lipefsilva_webhooks
1 messages ¡ Page 1 of 1 (latest)
đ Welcome to your new thread!
â˛ď¸ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
âąď¸ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always 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/1418283792212431038
đ Have more to share? Add more details, code, screenshots, videos, etc. below.
Hi there
Stripe signature validation error
const AWS = require('aws-sdk')
const serverless = require('serverless-http')
const express = require('express')
var bodyParser = require('body-parser')
const { Graph, GrinAuth, logger, profiler } = require('@grin-rnd/grin-api-sdk')
const { grintegrationsAuthMiddleware } = require('./middlewares')
const { initializeCloudfrontSigner } = require('../common/utils/mediaUtils')
if (process.env.IS_OFFLINE === 'true') {
console.log(`Debug mode detected - configuring...`)
require('dotenv').config()
AWS.config.credentials = new AWS.Credentials({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})
}
const app = express()
app.use((req, res, next) => {
if (req.originalUrl.endsWith('/v1/stripe/webhook')) {
bodyParser.raw({ type: '*/*' })(req, res, next);
} else {
bodyParser.json()(req, res, next);
}
})
this is the root of the API
This is the specific route code:
const express = require('express')
const Stripe = require('stripe')
const axios = require('axios')
const bodyParser = require('body-parser')
const { logger } = require('@grin-rnd/grin-api-sdk')
const router = express.Router()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2019-12-03',
})
router.post(
'/webhook',
async (req, res) => {
try {
const sigHeader = req.headers['stripe-signature']
const sig = Array.isArray(sigHeader) ? sigHeader[0] : sigHeader.toString()
logger.info("Stripe sig header", sig)
// Debug information about the request body
logger.info("Request body debug", {
length: req.body ? req.body.length : 0,
isBuffer: Buffer.isBuffer(req.body),
bodyType: typeof req.body,
hasBody: !!req.body,
contentType: req.headers['content-type']
})
let event
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
)
} catch (err) {
logger.error('â ď¸ Stripe signature verification failed', { error: err.message })
return res.status(400).send(`Webhook Error: ${err.message}`)
}
// ...
and here is the error:
ERROR RequestId: 9d8893b3-0f54-44ef-9683-4c31fa48bad9 â ď¸ Stripe signature verification failed {"env":"staging","traceId":"9d8893b3-0f54-44ef-9683-4c31fa48bad9","error":"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://docs.stripe.com/webhooks/signature\n"}
WARN RequestId: 9d8893b3-0f54-44ef-9683-4c31fa48bad9 400: POST /api/v1/stripe/webhook {"statusCode":400,"endpoint":"POST /api/v1/stripe/webhook","body":"Webhook Error: 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://docs.stripe.com/webhooks/signature\n","took":2,"env":"staging","traceId":"9d8893b3-0f54-44ef-9683-4c31fa48bad9"}
Yeah most likely the issue is with using bodyParser
You don't want to do anything to the raw request body otherwise verification will fail.
One thing you can do is log out the req.body before you pass it to constructEvent()
It should be binary here
INFO RequestId: 36c333d6-94e1-41f0-90d3-e6030aa1f9dc Request body debug {"env":"staging","traceId":"36c333d6-94e1-41f0-90d3-e6030aa1f9dc","length":4325,"isBuffer":true,"bodyType":"object","hasBody":true,"contentType":"application/json; charset=utf-8"}
this is before constructing the event
Can you log out the actual req.body?
Hi there,
taking over from my colleague who had to step away.
Have you tried to implement the webhook endpoint on your integration without using bodyParser?
Yep, but I may have missed something
let me try that again
app.use((req, res, next) => {
if (req.originalUrl === `${process.env.API_PATH}/v1/stripe/webhook`) {
next();
} else {
bodyParser.json()(req, res, next);
}
})
@polar vine is this how it should be?
got the same error this way :(
The log you are sharing also doesn't seem right. It should be binary.
Let me debug this
does it look correct now?
I'm currently using this test route
const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2019-12-03',
});
app.post(`${process.env.API_PATH}/test-stripe-webhook`, async (req, res) => {
const payload = req.body;
const sig = req.headers['stripe-signature'];
logger.info('Test route received webhook', {
signatureHeader: sig,
bodyType: typeof payload,
bodyLength: payload ? payload.length : 0,
isBuffer: Buffer.isBuffer(payload),
contentType: req.headers['content-type']
});
try {
// Try to construct the event with the webhook secret
const event = stripe.webhooks.constructEvent(
payload,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
logger.info('Signature verification successful!', {
eventType: event.type,
eventId: event.id
});
return res.status(200).json({
success: true,
message: 'Signature verified successfully',
eventId: event.id,
eventType: event.type
});
} catch (err) {
logger.error('Test route signature verification failed', { error: err.message });
return res.status(400).json({
success: false,
message: `Webhook Error: ${err.message}`,
headersSent: req.headersSent
});
}
});
Where does this come from?
test trigger
it is specifically that logger
logger.info('Test route received webhook', {
signatureHeader: sig,
bodyType: typeof payload,
bodyLength: payload ? payload.length : 0,
isBuffer: Buffer.isBuffer(payload),
contentType: req.headers['content-type']
});
The body still looks slightly different then when I simply console.log the body of an event:
<Buffer 7b 0a 20 20 22 69 64 22 3a 20 22 65 76 74 5f 33 53 38 6c 73 67 4c 55 34 5a 71 5a 42 70 70 62 30 4c 66 6c 4f 41 61 58 22 2c 0a 20 20 22 6f 62 6a 65 63 ... 1997 more bytes>
I'm working with serverless, do you think this could be related to the issue?
Shouldn't be an issue. Let me check a few things...
did you make sure that the webhook endpoint calls pass the if condition and not run (accidently) through the else statement? Just making sure.
yep, tested that as well
i'm investigating to see if something else may be processing it
here is the full file
Can you try something and add express.raw({type: "application/json"}) to your app.use funciton before calling next?
Should look like this
app.use((req, res, next) => {
if (req.originalUrl === `${process.env.API_PATH}/v1/stripe/webhook` ||
req.originalUrl === `${process.env.API_PATH}/test-stripe-webhook`) {
logger.info('redirecting to stripe webhook!')
express.raw({ type: 'application/json' })(req, res, next);
} else {
bodyParser.json()(req, res, next);
}
})
also I would recommend removing all middleware for now to have a most basic setup
sure
applied the raw arg
let me check if there is any middleware
there was only one middleware for auth, which was already being ignored
i removed it but got the same result
this is the current error:
ERROR RequestId: 5175dc4d-8ac9-471f-be24-701f0e9b6572 Test route signature verification failed {"env":"staging","traceId":"5175dc4d-8ac9-471f-be24-701f0e9b6572","error":"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://docs.stripe.com/webhooks/signature\n"}
WARN RequestId: 5175dc4d-8ac9-471f-be24-701f0e9b6572 400: POST /api/test-stripe-webhook {"statusCode":400,"endpoint":"POST /api/test-stripe-webhook","body":"{\"success\":false,\"message\":\"Webhook Error: 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://docs.stripe.com/webhooks/signature\\n\"}","took":5,"env":"staging","traceId":"5175dc4d-8ac9-471f-be24-701f0e9b6572"}
Give me moment, I will talk to one of my colleagues about this.
thank you
One more question, you said you are are serverless, right? And I see AWS. Did you read our callout about AWS API Gateway here: https://docs.stripe.com/webhooks/signature#aws-api-gateway-with-lambda-function?
Yes, and no, I didn't read this part of the docs
let me try it
the first thing I tried was using the rawBody though
HI đ
I'm stepping in as my colleague needs to go.
hey
can you add this other account of mine
it has nitro for me to send a long message
a sec
Validating webhook signatures in Express has been a long-standing pain point. We also have this Github Issue where various strategies are discussed on how to work around it.
@hasty basin
These are limitations of the Express framework and, in your case, AWS lambda.
Applying rawBody:
const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2019-12-03',
});
app.post(`${process.env.API_PATH}/test-stripe-webhook`, async (req, res) => {
const payload = req.rawBody;
logger.info("raw body!", payload)
const sig = req.headers['stripe-signature'];
logger.info('Test route received webhook', {
signatureHeader: sig,
bodyType: typeof payload,
bodyLength: payload ? payload.length : 0,
isBuffer: Buffer.isBuffer(payload),
contentType: req.headers['content-type']
});
try {
// Try to construct the event with the webhook secret
const event = stripe.webhooks.constructEvent(
payload,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
logger.info('Signature verification successful!', {
eventType: event.type,
eventId: event.id
});
return res.status(200).json({
success: true,
message: 'Signature verified successfully',
eventId: event.id,
eventType: event.type
});
} catch (err) {
logger.error('Test route signature verification failed', { error: err.message });
return res.status(400).json({
success: false,
message: `Webhook Error: ${err.message}`,
headersSent: req.headersSent
});
}
});
Bash:
INFO RequestId: 8cc0ba71-8a59-438a-8d7b-ab8035939db2 raw body! {"env":"staging","traceId":"8cc0ba71-8a59-438a-8d7b-ab8035939db2"}
INFO RequestId: 8cc0ba71-8a59-438a-8d7b-ab8035939db2 Test route received webhook {"env":"staging","traceId":"8cc0ba71-8a59-438a-8d7b-ab8035939db2","signatureHeader":"t=1758221502,v1=0037394d4f22a3d496625eae3b719768ef0ef3024a1adf2714276153020e4617,v0=52b8fd919fc5e6dfb45a90552d172aab097b4f2fbf1f1ba8ad2e895c55fe1ec7","bodyType":"undefined","bodyLength":0,"isBuffer":false,"contentType":"application/json; charset=utf-8"}
ERROR RequestId: 8cc0ba71-8a59-438a-8d7b-ab8035939db2 Test route signature verification failed {"env":"staging","traceId":"8cc0ba71-8a59-438a-8d7b-ab8035939db2","error":"No webhook payload was provided."}
WARN RequestId: 8cc0ba71-8a59-438a-8d7b-ab8035939db2 400: POST /api/test-stripe-webhook {"statusCode":400,"endpoint":"POST /api/test-stripe-webhook","body":"{\"success\":false,\"message\":\"Webhook Error: No webhook payload was provided.\"}","took":0,"env":"staging","traceId":"8cc0ba71-8a59-438a-8d7b-ab8035939db2"}
No webhook payload was provided
THis sounds like you need to validatepayload
like, the weird is that the payload is being sent by the stripe cli test trigger
and im not changing it in any ways
please take a look in the full file, with focus in the test route
It does not appear to be making it to your webhook handling code
The error message is quite clear
it does work when I'm not validating the signature
if I just process it to json and not validate
I would expect a much larger log line here
INFO RequestId: 8cc0ba71-8a59-438a-8d7b-ab8035939db2 raw body! {"env":"staging","traceId":"8cc0ba71-8a59-438a-8d7b-ab8035939db2"}
if you had actual data in the value
when I use the .body, instead of the .rawBody, it has a lot of data
let me show
i get this when using the .body property
And that doesn't validate either , does it?
no, i get this error:
ERROR RequestId: 2807a50b-4ed0-4cd1-9d19-8729698250bb Test route signature verification failed {"env":"staging","traceId":"2807a50b-4ed0-4cd1-9d19-8729698250bb","error":"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://docs.stripe.com/webhooks/signature\n"}
WARN RequestId: 2807a50b-4ed0-4cd1-9d19-8729698250bb 400: POST /api/test-stripe-webhook {"statusCode":400,"endpoint":"POST /api/test-stripe-webhook","body":"{\"success\":false,\"message\":\"Webhook Error: 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://docs.stripe.com/webhooks/signature\\n\"}","took":2,"env":"staging","traceId":"2807a50b-4ed0-4cd1-9d19-8729698250bb"}
Okay, that error can be caused by failures in the request body, signature in the headers, or the signing secret.
i was looking at this comment in the thread you sent:
Figured it out!
In the AWS admin, go to your API gateway endpoint for your webhook, then go to the integration request.
Open the Body Mapping Templates section then select application/json -- Scroll down and you'll see your body mapping template
Add this line: "rawbody": "$util.escapeJavaScript($input.body)",
I'm using Serverless, and needed to redeploy my whole stack. You'll have to do similar with whatever tool you're using to get API gateway to send the rawbody attribute (this didn't seem necessary for me for other mappings like IP address, so not sure whats going on)
In your Lambda function, you should be able to use event.rawbody as is, without unescaping. Here's my code (node.js)
var endpointSecret = "whsec_XXXXXXXX";
var headers = JSON.parse(event.headers);
var stripeEvent = stripe.webhooks.constructEvent(event.rawbody, headers["Stripe-Signature"], endpointSecret);
I was just going to filter requests by IP (checking against the list of Stripe IPs) but this is probably a more future-proof technique.
I don't know if that's my case yet, but may be related?
I'm not certain as I don't have a lot of experience integrating with AWS lambda. We share this GH issue because it has so many crowd-sourced solutions that apply to different edge cases.
You might give it a try
Thank you
How can I check each case?
You need to validate that each value is what you expect it to be.
It's tiresome and manual but it's really the only way.
@analog turret what should I see if I log that sig variable?
router.post('/webhook', async (req, res) => {
try {
let event
if (process.env.ENV !== 'staging') {
const sig = req.headers['stripe-signature']
event = stripe.webhooks.constructEvent(req.rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET)
} else {
event = req.body
}
We document the template for the signature here: https://docs.stripe.com/webhooks/signature#check-the-signature
Yea, it seems a lot different:
{"0":"t","1":"=","2":"1","3":"7","4":"5","5":"8","6":"2","7":"2","8":"3","9":"1","10":"8","11":"9","12":",","13":"v","14":"1","15":"=","16":"1","17":"a","18":"5","19":"e","20":"b","21":"4","22":"1","23":"e","24":"8","25":"b","26":"c","27":"8","28":"7","29":"8","30":"4","31":"2","32":"7","33":"4","34":"0","35":"8","36":"b","37":"3","38":"c","39":"7","40":"3","41":"b","42":"4","43":"7","44":"b","45":"a","46":"2","47":"6","48":"1","49":"8","50":"7","51":"4","52":"a","53":"f","54":"4","55":"1","56":"1","57":"7","58":"a","59":"6","60":"7","61":"3","62":"0","63":"1","64":"e","65":"2","66":"4","67":"a","68":"5","69":"a","70":"b","71":"9","72":"c","73":"8","74":"8","75":"d","76":"7","77":"6","78":"3","79":"7","80":",","81":"v","82":"0","83":"=","84":"6","85":"6","86":"1","87":"5","88":"8","89":"9","90":"9","91":"8","92":"4","93":"3","94":"c","95":"9","96":"9","97":"6","98":"2","99":"f","100":"d","101":"c","102":"7","103":"9","104":"c","105":"5","106":"8","107":"5","108":"2","109":"b","110":"8","111":"d","112":"a","113":"d","114":"3","115":"1","116":"d","117":"5","118":"7","119":"c","120":"d","121":"7","122":"4","123":"9","124":"b","125":"3","126":"b","127":"8","128":"8","129":"9","130":"d","131":"2","132":"d","133":"6","134":"c","135":"e","136":"f","137":"7","138":"e","139":"c","140":"3","141":"f","142":"d","143":"7","144":"f","145":"8","146":"9","147":"7","env":"staging","traceId":"43c38e19-1393-46ac-bc69-b58f8ecdba7b"}
Okay that looks like somehow the string got converted into an array of characters
It should be just a single string
@analog turret does it look correct now?
t=1758223588,v1=9112d4f617934104aa0c0abe0ca6891d55af407576a9b642e755857281ca1b08,v0=b1460803a0ed365b04f22d7d42cde236227bb483701a99583a39688c572601fb
That looks better and matches the format we expect
is there a workaround from providing the rawbody?
i seem to not be able to receive the binary
maybe can I convert it into binary?
Unfortunately I'm not aware of any. Usually we want to avoid any modification since even introducing an extra space would invalidate the event.
i think this modification is happening because of the API Gateway
let me try something