{"record":{"id":"022dd1758a6bd4bb","repo":"hcengineering/platform","slug":"data-is-missing-022dd1","errorCode":null,"errorMessage":"'data' is missing","messagePattern":"'data' is missing","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"services/gmail/pod-gmail/src/main.ts","lineNumber":161,"sourceCode":"\n          const { account, workspace } = decodeToken(token)\n\n          await gmailController.signout(workspace, account)\n        } catch (err) {\n          ctx.error('signout error', { message: JSON.stringify(err) })\n        }\n\n        res.send()\n      }\n    },\n    {\n      endpoint: '/push',\n      type: 'post',\n      handler: async (req, res) => {\n        try {\n          const data = req.body?.message?.data\n          if (data === undefined) {\n            res.status(400).send({ err: \"'data' is missing\" })\n            return\n          }\n          gmailController.push(data)\n\n          res.send()\n        } catch (err: any) {\n          ctx.error('Push request failed', { message: err.message })\n          res.status(500).send()\n        }\n      }\n    },\n    {\n      endpoint: '/state',\n      type: 'get',\n      handler: async (req, res) => {\n        try {\n          const token = extractToken(req.headers)\n","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/gmail/pod-gmail/src/main.ts#L143-L179","documentation":"The /push endpoint receives Google Pub/Sub push notifications for Gmail events. The Pub/Sub envelope wraps the payload in body.message.data (base64). If that field is absent, the endpoint rejects the request with 400 and this fixed message — it is a payload-shape validation, not an exception.","triggerScenarios":"POST to /push whose JSON body lacks body.message.data — e.g. a plain-text ping, a Pub/Sub 'pull'-style message posted incorrectly, or a non-Pub/Sub client calling the endpoint.","commonSituations":"Manually testing the endpoint with curl/Postman and sending the inner payload directly instead of the Pub/Sub push envelope; configuring a Pub/Sub subscription with the wrong push format (non-JSON wrapper); a load balancer rewriting the body.","solutions":["Send the full Google Pub/Sub push envelope: { \"message\": { \"data\": \"<base64>\", ... } }.","Base64-encode the Gmail notification payload before publishing to the topic.","Verify the Pub/Sub subscription is a push subscription with the default JSON envelope format."],"exampleFix":"// before\n{ \"data\": \"<base64>\" }\n// after\n{ \"message\": { \"data\": \"<base64>\", \"messageId\": \"...\", \"publishTime\": \"...\" } }","handlingStrategy":"validation","validationCode":"function isPubsubPushEnvelope(body: any): body is { message: { data: string } } {\n  return body != null && typeof body === 'object' &&\n    body.message != null && typeof body.message.data === 'string' && body.message.data.length > 0\n}\n// reject early: if (!isPubsubPushEnvelope(req.body)) return res.status(400)…","typeGuard":"function isPubsubPushEnvelope(v: unknown): v is { message: { data: string } } {\n  return typeof v === 'object' && v !== null &&\n    'message' in v && typeof (v as any).message?.data === 'string'\n}","tryCatchPattern":"try {\n  const res = await fetch(pushUrl, { method: 'POST', body: JSON.stringify(envelope) })\n  if (res.status === 400) {\n    const body = await res.json()\n    if (body?.err === \"'data' is missing\") {\n      // wrap payload in the Pub/Sub push envelope and retry\n    }\n  }\n} catch (e) { /* network error handling */ }","preventionTips":["Always publish to the topic the full Pub/Sub message; let Pub/Sub build the push envelope.","Use a Pub/Sub push subscription (default JSON wrapper) rather than posting raw payloads.","When testing manually, replicate the full envelope including base64-encoded data.","Base64-encode the inner Gmail payload before publishing."],"tags":["pubsub","validation","http-400"],"backgroundTag":"invalid-pubsub-push-envelope","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}