{"record":{"id":"c0afd636c06c7819","repo":"koala73/worldmonitor","slug":"webhookenvelope-required-for-webhook-channel","errorCode":null,"errorMessage":"webhookEnvelope required for webhook channel","messagePattern":"webhookEnvelope required for webhook channel","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/notificationChannels.ts","lineNumber":550,"sourceCode":"      throw new ConvexError(\"telegram channel must be linked through bot pairing\");\n    } else if (args.channelType === \"slack\") {\n      if (!args.webhookEnvelope) throw new ConvexError(\"webhookEnvelope required for slack channel\");\n      const doc = { userId, channelType: \"slack\" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"email\") {\n      const email = requireVerifiedAccountEmail(args.email, identity.emailVerified === true ? identity.email : undefined);\n      const doc = { userId, channelType: \"email\" as const, email, emailOwnership: \"verified_account\" as const, verified: true, linkedAt: now };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else if (args.channelType === \"webhook\") {\n      if (!args.webhookEnvelope) throw new ConvexError(\"webhookEnvelope required for webhook channel\");\n      const doc = { userId, channelType: \"webhook\" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now, webhookLabel: args.webhookLabel };\n      if (existing) {\n        await ctx.db.replace(existing._id, doc);\n      } else {\n        await ctx.db.insert(\"notificationChannels\", doc);\n      }\n    } else {\n      throw new ConvexError(\"discord channel must be set via set-discord-oauth\");\n    }\n  },\n});\n\n// Called by the notification relay via /relay/deactivate HTTP action\n// when Telegram returns 403 or Slack returns 404/410. Ungated by design —\n// delivery-side deactivation must work after Pro lapses (see #8430).\nexport const deactivateChannelForUser = internalMutation({\n  args: { userId: v.string(), channelType: channelTypeValidator },\n  handler: async (ctx, args) => {","sourceCodeStart":532,"sourceCodeEnd":568,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/convex/notificationChannels.ts#L532-L568","documentation":"Channel-shape validation in setChannel: channelType is 'webhook' but args.webhookEnvelope is missing. Generic webhook channels persist the webhook envelope as their payload, so it is required to construct the channel document.","triggerScenarios":"Thrown at convex/notificationChannels.ts:517 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Collect the webhook URL or envelope before saving the channel","Require the webhookEnvelope field in the UI when webhook is the selected channel type"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}