koala73/worldmonitor · error · ConvexError
webhookEnvelope required for webhook channel
Error message
webhookEnvelope required for webhook channel
What it means
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.
Source
Thrown at convex/notificationChannels.ts:517
}
} 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) {
await ctx.db.replace(existing._id, doc);
} else {
await ctx.db.insert("notificationChannels", doc);
}
} else {
throw new ConvexError("discord channel must be set via set-discord-oauth");
}
},
});
export const deleteChannel = mutation({
args: { channelType: channelTypeValidator },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new ConvexError("UNAUTHENTICATED");
const userId = identity.subject;View on GitHub (pinned to ffec79ac33)
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.
Example fix
// before
await setChannel({ channelType: "webhook", webhookLabel: "CI" });
// after
await setChannel({ channelType: "webhook", webhookEnvelope: JSON.stringify({ url, secret }), webhookLabel: "CI" }); Defensive patterns
Strategy: validation
Validate before calling
if (channelType === "webhook" && !webhookEnvelope) {
setFieldError("url", "Webhook URL/envelope is required");
return;
}
await setChannel({ channelType: "webhook", webhookEnvelope, webhookLabel }); Type guard
function hasWebhookEnvelope(a: { webhookEnvelope?: string }): a is { webhookEnvelope: string } {
return typeof a.webhookEnvelope === "string" && a.webhookEnvelope.length > 0;
} Prevention
- 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.
When it happens
Trigger: Calling `setChannel({ channelType: "webhook" })` without a `webhookEnvelope`. Building args from a partial form state where the webhook URL/secret envelope was never populated.
Common situations: 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.
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/c0afd636c06c7819.
Report an issue: GitHub.