{"record":{"id":"8c641b5167725731","repo":"hcengineering/platform","slug":"invalid-body","errorCode":null,"errorMessage":"Invalid body","messagePattern":"Invalid body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"services/payment/pod-payment/src/providers/polar/webhook.ts","lineNumber":43,"sourceCode":" * Uses @polar-sh/sdk for webhook validation\n * Webhooks are sent for checkout and subscription lifecycle events\n * Documentation: https://polar.sh/docs/integrate/webhooks/delivery\n */\nexport async function handlePolarWebhook (\n  ctx: MeasureContext,\n  accountsUrl: string,\n  serviceToken: string,\n  webhookSecret: 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\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    // Validate webhook signature and parse event\n    const event = validateEvent(rawBody, req.headers as Record<string, string>, webhookSecret)\n\n    // Route to appropriate handler based on event type\n    switch (event.type) {\n      case 'subscription.created':\n      case 'subscription.updated':\n      case 'subscription.active':\n      case 'subscription.canceled':\n      case 'subscription.uncanceled':\n      case 'subscription.revoked':\n        void handleSubscriptionUpdated(ctx, accountsUrl, serviceToken, event).catch((err) => {\n          ctx.error('Failed to process Polar webhook event', { event, err })\n        })\n        break","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/providers/polar/webhook.ts#L25-L61","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure express.raw({ type: '*/*' }) (or the provider-specific content type) middleware is registered on this exact route before the handler","Send the webhook POST with a non-empty raw JSON payload and Content-Type application/json","Check any reverse proxy / API gateway isn't transforming or dropping the body","If using a test client, pass the body as a raw Buffer/string, not a parsed object"],"exampleFix":"// before\napp.post('/webhooks/polar', handlePolarWebhook)\n// after\napp.post('/webhooks/polar', express.raw({ type: '*/*' }), handlePolarWebhook)","handlingStrategy":"validation","validationCode":"const rawBody = req.body as Buffer\nif (!(rawBody instanceof Buffer) || rawBody.length === 0) {\n  throw new Error('Request must be sent through express.raw(); body must be a non-empty Buffer')\n}","typeGuard":"function isRawBody(body: unknown): body is Buffer {\n  return body instanceof Buffer && body.length > 0\n}","tryCatchPattern":null,"preventionTips":["Always mount express.raw({ type: '*/*' }) on raw-signature webhook routes","Never attach express.json() globally to webhook paths","Integration-test webhooks with raw Buffer payloads","Check proxies/gateways for body re-encoding"],"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"}