OmniPayDocs
Redirect checkout

Completion and fulfillment

Handle browser returns, polling, statuses, and webhooks without duplicate fulfillment.

Redirect checkout produces three different signals. They are useful for different purposes:

SignalUse it forPayment authority
Browser returnRestore your UI and show progressNo
Authenticated status responseRead the current deposit stateYes
Verified webhookProcess asynchronous state changesYes

Handle the browser return

The payer returns to the redirect_url supplied in your create request. The return means only that navigation reached your application; it does not prove that payment succeeded.

Your return page should:

  1. identify the order from server-side session state or a safe opaque value;
  2. show a processing state;
  3. ask your backend for the current deposit status;
  4. display success only when your backend reports succeeded.

Do not accept a client-supplied status, amount, or transaction ID as proof.

Interpret statuses

StatusTerminal?Your action
pendingNoContinue waiting
processingNoContinue waiting
succeededYesFulfill exactly once
failedYesShow failure and stop polling
expiredYesOffer a new checkout with a new merchant_id
cancelledYesShow cancellation
rejectedYesShow failure and follow your support workflow
refundedYesApply your refund workflow

Poll with a limit

Webhooks are preferred for backend processing, but short-lived polling improves the payer experience after a browser return.

const terminal = new Set([
  "succeeded",
  "failed",
  "expired",
  "cancelled",
  "rejected",
  "refunded",
]);

for (let attempt = 0; attempt < 20; attempt += 1) {
  const deposit = await getDepositFromYourBackend(orderId);

  if (terminal.has(deposit.status)) {
    renderResult(deposit.status);
    break;
  }

  await new Promise((resolve) => setTimeout(resolve, 3000));
}

Keep API keys in your backend. The browser should call your authenticated application endpoint, not OmniPay directly.

Fulfill from webhooks

Subscribe to deposit events and verify the Svix signature against the exact raw request body. Fulfillment should be idempotent because a webhook may be retried or replayed.

For a successful order:

  1. verify the webhook signature;
  2. reject or ignore duplicate svix-id values;
  3. locate the order by merchant_id;
  4. cross-check the OmniPay id and amount;
  5. transition the order only if it has not already been fulfilled;
  6. commit the event record and business transition together.

Configure and verify webhooks →

Production checklist

  • Persist merchant_id and the OmniPay deposit id.
  • Record X-Request-Id from API responses.
  • Treat browser returns as navigation only.
  • Handle every documented terminal status.
  • Use bounded polling with a delay.
  • Verify webhooks before parsing and make fulfillment idempotent.
  • Test payer cancellation, timeout, webhook retry, and duplicate delivery.

On this page