koala73/worldmonitor · error · ConvexError

webhookEnvelope required for slack channel

webhookEnvelope required for slack channel

Error message

webhookEnvelope required for slack channel

What it means

Channel-shape validation in setChannel: channelType is 'slack' but args.webhookEnvelope is missing. Slack channels persist the webhook envelope as their payload, so it is required to construct the channel document.

Solutions

  1. Complete the Slack webhook capture step before saving the channel
  2. Require the webhookEnvelope field in the UI when slack is the selected channel type
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/notificationChannels.ts:501 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-15). Data as JSON: /api/errors/8e0c6e52f0a17d92. Report an issue: GitHub.

Appendix: source

Thrown at convex/notificationChannels.ts:228

    scheduleWelcome: v.optional(v.boolean()),
    // Internal-only: derived by the relay HTTP handler from Clerk, never the body.
    verifiedAccountEmail: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const { userId, channelType, webhookEnvelope, email, webhookLabel } = args;
    const existing = await ctx.db
      .query("notificationChannels")
      .withIndex("by_user_channel", (q) =>
        q.eq("userId", userId).eq("channelType", channelType),
      )
      .unique();
    const isNew = !existing;
    let channelId = existing ? String(existing._id) : "";
    const now = Date.now();
    if (channelType === "telegram") {
      throw new ConvexError("telegram channel must be linked through bot pairing");
    } else if (channelType === "slack") {
      if (!webhookEnvelope) throw new ConvexError("webhookEnvelope required for slack channel");
      const doc = { userId, channelType: "slack" as const, webhookEnvelope, verified: true, linkedAt: now };
      if (existing) { await ctx.db.replace(existing._id, doc); } else { channelId = String(await ctx.db.insert("notificationChannels", doc)); }
    } else if (channelType === "email") {
      await assertProEntitlement(ctx, userId);
      const recipient = requireVerifiedAccountEmail(email, args.verifiedAccountEmail);
      const doc = { userId, channelType: "email" as const, email: recipient, emailOwnership: "verified_account" as const, verified: true, linkedAt: now };
      if (existing) { await ctx.db.replace(existing._id, doc); } else { channelId = String(await ctx.db.insert("notificationChannels", doc)); }
    } else if (channelType === "webhook") {
      if (!webhookEnvelope) throw new ConvexError("webhookEnvelope required for webhook channel");
      const doc = { userId, channelType: "webhook" as const, webhookEnvelope, verified: true, linkedAt: now, webhookLabel };
      if (existing) { await ctx.db.replace(existing._id, doc); } else { channelId = String(await ctx.db.insert("notificationChannels", doc)); }
    } else {
      throw new ConvexError("discord channel must be set via set-discord-oauth");
    }
    if (isNew && args.scheduleWelcome === true) {
      await ctx.scheduler.runAfter(
        0,
        (internal as any).notificationChannels.queueChannelWelcome,

View on GitHub (pinned to e586b8b4b8)