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
- Complete the Slack webhook capture step before saving the channel
- 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)