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
- Confirm the user actually has (or had) a subscription in the customers/subscriptions tables
- If subscriptions exist, inspect their rawPayload webhook data for a missing customer_id and repair from the Dodo dashboard
- 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 SentryView on GitHub (pinned to 7d06c8633d)