{"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":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/notificationChannels.ts","lineNumber":517,"sourceCode":"      }\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      if (!args.email) throw new ConvexError(\"email required for email channel\");\n      const doc = { userId, channelType: \"email\" as const, email: args.email, 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\nexport const deleteChannel = mutation({\n  args: { channelType: channelTypeValidator },\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) throw new ConvexError(\"UNAUTHENTICATED\");\n    const userId = identity.subject;","sourceCodeStart":499,"sourceCodeEnd":535,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/notificationChannels.ts#L499-L535","documentation":"Thrown by the `setChannel` Convex mutation when `args.channelType === \"webhook\"` but `args.webhookEnvelope` is empty or undefined. The webhook channel document requires a `webhookEnvelope` payload (the URL/secret envelope), so the call is rejected before insertion. Plain string-typed ConvexError; client reads `err.data` as the message.","triggerScenarios":"Calling `setChannel({ channelType: \"webhook\" })` without a `webhookEnvelope`. Building args from a partial form state where the webhook URL/secret envelope was never populated.","commonSituations":"A custom-webhook settings UI submitted with an empty URL field; the envelope object failed to serialize before the call; the caller passed `webhookLabel` only and forgot the envelope.","solutions":["Pass a non-empty `webhookEnvelope` string: `setChannel({ channelType: \"webhook\", webhookEnvelope, webhookLabel })`.","Gate the submit on a non-empty webhook URL before constructing the envelope.","Confirm the envelope is serialized to the exact string shape the server expects (URL + secret), not left as a JS object."],"exampleFix":"// before\nawait setChannel({ channelType: \"webhook\", webhookLabel: \"CI\" });\n\n// after\nawait setChannel({ channelType: \"webhook\", webhookEnvelope: JSON.stringify({ url, secret }), webhookLabel: \"CI\" });","handlingStrategy":"validation","validationCode":"if (channelType === \"webhook\" && !webhookEnvelope) {\n  setFieldError(\"url\", \"Webhook URL/envelope is required\");\n  return;\n}\nawait setChannel({ channelType: \"webhook\", webhookEnvelope, webhookLabel });","typeGuard":"function hasWebhookEnvelope(a: { webhookEnvelope?: string }): a is { webhookEnvelope: string } {\n  return typeof a.webhookEnvelope === \"string\" && a.webhookEnvelope.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Build the envelope only after a non-empty webhook URL is provided.","Confirm the envelope is the serialized string the server expects, not a raw object."],"tags":["validation","convex","notifications","webhook"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}