0Bit Documentation

Handle failed or cancelled flows

Recover safely from failed, expired, cancelled, delayed, and interrupted hosted 0Gate flows.

Every hosted payment flow needs a recovery model. Customers refresh pages, close iframes, abandon redirects, fail compliance or payment steps, and contact support before webhook delivery catches up. Build the recovery path before launch.

Failure pages must stay public-safe

Do not expose provider names, compliance rule details, treasury state, raw webhook payloads, or internal retry diagnostics on customer-facing screens.

Failure map

SituationCustomer-facing stateBackend rule
Customer closes iframeOffer resume, retry, or support.Do not mark failed until trusted state says terminal.
Return URL opensShow processing or terminal status from your server.Ignore status passed only through the URL.
Session expiresAsk the user to start again.Do not reuse expired session values.
Hosted flow failsShow a generic failed state and next action.Store the event id and close the attempt once.
Duplicate webhook arrivesNo visible change.Return success after dedupe.
Webhook delayedKeep processing, then reconcile if needed.Reconciliation must not double-fulfill.

Terminal states

Terminal stateMeaningRetry guidance
fulfilledVerified backend state completed and your ledger updated.Do not retry fulfillment. Show receipt/status.
failedThe hosted flow could not complete.Let the customer start a new attempt if product rules allow it.
expiredThe session lifetime ended before completion.Create a new session; do not reuse the old browser secret.
cancelledUser or backend cancelled the attempt.Preserve audit state and start fresh if needed.

Recovery controller

The status endpoint your browser reads should be backed by your database, not by browser callback memory.

async function getPaymentRecoveryState(attemptId: string) {
  const attempt = await paymentAttempts.get(attemptId);

  if (attempt.status === 'fulfilled') return { view: 'complete' };
  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 };

  if (attempt.updatedAt < minutesAgo(15)) {
    await reconciliationQueue.enqueue({ attemptId, reason: 'stale_processing_view' });
  }

  return { view: 'processing' };
}

Retry policy

EdgeWhat can happenCorrect behavior
User double-clicks startTwo browser requests race.Use one server-side attempt or idempotency key per logical action.
Session creation times outYour server does not know whether the write succeeded.Retry with the same logical idempotency key.
Widget mount failsBrowser cannot open hosted flow.Show retry, but do not create unlimited attempts.
Webhook delivery retriesSame event is delivered more than once.Dedupe and return success for already-seen events.
Support triggers reconciliationA human needs current trusted state.Read server-side state and compare before changing the ledger.

Support view

Give support enough to help without exposing internals.

ShowHide
Your attempt id and customer/account reference.Secret keys, webhook secrets, browser session secrets.
0Gate session id and current normalized status.Raw webhook bodies and signatures.
Timeline of received event ids and terminal state changes.Provider, venue, treasury, compliance-rule, or risk-threshold details.
Last reconciliation check and next allowed action.Internal settlement notes not approved for public/support display.

Customer copy

StatusCopy pattern
Processing“We are confirming your payment. You can leave this page and check status later.”
Failed“This payment could not be completed. No final credit has been applied.”
Expired“This session expired. Start again to get a fresh checkout.”
Cancelled“This checkout was cancelled. You can start a new attempt.”
Support needed“Contact support with this attempt id.”

On this page