koala73/worldmonitor · error · ConvexError
DODO_PORTAL_ERROR
DODO_PORTAL_ERROR
Error message
DODO_PORTAL_ERROR
What it means
Thrown in `createCustomerPortalUrlForUser` when the Dodo SDK call `client.customers.customerPortal.create(...)` throws — either a Dodo APIError (non-2xx response) or a transport failure. The underlying cause is logged server-side (`console.error` with the customer id and message), and the call is re-thrown as an object-typed ConvexError (`{ kind: "DODO_PORTAL_ERROR" }`) so the client tags it for Sentry while the user falls back to the generic Dodo portal.
Source
Thrown at convex/payments/billing.ts:470
} 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
// classification (browser → `extractBillingErrorKind` → tag
// `billing_error_kind`; the user still falls back to the generic Dodo
// portal), and log the underlying cause here so it survives in the
// Convex function logs for server-side triage. WORLDMONITOR-ST.
const cause = err instanceof Error ? err.message : String(err);
console.error(
`[billing] Dodo customer-portal create failed for customer ${dodoCustomerId}:`,
cause,
);
throw new ConvexError({ kind: "DODO_PORTAL_ERROR" });
}
return { portal_url: session.link };
}
function getSubscriptionStatusPriority(status: string): number {
switch (status) {
case "active":
return 0;
case "on_hold":
return 1;
case "cancelled":
return 2;
default:
return 3;
}
}
View on GitHub (pinned to ffec79ac33)
Solutions
- Retry the portal open after a short delay (transient Dodo 5xx/network often clears).
- Check the Convex function logs for the logged underlying cause (`[billing] Dodo customer-portal create failed for customer ...`).
- If persistent, verify the customer id is still valid in the Dodo dashboard and that the Dodo account is in good standing.
- Surface a fallback 'open Dodo portal directly' link to the user while investigating.
Example fix
// before
const { portal_url } = await getCustomerPortalUrl({});
// after (client-side retry with fallback)
try {
const { portal_url } = await getCustomerPortalUrl({});
location.href = portal_url;
} catch (e) {
if (e.data?.kind === "DODO_PORTAL_ERROR") {
location.href = "https://pay.dodopayments.com"; // fallback
} else throw e;
} Defensive patterns
Strategy: retry
Try / catch
async function openPortal() {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const { portal_url } = await getCustomerPortalUrl({});
location.href = portal_url;
return;
} catch (e: any) {
if (e?.data?.kind === "DODO_PORTAL_ERROR" && attempt < 2) {
await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
continue;
}
location.href = "https://pay.dodopayments.com"; // fallback
return;
}
}
} Prevention
- Retry once or twice — most DODO_PORTAL_ERROR causes are transient (5xx/network).
- Provide a fallback direct Dodo portal link when retries are exhausted.
- Check Convex logs for the underlying cause string before escalating.
When it happens
Trigger: Dodo returns a non-2xx for the portal-session create (rate limit, 5xx, bad customer id, account suspended), or the outbound HTTPS request to Dodo fails (timeout, DNS, network). Distinct from NO_CUSTOMER: here a customer id exists but the create call itself failed.
Common situations: Dodo temporary outage or 5xx; transient network blip between Convex and Dodo; customer id is stale/revoked on Dodo's side; rate-limited by Dodo during a burst of portal opens.
Related errors
- NO_CUSTOMER
- DODO_API_KEY_MISSING
- API_ACCESS_REQUIRED
- [backfillCanary250] Resend list-contacts ${res.status}: ${bo
- [createProLaunchBroadcast] Resend ${res.status}: ${body}
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/65caa439081b49bc.
Report an issue: GitHub.