koala73/worldmonitor · error · ConvexError
Invalid returnUrl: must use a trusted worldmonitor.app origi
Error message
Invalid returnUrl: must use a trusted worldmonitor.app origin
What it means
Even a parseable absolute URL must have an origin in the trusted allowlist (`TRUSTED_RETURN_URL_ORIGINS`) or exactly match the deployment's `SITE_URL` origin. This is the open-redirect guard: Vercel serves the app on every attached domain, and several worldmonitor.app subdomains are vendor-owned CNAMEs, so a `*.worldmonitor.app` suffix match is deliberately avoided in favor of an enumerated list.
Source
Thrown at convex/payments/checkout.ts:227
}
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) {
metadata.wm_anon_claim = "v2";
}
// #6335: carry the login email that was authenticated FOR THIS CHECKOUT, so
// the activation webhook can address lifecycle mail without depending on theView on GitHub (pinned to ffec79ac33)
Solutions
- Use one of the allowlisted origins: worldmonitor.app, www, app, api, tech, finance, commodity, happy, energy (all https)
- For self-hosted/preview deployments, set the `SITE_URL` env var to your origin so it is trusted as the extraOrigin
- If a legitimate new app-serving subdomain is missing, add it to TRUSTED_RETURN_URL_ORIGINS and update tests/checkout-return-url-origin.test.mts
Defensive patterns
Strategy: validation
Validate before calling
// Allowlist the returnUrl origin client-side before checkout.
const TRUSTED = ['https://worldmonitor.app','https://www.worldmonitor.app','https://app.worldmonitor.app','https://api.worldmonitor.app','https://tech.worldmonitor.app','https://finance.worldmonitor.app','https://commodity.worldmonitor.app','https://happy.worldmonitor.app','https://energy.worldmonitor.app'];
function trustedOrigin(u: string): boolean { try { return TRUSTED.includes(new URL(u).origin); } catch { return false; } } Type guard
function isTrustedReturnUrl(value: string, trusted: readonly string[]): boolean {
try { return trusted.includes(new URL(value).origin); } catch { return false; }
} Prevention
- Use an allowlisted worldmonitor.app origin for returnUrl
- For self-hosted/preview, set SITE_URL so the deployment origin is trusted as the extraOrigin
- Keep TRUSTED_RETURN_URL_ORIGINS and the pinning test in sync when adding app-serving subdomains
When it happens
Trigger: Passing a `returnUrl` whose origin is not in the allowlist: e.g. `https://evil.com/...`, a non-listed subdomain like `https://staging.worldmonitor.app`, or an `http://` variant of an allowed host.
Common situations: Trying to redirect post-payment to a staging/preview domain not in the list; third-party URL; http vs https mismatch; a new variant subdomain added to Vercel but not to the allowlist (the documented drift incident WORLDMONITOR-K7).
Related errors
- Invalid returnUrl: must be a valid absolute URL
- Dodo checkout session ${session.session_id} has no checkout_
- INVALID_INVITE_TOKEN
- Checkout failed: ${msg}
- ACTIVE_SUBSCRIPTION_EXISTS
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/71a5f2298a25b780.
Report an issue: GitHub.