different-ai/openwork · error · Error
Seat checkout response did not include a URL.
Error message
Seat checkout response did not include a URL.
What it means
startSeatCheckout POSTs {type:"seat"} to /v1/billing/stripe/checkout expecting a JSON body with a string 'url' field for the Stripe-hosted checkout session. The error is thrown when the endpoint responds 200 OK but the payload contains no usable url, so the redirect (window.location.href = url) cannot proceed.
Source
Thrown at ee/apps/den-web/app/(den)/dashboard/_components/billing-dashboard-screen.tsx:295
async function startSeatCheckout() {
if (!canManageBillingSettings) {
setStripeError("Admins can start seat checkout from Members. Owners and super-admins manage Billing settings here.");
return;
}
setStripeError(null);
try {
await runReauthableAction("seat-checkout", async () => {
setStripeActionBusy("seat-checkout");
const { response, payload } = await requestJson(
"/v1/billing/stripe/checkout",
{ method: "POST", body: JSON.stringify({ type: "seat" }) },
12000,
);
if (!response.ok) throw getRequestError(payload, response, `Seat checkout failed (${response.status}).`);
const url = payload && typeof payload === "object" && "url" in payload && typeof payload.url === "string" ? payload.url : null;
if (!url) throw new Error("Seat checkout response did not include a URL.");
window.location.href = url;
});
} catch (error) {
setStripeError(error instanceof Error ? error.message : "Could not start seat billing checkout.");
} finally {
setStripeActionBusy(null);
}
}
async function openStripePortal() {
if (!canManageBillingSettings) {
setStripeError("Only workspace owners and super-admins can open billing portals from Settings.");
return;
}
setStripeError(null);
try {
await runReauthableAction("billing-portal", async () => {View on GitHub (pinned to 2b7df46e8a)
Solutions
- Log the checkout response payload to confirm what the server actually returned for 'url'.
- Fix the server handler to return 4xx/5xx with getRequestError-compatible detail instead of a 200 without a URL.
- Verify the server can create Stripe checkout sessions (valid API keys, price configured for seat billing).
- Check client and server share the same response contract ({url: string}); update parsing if the field moved (e.g. payload.session.url).
Example fix
// before
const url = payload && typeof payload === "object" && "url" in payload && typeof payload.url === "string" ? payload.url : null;
if (!url) throw new Error("Seat checkout response did not include a URL.");
// after
const url = isCheckoutUrlResponse(payload) ? payload.url : null;
if (!url) throw new Error(`Seat checkout response did not include a URL (keys: ${Object.keys(payload ?? {}).join(",")}).`); Defensive patterns
Strategy: type-guard
Validate before calling
function hasUrl(p: unknown): p is { url: string } {
return typeof p === "object" && p !== null && "url" in p && typeof (p as { url: unknown }).url === "string";
}
// before redirecting: if (!hasUrl(payload)) throw ... Type guard
function isCheckoutUrlResponse(p: unknown): p is { url: string } {
return typeof p === "object" && p !== null && "url" in p && typeof (p as { url: unknown }).url === "string" && (p as { url: string }).url.startsWith("https://");
} Try / catch
try {
await startSeatCheckout();
} catch (error) {
setStripeError(error instanceof Error ? error.message : "Could not start seat billing checkout.");
} Prevention
- Validate checkout responses server-side and return 5xx with detail instead of 200 without a url
- Type the endpoint response and parse it with Zod so missing fields fail loudly in dev
- Monitor Stripe checkout session creation errors in server logs
- Keep a direct Stripe dashboard link as an operator fallback when portal/checkout APIs misbehave
When it happens
Trigger: POST /v1/billing/stripe/checkout returns 200 with a body like {}, {url: null}, or {url: 123}; the server-side Stripe checkout session creation silently failed but still returned success.
Common situations: Stripe publishable/secret keys misconfigured on the Den server; checkout session creation returning a session without a URL; response schema change (url nested under data); org has no Stripe customer yet and server returns an empty success.
Related errors
- Billing response was incomplete.
- Billing portal response did not include a URL.
- Invalid cloud provider sync response.
- Invalid cloud provider sync status.
- Invalid cloud provider sync status response.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/c18624e3378e9012.
Report an issue: GitHub.