diff --git a/.changeset/four-stingrays-cheat.md b/.changeset/four-stingrays-cheat.md new file mode 100644 index 00000000000..9fe9abee94d --- /dev/null +++ b/.changeset/four-stingrays-cheat.md @@ -0,0 +1,5 @@ +--- +"@clerk/express": patch +--- + +Make webhook verification work with both raw and parsed request bodies diff --git a/packages/express/src/webhooks.ts b/packages/express/src/webhooks.ts index c2564373a8c..eb30f9839a0 100644 --- a/packages/express/src/webhooks.ts +++ b/packages/express/src/webhooks.ts @@ -21,9 +21,8 @@ export * from '@clerk/backend/webhooks'; * @example * ```typescript * import { verifyWebhook } from '@clerk/express/webhooks'; - * import express from 'express'; * - * app.post('/api/webhooks', express.raw({ type: 'application/json' }), async (req, res) => { + * app.post('/api/webhooks', async (req, res) => { * try { * const evt = await verifyWebhook(req); * // handle event @@ -40,8 +39,23 @@ export async function verifyWebhook(req: ExpressRequest, options?: VerifyWebhook const webRequest = incomingMessageToRequest(req); // Cloning instead of implementing the body inside incomingMessageToRequest // to make it more predictable + // we must pass in body as string not as an Object or Buffer + let serializedBody: string; + if (typeof req.body === 'string') { + serializedBody = req.body; + } else if (Buffer.isBuffer(req.body)) { + serializedBody = req.body.toString('utf8'); + } else if (req.body === undefined || req.body === null) { + serializedBody = ''; + } else { + try { + serializedBody = JSON.stringify(req.body); + } catch (error) { + throw new Error(`Failed to serialize request body: ${error instanceof Error ? error.message : 'Unknown error'}`); + } + } const clonedRequest = new Request(webRequest, { - body: req.body, + body: serializedBody, }); return verifyWebhookBase(clonedRequest, options); }