koala73/worldmonitor · error · ConvexError
PRO_REQUIRED
PRO_REQUIRED
Error message
Notifications are a PRO feature. Upgrade to enable real-time and digest alerts.
What it means
Thrown by `assertProEntitlement` (used by notification-channel mutations) when the user's entitlement row is missing, expired (`validUntil < now`), or has `features.tier < 1`. Notifications are a Pro-only feature and the gate is enforced at the Convex write boundary so callers cannot bypass the edge API gate. Carries object data `{ code: "PRO_REQUIRED", message: "..." }` — note this uses `code` (not `kind` like followedCountries), so the client branches on `err.data.code`. The helper `hasProEntitlement` reads the `entitlements` table by userId.
Source
Thrown at convex/notificationChannels.ts:69
userId: string,
): Promise<boolean> {
const entitlement = await ctx.db
.query("entitlements")
.withIndex("by_userId", (q) => q.eq("userId", userId))
.first();
const tier =
entitlement && entitlement.validUntil >= Date.now()
? entitlement.features.tier
: 0;
return tier >= 1;
}
async function assertProEntitlement(
ctx: MutationCtx,
userId: string,
): Promise<void> {
if (!(await hasProEntitlement(ctx, userId))) {
throw new ConvexError({
code: "PRO_REQUIRED",
message:
"Notifications are a PRO feature. Upgrade to enable real-time and digest alerts.",
});
}
}
/**
* Queue a first-connect welcome outside the relay HTTP request lifecycle.
*
* The mutation that creates the channel schedules this action in the same
* Convex transaction as the channel insert. A Vercel-to-Convex timeout can
* therefore hide the mutation response without losing the welcome event.
*/
export const queueChannelWelcome = internalAction({
args: {
userId: v.string(),
channelType: channelTypeValidator,View on GitHub (pinned to ffec79ac33)
Solutions
- Gate the notifications UI on the user's entitlement tier (>= 1) client-side; hide the panel for free users.
- On `err.data.code === "PRO_REQUIRED"`, show an upgrade prompt and disable the channel action.
- After a Pro upgrade, await the entitlement webhook/sync before allowing channel creation.
- Ensure test/dev accounts have a seeded entitlement row with future `validUntil` and `tier >= 1`.
Example fix
// before
await convex.mutation(api.notificationChannels.setChannel, { channelType: "telegram", chatId });
// after — gate on entitlement
const { tier } = useEntitlement();
if (tier < 1) { showUpgradeModal(); return; }
try {
await convex.mutation(api.notificationChannels.setChannel, { channelType: "telegram", chatId });
} catch (err) {
if (err.data?.code === "PRO_REQUIRED") showUpgradeModal();
else throw err;
} Defensive patterns
Strategy: validation
Validate before calling
const { tier } = useEntitlement();
if (!tier || tier < 1) { showUpgradeModal(); return; } Type guard
function hasProTier(ent: { features: { tier: number } } | null): boolean {
return !!ent && ent.features.tier >= 1;
} Try / catch
try {
await convex.mutation(api.notificationChannels.setChannel, { channelType, chatId });
} catch (err) {
if (err.data?.code === "PRO_REQUIRED") showUpgradeModal();
else throw err;
} Prevention
- Gate the notifications UI on entitlement tier >= 1 client-side.
- This module uses `code` (not `kind`) — branch on err.data.code.
- After a Pro upgrade, await the entitlement sync before allowing channel creation.
- Seed test accounts with a future-dated tier >= 1 entitlement.
- Note the check requires validUntil >= now, not just tier >= 1.
When it happens
Trigger: Calling `setChannel`, `deleteChannel`, or any notification mutation as a free-tier user; an entitlement that expired (`validUntil` in the past); a missing entitlement row for a never-subscribed user; a webhook-sync race where the user just upgraded but the row isn't written yet.
Common situations: A free user finds the notifications settings panel; a Pro user's subscription lapsed and the entitlement row expired but the UI still shows the panel; a billing webhook delay after checkout; a test account without a seeded entitlement.
Related errors
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/6a4b91f37bd9c3e5.
Report an issue: GitHub.