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 its registered scheme / universal link ──────►│
        │  │                                                                   │
        │  │                                          [customer authorises]    │
        │  │                                                                   │
        │  │                            │   webhook (CB)  │                    │
        │  │                            │◄────────────────┤                    │
        │  │                            │   ack 200       │                    │
        │  │                            ├────────────────►│                    │
        │  │                                                                   │
        │◄─┴──── return via your.app.scheme://?status=SUCCESS&ref=... ─────────┤
        │                                                                      │
        │   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 its registered URL scheme (or FNB universal link) 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.

Wallet launch reference

Most wallet apps register a URL scheme using the historical masterpass. prefix. The standalone Scan to Pay app has different Android and iOS registrations, so choose the scheme for both the wallet and the platform you intend to launch.

WalletPlatformLaunch method
Scan to Pay standalone appAndroidmasterpass.app.scheme://
Scan to Pay standalone appiOSmasterpass.capitec.scheme://
ABSAAndroid / iOSmasterpass.absa.scheme://
Standard BankAndroid / iOSmasterpass.sbsa.scheme://
NedbankAndroid / iOSmasterpass.nedbank.scheme://
Capitec-enabled walletAndroid / iOSmasterpass.capitec.scheme://
VodaPayAndroid / iOSmasterpass.vodapay.scheme://
SpendaAndroid / iOSmasterpass.spenda.scheme://
FNB / RMBAndroid / iOSHTTPS universal link — see FNB / RMB universal link
📘

Historical naming. The standalone iOS app retains the masterpass.capitec.scheme registration, which is also the published Capitec-compatible scheme, while the standalone Android app uses masterpass.app.scheme. Do not substitute scantopay://, masterpass://, or another inferred scheme; those values do not identify the standalone app.

The URL you build looks like:

{wallet 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.

For example, to launch the standalone Scan to Pay app:

masterpass.app.scheme://masterpass.oltio.co.za/0123456789/your.app.scheme%3A%2F%2Fmerchant.com
masterpass.capitec.scheme://masterpass.oltio.co.za/0123456789/your.app.scheme%3A%2F%2Fmerchant.com

FNB / RMB universal link

FNB and RMB use an HTTPS universal link instead of the wallet-scheme path above:

https://www.online.fnb.co.za/banking/mobileservices?codetype=masterpassdeeplink&urlencodedqr={URL-encoded 10-digit code}&urlencodedcallbackurl={URL-encoded return URL}
Query parameterRequired value
codetypeThe fixed value masterpassdeeplink
urlencodedqrThe URL-encoded 10-digit code returned by POST /code/create
urlencodedcallbackurlYour app's URL-encoded return URL

Example:

https://www.online.fnb.co.za/banking/mobileservices?codetype=masterpassdeeplink&urlencodedqr=0123456789&urlencodedcallbackurl=your.app.scheme%3A%2F%2Fmerchant.com

Percent-encode each value once. Keep the parameter names exactly as shown.


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) and a transaction reference in the query string. An attacker can call your URL scheme manually with a forged status and reference. 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


Did this page help you?