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
- Complete the Slack OAuth incoming-webhook install and capture the full webhook response as `webhookEnvelope` before calling `setChannel`.
- Client-side: disable save until `webhookEnvelope` is present for the slack branch.
- 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
- Complete the Slack OAuth incoming-webhook install and capture the full response as webhookEnvelope.
- Disable save until webhookEnvelope is present for the slack branch.
- Re-run the Slack install flow if the envelope is missing.
- The same guard exists for the generic "webhook" channel type at line 517.
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
- INCOMPATIBLE_DELIVERY
- COUNTRIES_LIMIT_EXCEEDED
- TICKERS_LIMIT_EXCEEDED
- digestHour must be an integer 0–23
- digestTimezone must be a valid IANA timezone (e.g. America/N
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/8e0c6e52f0a17d92.
Report an issue: GitHub.