koala73/worldmonitor · error · ConvexError
NO_CUSTOMER
NO_CUSTOMER
Error message
NO_CUSTOMER
What it means
Thrown in `createCustomerPortalUrlForUser` when the user has no Dodo `customer_id` — either they have no subscription at all, or every subscription's `rawPayload` lacks a usable customer field. It is an object-typed ConvexError (`{ kind: "NO_CUSTOMER" }`) so the client surfaces the existing 'contact support' toast via `err.data.kind`.
Source
Thrown at convex/payments/billing.ts:442
* 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 SentryView on GitHub (pinned to ffec79ac33)
Solutions
- Confirm the user actually has a Dodo subscription (check `subscriptions` table for their userId).
- If they should have one, inspect the stored `rawPayload` for a missing `customer_id` and re-process the originating webhook.
- Surface a 'contact support' / 'no active subscription' message client-side rather than retrying the portal call.
- For genuinely free users, hide the portal button until a subscription exists.
Example fix
// before
const { portal_url } = await getCustomerPortalUrl({});
// after
const subs = await listMySubscriptions({});
if (subs.length === 0) { showToast("No active subscription to manage."); return; }
const { portal_url } = await getCustomerPortalUrl({}); Defensive patterns
Strategy: validation
Validate before calling
const subs = await listMySubscriptions({});
if (subs.length === 0) {
showToast("No active subscription to manage.");
return;
}
await getCustomerPortalUrl({}); Try / catch
try {
const { portal_url } = await getCustomerPortalUrl({});
} catch (e: any) {
if (e?.data?.kind === "NO_CUSTOMER") {
showToast("No subscription found. Contact support if this is unexpected.");
return;
}
throw e;
} Prevention
- Hide the 'manage subscription' button for users with no subscription.
- Treat NO_CUSTOMER as a terminal user-facing state, not a retryable error.
When it happens
Trigger: A user opens the billing/portal flow (`getCustomerPortalUrl` or the edge-gateway `internalGetCustomerPortalUrl`) but has never had a subscription created through Dodo, so no customer record resolves. Also fires in the rare case where all webhook deliveries for the user dropped the `customer_id` field.
Common situations: Free-tier or trial user clicking 'manage subscription' before ever paying; a user whose subscription was created off-platform; corrupted/incomplete webhook payload storage that lost `customer_id`.
Related errors
- DODO_PORTAL_ERROR
- DODO_API_KEY_MISSING
- USER_ID_REQUIRED
- ANON_CLAIM_PROOF_REQUIRED
- OWNER_EMAIL_UNAVAILABLE
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/acd58bbba39957a6.
Report an issue: GitHub.