{"record":{"id":"6fc1e1c466d3f42b","repo":"TryGhost/Ghost","slug":"redirectresult-error-message-6fc1e1","errorCode":null,"errorMessage":"redirectResult.error.message","messagePattern":"redirectResult\\.error\\.message","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/portal/src/utils/api.js","lineNumber":566,"sourceCode":"                if (!res.ok) {\n                    const errData = await res.json();\n                    const err = errData?.errors?.[0] || {};\n                    const errMssg = err.message || 'Failed to signup, please try again.';\n                    const error = new Error(errMssg);\n                    error.code = err.code;\n                    throw error;\n                }\n                return res.json();\n            }).then(function (responseBody) {\n                if (responseBody.url) {\n                    return window.location.assign(responseBody.url);\n                }\n                const stripe = window.Stripe(responseBody.publicKey);\n                return stripe.redirectToCheckout({\n                    sessionId: responseBody.sessionId\n                }).then(function (redirectResult) {\n                    if (redirectResult.error) {\n                        throw new Error(redirectResult.error.message);\n                    }\n                });\n            });\n        },\n\n        async continueGiftCheckout() {\n            const siteUrlObj = new URL(siteUrl);\n            const identity = await api.member.identity();\n            const url = endpointFor({type: 'members', resource: 'create-stripe-checkout-session'});\n\n            const checkoutSuccessUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);\n            checkoutSuccessUrl.searchParams.set('stripe', 'success');\n\n            const checkoutCancelUrl = window.location.href.startsWith(siteUrlObj.href) ? new URL(window.location.href) : new URL(siteUrl);\n            checkoutCancelUrl.searchParams.set('stripe', 'cancel');\n\n            const body = {\n                type: 'subscription',","sourceCodeStart":548,"sourceCodeEnd":584,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/portal/src/utils/api.js#L548-L584","documentation":"Thrown inside checkoutPlan() after the Ghost backend successfully created a Stripe Checkout Session but Stripe.js's redirectToCheckout() rejected it. This is a Stripe.js client-side failure, not a Ghost backend failure — the sessionId/publicKey came back fine, but the browser could not hand off to Stripe's hosted checkout page.","triggerScenarios":"stripe.redirectToCheckout({sessionId}) returns {error} — typically because the sessionId is malformed/expired, the publishable key (publicKey) doesn't match the account that created the session, Stripe.js failed to load, or the session was already consumed/expired (sessions expire 24h after creation).","commonSituations":"Stripe keys mismatched between Ghost Admin settings and the live site (test vs live key); clock drift causing the session to appear expired; Stripe.js script blocked by an ad-blocker or CSP; user opened checkout, left it >24h, then clicked again reusing a cached sessionId; Stripe partial outage.","solutions":["Refresh the page and restart checkout — a fresh session is created each time and bypasses a stale/expired sessionId.","Verify Stripe publishable/secret key parity in Ghost Admin settings (Stripe Connect integration) — a test key paired with live mode is the classic cause.","Check browser console for Stripe.js load failures (network/CSP) and ensure the Stripe.js script tag is present and unblocked.","Check status.stripe.com for a Checkout outage.","If reproducible, capture redirectResult.error.code/type from Stripe (e.g. 'invalid_session_id') for a precise diagnosis."],"exampleFix":"// before: throws raw Stripe error.message with no context\nstripe.redirectToCheckout({sessionId}).then(r => { if (r.error) throw new Error(r.error.message); });\n\n// after: classify the Stripe error so the UI can offer a retry\nstripe.redirectToCheckout({sessionId}).then(r => {\n    if (r.error) {\n        const e = new Error(r.error.message);\n        e.code = r.error.code;\n        e.retryable = ['expired', 'invalid_session_id'].includes(r.error.code);\n        throw e;\n    }\n});","handlingStrategy":"try-catch","validationCode":"// Verify Stripe.js loaded and the key matches before redirecting\nfunction stripeReady() {\n    return typeof window.Stripe === 'function' && !!window.Stripe._instance;\n}\n// Cannot validate sessionId expiry client-side; restart the flow to get a fresh one.","typeGuard":"function isStripeRedirectError(result) {\n    return typeof result === 'object' && result !== null && typeof result.error === 'object' && typeof result.error.message === 'string';\n}","tryCatchPattern":"try {\n    await api.member.checkoutPlan({...});\n} catch (err) {\n    // Stripe redirect failed — offer a retry that mints a new session\n    if (/session|stripe|checkout/i.test(err.message)) {\n        offerRetry();\n    } else {\n        showToast(err.message);\n    }\n}","preventionTips":["Restart the checkout flow on any Stripe redirect failure — stale sessions are the top cause.","Keep Stripe publishable and secret keys in the same mode (test/live) in Ghost Admin.","Ensure the Stripe.js script tag is present and not CSP-blocked."],"tags":["portal","stripe","checkout","client-side","stripe-redirect"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}