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

  1. Gate the notifications UI on the user's entitlement tier (>= 1) client-side; hide the panel for free users.
  2. On `err.data.code === "PRO_REQUIRED"`, show an upgrade prompt and disable the channel action.
  3. After a Pro upgrade, await the entitlement webhook/sync before allowing channel creation.
  4. 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

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.