koala73/worldmonitor · error · ConvexError

webhookEnvelope required for slack channel

Error message

webhookEnvelope required for slack channel

What it means

Thrown by `setChannel` when `channelType === "slack"` but `args.webhookEnvelope` is missing/empty. The slack branch stores a webhook envelope (the incoming-webhook URL/payload from Slack's OAuth install). Plain-string ConvexError; `err.data === "webhookEnvelope required for slack channel"`. Note the same guard exists for the `webhook` channel type at line 517 (`webhookEnvelope required for webhook channel`).

Source

Thrown at convex/notificationChannels.ts:501

    const existing = await ctx.db
      .query("notificationChannels")
      .withIndex("by_user_channel", (q) =>
        q.eq("userId", userId).eq("channelType", args.channelType),
      )
      .unique();

    const now = Date.now();

    if (args.channelType === "telegram") {
      if (!args.chatId) throw new ConvexError("chatId required for telegram channel");
      const doc = { userId, channelType: "telegram" as const, chatId: args.chatId, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "slack") {
      if (!args.webhookEnvelope) throw new ConvexError("webhookEnvelope required for slack channel");
      const doc = { userId, channelType: "slack" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "email") {
      if (!args.email) throw new ConvexError("email required for email channel");
      const doc = { userId, channelType: "email" as const, email: args.email, verified: true, linkedAt: now };
      if (existing) {
        await ctx.db.replace(existing._id, doc);
      } else {
        await ctx.db.insert("notificationChannels", doc);
      }
    } else if (args.channelType === "webhook") {
      if (!args.webhookEnvelope) throw new ConvexError("webhookEnvelope required for webhook channel");
      const doc = { userId, channelType: "webhook" as const, webhookEnvelope: args.webhookEnvelope, verified: true, linkedAt: now, webhookLabel: args.webhookLabel };
      if (existing) {

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Complete the Slack OAuth incoming-webhook install and capture the full webhook response as `webhookEnvelope` before calling `setChannel`.
  2. Client-side: disable save until `webhookEnvelope` is present for the slack branch.
  3. On `err.data === "webhookEnvelope required for slack channel"`, re-run the Slack install flow.

Example fix

// before
await convex.mutation(api.notificationChannels.setChannel, { channelType: "slack" });

// after — capture the incoming webhook envelope from Slack OAuth
const envelope = JSON.stringify(slackWebhookResponse);
if (channelType === "slack" && !envelope) {
  setFieldError("webhook", "Reconnect Slack to generate a webhook.");
  return;
}
await convex.mutation(api.notificationChannels.setChannel, { channelType: "slack", webhookEnvelope: envelope });
Defensive patterns

Strategy: validation

Validate before calling

if (channelType === "slack" && !webhookEnvelope) {
  setFieldError("webhook", "Reconnect Slack to generate a webhook.");
  return;
}

Type guard

function hasSlackEnvelope(args: { channelType: string; webhookEnvelope?: string }): boolean {
  return args.channelType !== "slack" || (typeof args.webhookEnvelope === "string" && args.webhookEnvelope.length > 0);
}

Try / catch

try {
  await convex.mutation(api.notificationChannels.setChannel, { channelType: "slack", webhookEnvelope: envelope });
} catch (err) {
  if (err.data === "webhookEnvelope required for slack channel") setFieldError("webhook", "Reconnect Slack.");
  else throw err;
}

Prevention

When it happens

Trigger: Calling `setChannel({ channelType: "slack" })` with no `webhookEnvelope`; the Slack OAuth install completed but the incoming-webhook response wasn't captured; the envelope is an empty string.

Common situations: The Slack OAuth redirect was handled but the webhook response payload wasn't serialized into the envelope; a partial install (user cancelled the webhook creation step); a test calling the mutation with only the channel type.

Related errors


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