{"record":{"id":"dd49bbd61467fd5d","repo":"hcengineering/platform","slug":"invalid-body-dd49bb","errorCode":null,"errorMessage":"Invalid body","messagePattern":"Invalid body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"services/payment/pod-payment/src/providers/stripe/webhook.ts","lineNumber":45,"sourceCode":" * Documentation: https://stripe.com/docs/webhooks\n */\nexport async function handleStripeWebhook (\n  ctx: MeasureContext,\n  accountsUrl: string,\n  serviceToken: string,\n  webhookSecret: string,\n  stripeApiKey: string,\n  req: Request,\n  res: Response\n): Promise<void> {\n  try {\n    // Body is a Buffer from express.raw() middleware\n    const rawBody = req.body as Buffer\n    const sig = req.headers['stripe-signature'] as string\n\n    if (!(rawBody instanceof Buffer) || rawBody.length === 0) {\n      ctx.error('Invalid webhook body')\n      res.status(400).json({ error: 'Invalid body' })\n      return\n    }\n\n    if (sig === undefined) {\n      ctx.error('Missing Stripe signature header')\n      res.status(400).json({ error: 'Missing signature' })\n      return\n    }\n\n    // Create Stripe instance for webhook verification\n    const stripe = new Stripe(stripeApiKey, { apiVersion: '2025-02-24.acacia' })\n\n    // Verify webhook signature and parse event\n    let event: Stripe.Event\n    try {\n      event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)\n    } catch (err: any) {\n      ctx.error('Invalid Stripe webhook signature', { err })","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/providers/stripe/webhook.ts#L27-L63","documentation":"handleStripeWebhook returns HTTP 400 'Invalid body' when req.body is not a non-empty Buffer. Stripe signature verification (stripe.webhooks.constructEvent) requires the exact raw bytes, so the route expects express.raw() middleware output.","triggerScenarios":"express.raw() middleware missing on the route so req.body is a parsed JS object; empty POST body; a body-transforming proxy; client sends no payload (e.g. GET-like health probe hitting the webhook URL).","commonSituations":"Framework default JSON body parser runs before the raw middleware; tests sending parsed objects; gateways like Cloudflare Workers re-serializing bodies.","solutions":["Register express.raw({ type: 'application/json' }) on the Stripe webhook route before the handler","POST a non-empty raw JSON payload with Content-Type application/json","Disable/reorder any global JSON body parser for this route so the raw body survives","Check intermediary proxies for body mutation"],"exampleFix":"// before\napp.post('/webhooks/stripe', handleStripeWebhook)\n// after\napp.post('/webhooks/stripe', express.raw({ type: 'application/json' }), handleStripeWebhook)","handlingStrategy":"validation","validationCode":"const rawBody = req.body as Buffer\nif (!(rawBody instanceof Buffer) || rawBody.length === 0) {\n  throw new Error('Stripe webhook route requires express.raw() and a non-empty body')\n}","typeGuard":"function isRawBody(body: unknown): body is Buffer {\n  return body instanceof Buffer && body.length > 0\n}","tryCatchPattern":null,"preventionTips":["Mount express.raw({ type: 'application/json' }) before the Stripe webhook handler","Exclude webhook paths from global JSON body parsing","Send raw payloads in tests, not parsed objects","Confirm gateways don't rewrite request bodies"],"tags":["http","webhook","validation","request-body"],"backgroundTag":"webhook-body-invalid","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}