koala73/worldmonitor · error · ConvexError

NO_CUSTOMER

NO_CUSTOMER

Error message

NO_CUSTOMER

What it means

Structured ConvexError thrown by createCustomerPortalUrlForUser when no usable DodoPayments customer id can be resolved for the signed-in user: the lookup query returned null because the user has no subscription, or every subscription's rawPayload lacks a customer_id (rare webhook data loss). The portal URL cannot be created without a customer, so the request fails closed rather than sending a broken request to Dodo.

Solutions

  1. Confirm the user actually has (or had) a subscription in the customers/subscriptions tables
  2. If subscriptions exist, inspect their rawPayload webhook data for a missing customer_id and repair from the Dodo dashboard
  3. If the user has never subscribed, surface an in-app message instead of a portal error
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/payments/billing.ts:442 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/acd58bbba39957a6. Report an issue: GitHub.

Appendix: source

Thrown at convex/payments/billing.ts:443

 * the rawPayload and a same-user customers row still held the answer.
 */
export async function createCustomerPortalUrlForUser(
  ctx: Pick<ActionCtx, "runQuery">,
  userId: string,
): Promise<{ portal_url: string }> {
  const dodoCustomerId = await ctx.runQuery(
    internal.payments.billing.getDodoCustomerIdForUserPortal,
    { userId },
  );

  if (!dodoCustomerId) {
    // User has no subscription at all, or every sub's rawPayload lacks a
    // usable customer_id (very rare — would mean every webhook delivery
    // for this user dropped the customer field). Throw structured so
    // the client surfaces the existing "contact support" toast
    // (object-typed `data` so `err.data.kind` survives the wire — see
    // `api/_convex-error.js`).
    throw new ConvexError({ kind: "NO_CUSTOMER" });
  }

  const client = getDodoClient();
  let session;
  try {
    session = await client.customers.customerPortal.create(
      dodoCustomerId,
      { send_email: false },
    );
  } catch (err) {
    // The Dodo REST SDK throws a plain Error (APIError on a non-2xx Dodo
    // response, or a transport failure) when the portal-session create
    // fails. Convex's action runtime then masks any NON-ConvexError throw
    // as an opaque `[Request ID: X] Server Error`, dropping the real cause
    // from the wire — the exact opacity WORLDMONITOR-R5 fought for the
    // missing-customer path above (this was the last unwrapped throw site).
    // Re-throw as a structured ConvexError so the client receives
    // `err.data.kind === 'DODO_PORTAL_ERROR'` for proper Sentry

View on GitHub (pinned to 7d06c8633d)