Configure return URLs and outcome pages
Build success, failure, cancelled, expired, and processing pages for hosted 0Gate flows without trusting browser-only state.
Return URLs and outcome pages are the bridge between the hosted 0Gate experience and your product. They should make users feel oriented, but they should read final state from your backend.
Do not trust URL state
A user can visit, refresh, replay, or share a return URL. Use it as a routing signal only. The page should fetch your own attempt status, which is backed by signed webhooks and reconciliation.
Outcome flow
URL design
| URL | Use |
|---|---|
/checkout/{attemptId}/return | User arrived from a successful or processing hosted path. |
/checkout/{attemptId}/cancel | User left or cancelled the hosted path. |
/checkout/{attemptId}/status | Reusable status page for email, support, and refreshes. |
Keep the attempt id opaque. Do not place secret keys, browser session secrets, raw provider values, or terminal truth in query parameters.
Status endpoint
export async function getCheckoutStatus(attemptId: string, accountId: string) {
const attempt = await checkoutAttempts.getForAccount(attemptId, accountId);
if (!attempt) return { view: 'not_found' };
if (attempt.status === 'fulfilled') return { view: 'success' };
if (attempt.status === 'failed') return { view: 'failed', canRetry: true };
if (attempt.status === 'expired') return { view: 'expired', canRetry: true };
if (attempt.status === 'cancelled') return { view: 'cancelled', canRetry: true };
return { view: 'processing', nextPollMs: 3000 };
}Page states
| Backend state | Page view | Customer copy |
|---|---|---|
| processing | Processing | “We are confirming your checkout. You can leave this page and return later.” |
| fulfilled | Success | “Your checkout is complete.” |
| failed | Failed | “This checkout could not be completed. You can try again or contact support.” |
| expired | Expired | “This session expired. Start again to get a fresh checkout.” |
| cancelled | Cancelled | “This checkout was cancelled. You can start a new attempt.” |
Client page pattern
export function CheckoutOutcome({ attemptId }: { attemptId: string }) {
const status = useCheckoutStatus(attemptId);
if (status.view === 'processing') return <ProcessingReceipt nextPollMs={status.nextPollMs} />;
if (status.view === 'success') return <SuccessReceipt />;
if (status.view === 'failed') return <FailureReceipt canRetry={status.canRetry} />;
if (status.view === 'expired') return <ExpiredReceipt />;
if (status.view === 'cancelled') return <CancelledReceipt />;
return <SupportFallback attemptId={attemptId} />;
}Outcome page guardrails
| Do | Avoid |
|---|---|
| Show your attempt id, normalized state, created time, and next action. | Showing raw webhook payloads, signatures, or browser session secrets. |
| Poll your own status endpoint while processing. | Polling client-side callbacks or trusting URL query values. |
| Offer a fresh retry for failed/expired/cancelled attempts. | Reusing expired or cancelled browser session values. |
| Provide support links with a safe reference id. | Exposing provider, treasury, compliance-rule, or risk-threshold details. |
Related pages
Use hosted redirect & WebViews
Understand how users arrive back at outcome pages.
Handle failed or cancelled flows
Build retry, support, and reconciliation behavior.
Handle widget callbacks
Keep callback-driven status transitions browser-only.
Build a 0Gate payment flow
Connect outcome pages to the durable backend flow.
Pre-fill and constrain a session
Bind 0Gate flow, amount, asset, wallet, return URLs, and correlation fields on the server before the browser opens the hosted flow.
Handle widget callbacks
Wire 0Gate browser callbacks for UX while keeping fulfillment, ledger updates, and support state on verified backend events.