koala73/worldmonitor · critical · ConvexError

DODO_API_KEY_MISSING

DODO_API_KEY_MISSING

Error message

DODO_API_KEY_MISSING

What it means

Thrown by `getDodoClient()` in convex/payments/billing.ts when the `DODO_API_KEY` environment variable is unset on the Convex deployment. It is an object-typed ConvexError (`{ kind: "DODO_API_KEY_MISSING" }`) deliberately so the client receives `err.data.kind` rather than an opaque server error — Convex's HTTP runtime drops `errorData` for string-data throws. The throw is also flagged at error level so on-call sees the config drift.

Source

Thrown at convex/payments/billing.ts:53

/**
 * Returns the direct DodoPayments REST SDK client owned by this billing module.
 *
 * This client handles billing, customer portal, and subscription operations.
 * lib/dodo.ts independently owns direct REST checkout-session creation;
 * webhook verification lives in payments/webhookHandlers.ts.
 *
 * Canonical env var: DODO_API_KEY.
 */
function getDodoClient(
  options: { timeout?: number; maxRetries?: number } = {},
): DodoPayments {
  const apiKey = process.env.DODO_API_KEY;
  if (!apiKey) {
    // Structured throw (object-typed `data`) so the client receives
    // `err.data.kind` instead of an opaque `[Request ID: X] Server Error`
    // (Convex's HTTP runtime drops `errorData` for string-data throws).
    // Surfaces a config drift bug at error level so on-call sees the real cause.
    throw new ConvexError({ kind: "DODO_API_KEY_MISSING" });
  }
  const isLive = process.env.DODO_PAYMENTS_ENVIRONMENT === "live_mode";
  return new DodoPayments({
    bearerToken: apiKey,
    ...(isLive ? {} : { environment: "test_mode" as const }),
    ...options,
  });
}

function compareEntitlementPlans(
  a: { planKey: string; validUntil: number },
  b: { planKey: string; validUntil: number },
): number {
  const tierDelta = getFeaturesForPlan(a.planKey).tier - getFeaturesForPlan(b.planKey).tier;
  if (tierDelta !== 0) return tierDelta;
  const rankDelta = (PLAN_PRECEDENCE[a.planKey] ?? 0) - (PLAN_PRECEDENCE[b.planKey] ?? 0);
  if (rankDelta !== 0) return rankDelta;
  return a.validUntil - b.validUntil;

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Set the secret on the Convex deployment: `npx convex env set DODO_API_KEY <key>` (or via the Convex dashboard).
  2. Confirm the exact variable name matches the canonical `DODO_API_KEY`.
  3. Redeploy/retrigger the billing action and verify a portal/checkout call succeeds.
  4. If rotating, add the new key before removing the old to avoid a window with no key.

Example fix

# before: env missing DODO_API_KEY

# after
npx convex env set DODO_API_KEY sk_test_xxxxxxxx
Defensive patterns

Strategy: try-catch

Validate before calling

// Server-side config: ensure the secret exists before deploy
// npx convex env set DODO_API_KEY <key>

Try / catch

try {
  const { portal_url } = await getCustomerPortalUrl({});
} catch (e: any) {
  if (e?.data?.kind === "DODO_API_KEY_MISSING") {
    showConfigError("Billing is misconfigured. Please contact support.");
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Any billing action that constructs a Dodo client (checkout, customer-portal session, subscription queries) when `DODO_API_KEY` is not set in Convex environment variables. Most commonly the first such call after a fresh deploy or environment clone.

Common situations: New Convex deployment/prod environment where the secret was never added; the key was named differently (e.g. `DODO_API_KEY` vs `DODO_PAYMENTS_API_KEY`); secret rotation removed the old value before adding the new one; staging env missing the var.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/88fe6dafea1666f2. Report an issue: GitHub.