hcengineering/platform · error

Invalid body

Error message

Invalid body

What it means

handlePolarWebhook returns HTTP 400 'Invalid body' when the raw request body is not a non-empty Buffer. The route relies on express.raw() middleware to populate req.body as a Buffer, which validateEvent() needs for exact-byte signature verification.

Source

Thrown at services/payment/pod-payment/src/providers/polar/webhook.ts:43

 * Uses @polar-sh/sdk for webhook validation
 * Webhooks are sent for checkout and subscription lifecycle events
 * Documentation: https://polar.sh/docs/integrate/webhooks/delivery
 */
export async function handlePolarWebhook (
  ctx: MeasureContext,
  accountsUrl: string,
  serviceToken: string,
  webhookSecret: string,
  req: Request,
  res: Response
): Promise<void> {
  try {
    // Body is a Buffer from express.raw() middleware
    const rawBody = req.body as Buffer

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

    // Validate webhook signature and parse event
    const event = validateEvent(rawBody, req.headers as Record<string, string>, webhookSecret)

    // Route to appropriate handler based on event type
    switch (event.type) {
      case 'subscription.created':
      case 'subscription.updated':
      case 'subscription.active':
      case 'subscription.canceled':
      case 'subscription.uncanceled':
      case 'subscription.revoked':
        void handleSubscriptionUpdated(ctx, accountsUrl, serviceToken, event).catch((err) => {
          ctx.error('Failed to process Polar webhook event', { event, err })
        })
        break

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Ensure express.raw({ type: '*/*' }) (or the provider-specific content type) middleware is registered on this exact route before the handler
  2. Send the webhook POST with a non-empty raw JSON payload and Content-Type application/json
  3. Check any reverse proxy / API gateway isn't transforming or dropping the body
  4. If using a test client, pass the body as a raw Buffer/string, not a parsed object

Example fix

// before
app.post('/webhooks/polar', handlePolarWebhook)
// after
app.post('/webhooks/polar', express.raw({ type: '*/*' }), handlePolarWebhook)
Defensive patterns

Strategy: validation

Validate before calling

const rawBody = req.body as Buffer
if (!(rawBody instanceof Buffer) || rawBody.length === 0) {
  throw new Error('Request must be sent through express.raw(); body must be a non-empty Buffer')
}

Type guard

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

Prevention

When it happens

Trigger: express.raw() middleware not applied to the webhook route (req.body parsed as JSON object instead of Buffer); a zero-length POST body; a proxy/gateway stripping or re-encoding the body; sending form-encoded or multipart data.

Common situations: Mounting the webhook handler behind a framework that globally parses JSON bodies before the route; testing with curl without -d data; load balancers (e.g. some API gateways) that buffer and re-serialize the request.

Related errors


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