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:
- Customer taps "Pay" in your app.
- Your app calls your backend to start the payment.
- Your backend calls
POST /code/createon the Scan to Pay API (same endpoint as Dynamic QR — see Dynamic QR). - The platform returns a 10-digit code.
- Your app launches the wallet via a
masterpass.{bank}.scheme://URL containing the code and a return URL. - The customer authorises in the wallet app with PIN or 3DS.
- Scan to Pay sends a webhook to your backend with the outcome.
- 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.
| Wallet | Scheme |
|---|---|
| ABSA | masterpass.absa.scheme:// |
| Standard Bank | masterpass.sbsa.scheme:// |
| Nedbank | masterpass.nedbank.scheme:// |
| Capitec | masterpass.capitec.scheme:// |
| VodaPay | masterpass.vodapay.scheme:// |
| Spenda | masterpass.spenda.scheme:// |
| FNB | uses a universal link — see iOS / Android pages |
SDK naming note. The deep-link schemes retain the historicalmasterpass.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 fromPOST /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 withstatus=SUCCESSand 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
getTransactionStateif you don't have webhooks configured) before treating an order as paid.
The reliable pattern:
- The wallet returns to your app — your UI moves to a "Confirming…" state.
- Your app polls your backend for the order's status.
- Your backend already received (or will shortly receive) the encrypted webhook with the authoritative outcome.
- 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
- Implement on Android → App-to-app on Android
- Implement on iOS → App-to-app on iOS
- Parse the response when the wallet returns → Handling the response
- Receive the authoritative outcome via webhook → Webhooks
- Same code-create endpoint as dynamic QR → Dynamic QR
Updated 4 days ago
