hcengineering/platform · error

Invalid body

Error message

Invalid body

What it means

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.

Source

Thrown at services/payment/pod-payment/src/providers/stripe/webhook.ts:45

 * Documentation: https://stripe.com/docs/webhooks
 */
export async function handleStripeWebhook (
  ctx: MeasureContext,
  accountsUrl: string,
  serviceToken: string,
  webhookSecret: string,
  stripeApiKey: string,
  req: Request,
  res: Response
): Promise<void> {
  try {
    // Body is a Buffer from express.raw() middleware
    const rawBody = req.body as Buffer
    const sig = req.headers['stripe-signature'] as string

    if (!(rawBody instanceof Buffer) || rawBody.length === 0) {
      ctx.error('Invalid webhook body')
      res.status(400).json({ error: 'Invalid body' })
      return
    }

    if (sig === undefined) {
      ctx.error('Missing Stripe signature header')
      res.status(400).json({ error: 'Missing signature' })
      return
    }

    // Create Stripe instance for webhook verification
    const stripe = new Stripe(stripeApiKey, { apiVersion: '2025-02-24.acacia' })

    // Verify webhook signature and parse event
    let event: Stripe.Event
    try {
      event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)
    } catch (err: any) {
      ctx.error('Invalid Stripe webhook signature', { err })

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Register express.raw({ type: 'application/json' }) on the Stripe webhook route before the handler
  2. POST a non-empty raw JSON payload with Content-Type application/json
  3. Disable/reorder any global JSON body parser for this route so the raw body survives
  4. Check intermediary proxies for body mutation

Example fix

// before
app.post('/webhooks/stripe', handleStripeWebhook)
// after
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), handleStripeWebhook)
Defensive patterns

Strategy: validation

Validate before calling

const rawBody = req.body as Buffer
if (!(rawBody instanceof Buffer) || rawBody.length === 0) {
  throw new Error('Stripe webhook route requires express.raw() and a non-empty body')
}

Type guard

function isRawBody(body: unknown): body is Buffer {
  return body instanceof Buffer && body.length > 0
}

Prevention

When it happens

Trigger: 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).

Common situations: Framework default JSON body parser runs before the raw middleware; tests sending parsed objects; gateways like Cloudflare Workers re-serializing bodies.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/dd49bbd61467fd5d. Report an issue: GitHub.