koala73/worldmonitor · error · ConvexError
Invalid returnUrl: must be a valid absolute URL
Error message
Invalid returnUrl: must be a valid absolute URL
What it means
`_createCheckoutSession` parses `args.returnUrl` with `new URL(...)`. A value that cannot be parsed as an absolute URL (e.g., a bare path like `/dashboard`) throws and is wrapped in this ConvexError. This is the first of two returnUrl validations; the origin allowlist check follows.
Source
Thrown at convex/payments/checkout.ts:223
const msg = err instanceof Error ? err.message : String(err);
console.error(`[checkout] pending-payment guard query failed (failing open): ${msg}`);
return null;
}
}
async function _createCheckoutSession(
args: CheckoutArgs,
user: UserInfo,
) {
// Validate returnUrl to prevent open-redirect attacks.
const siteUrl = process.env.SITE_URL ?? "https://worldmonitor.app";
let returnUrl = siteUrl;
if (args.returnUrl) {
let parsedReturnUrl: URL;
try {
parsedReturnUrl = new URL(args.returnUrl);
} catch {
throw new ConvexError("Invalid returnUrl: must be a valid absolute URL");
}
if (!isTrustedReturnUrlOrigin(parsedReturnUrl.origin, new URL(siteUrl).origin)) {
throw new ConvexError(
"Invalid returnUrl: must use a trusted worldmonitor.app origin",
);
}
returnUrl = parsedReturnUrl.toString();
}
// Build metadata: HMAC-signed userId for the webhook identity bridge.
const metadata: Record<string, string> = {};
metadata.wm_user_id = user.userId;
metadata.wm_user_id_sig = await signUserId(user.userId);
const anonymousClaimToken = ANON_ID_V4_REGEX.test(user.userId)
? await signAnonClaimToken(user.userId)
: null;
if (anonymousClaimToken) {View on GitHub (pinned to ffec79ac33)
Solutions
- Pass a full absolute URL including the scheme, e.g. `https://app.worldmonitor.app/dashboard`
- Omit `returnUrl` entirely to fall back to the default `SITE_URL` (https://worldmonitor.app)
Example fix
// before
createCheckout({ productId, returnUrl: "/dashboard" })
// after
createCheckout({ productId, returnUrl: "https://app.worldmonitor.app/dashboard" }) Defensive patterns
Strategy: validation
Validate before calling
// Validate returnUrl is an absolute URL before calling createCheckout.
function validAbsoluteUrl(u: string | undefined): boolean {
if (!u) return true; // undefined falls back to SITE_URL
try { new URL(u); return true; } catch { return false; }
}
if (!validAbsoluteUrl(args.returnUrl)) { /* block submission */ } Type guard
function isAbsoluteUrl(value: string | undefined): value is string {
if (!value) return false;
try { new URL(value); return true; } catch { return false; }
} Prevention
- Pass a full absolute URL (with scheme) or omit returnUrl to use the SITE_URL default
- Don't pass bare route paths like '/dashboard'
When it happens
Trigger: Passing `returnUrl` as a relative path (`/dashboard`), a scheme-less string (`worldmonitor.app/dashboard`), or any value the URL constructor rejects.
Common situations: Frontend passes a route path instead of a full URL; misconfigured env producing a partial string; client assumes the server will prefix the scheme.
Related errors
- Invalid returnUrl: must use a trusted worldmonitor.app origi
- Dodo checkout session ${session.session_id} has no checkout_
- Checkout failed: ${msg}
- ACTIVE_SUBSCRIPTION_EXISTS
- PAYMENT_IN_PROGRESS
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/c27d60057fd785d1.
Report an issue: GitHub.