App-to-app overview

The full app-to-app flow, the per-bank URL scheme convention, and the trust model that keeps your goods safe.

App-to-app uses URL schemes to hand the customer from your merchant app to their Scan to Pay-enabled wallet app, and back. Both Android and iOS support the pattern; the implementation details differ but the concept is identical.


The flow

   Customer in your app        Your backend          Scan to Pay      Wallet app on phone
   ──────────────────          ────────────          ───────────      ──────────────────
        │                            │                    │                    │
        │ taps "Pay"                 │                    │                    │
        ├───────────────────────────►│                    │                    │
        │                            │  POST /code/create │                    │
        │                            ├───────────────────►│                    │
        │                            │                    │                    │
        │                            │◄──── code ─────────┤                    │
        │                            │                                         │
        │◄────── code ───────────────┤                                         │
        │                                                                      │
        │  ┌─ launch wallet via masterpass.{bank}.scheme:// deep link ────────►│
        │  │                                                                   │
        │  │                                          [customer authorises]    │
        │  │                                                                   │
        │  │                            │   webhook (CB)  │                    │
        │  │                            │◄────────────────┤                    │
        │  │                            │   ack 200       │                    │
        │  │                            ├────────────────►│                    │
        │  │                                                                   │
        │◄─┴── return via your.app.scheme://?status=SUCCESS&transactionId=... ─┤
        │                                                                      │
        │   verify with your backend before releasing goods                    │
        ├────────────────────────────►│                                        │

Eight steps:

  1. Customer taps "Pay" in your app.
  2. Your app calls your backend to start the payment.
  3. Your backend calls POST /code/create on the Scan to Pay API (same endpoint as Dynamic QR — see Dynamic QR).
  4. The platform returns a 10-digit code.
  5. Your app launches the wallet via a masterpass.{bank}.scheme:// URL containing the code and a return URL.
  6. The customer authorises in the wallet app with PIN or 3DS.
  7. Scan to Pay sends a webhook to your backend with the outcome.
  8. The wallet app returns to your app via your custom URL scheme, with status as a query parameter.

The URL scheme convention

Every wallet app registers a URL scheme of the form masterpass.{bank}.scheme://. You launch the wallet by constructing a URL using that scheme.

WalletScheme
ABSAmasterpass.absa.scheme://
Standard Bankmasterpass.sbsa.scheme://
Nedbankmasterpass.nedbank.scheme://
Capitecmasterpass.capitec.scheme://
VodaPaymasterpass.vodapay.scheme://
Spendamasterpass.spenda.scheme://
FNBuses a universal link — see iOS / Android pages
📘

SDK naming note. The deep-link schemes retain the historical masterpass. prefix for backwards compatibility. The product is Scan to Pay; the URL scheme is the existing one to avoid breaking integrations with bank-issued wallet apps. See App-to-app on Android and iOS for the full list and how to handle the case where the customer has multiple wallets.

The URL you build looks like:

masterpass.absa.scheme://masterpass.oltio.co.za/{10-digit code}/{URL-encoded return URL}

Where:

  • {10-digit code} is the value returned from POST /code/create
  • {URL-encoded return URL} is your own app's URL scheme — what the wallet will open after the customer finishes. Must be URL-encoded.

The trust model

⚠️

Never release goods on the wallet's return alone.

The wallet returns to your app via a URL scheme with status=SUCCESS (or another value) in the query string. An attacker can call your URL scheme manually with status=SUCCESS and a stolen transaction ID. Treat the return as a hint that the customer's flow finished, not as proof of payment.

Always verify via your backend webhook (or getTransactionState if you don't have webhooks configured) before treating an order as paid.

The reliable pattern:

  1. The wallet returns to your app — your UI moves to a "Confirming…" state.
  2. Your app polls your backend for the order's status.
  3. Your backend already received (or will shortly receive) the encrypted webhook with the authoritative outcome.
  4. Once your backend confirms SUCCESS, your UI shows the success page.

The wallet's return is the trigger for your UI to update; the webhook is the proof of payment.


What's next