payto links for banking apps
A payment request often reaches the payer on the phone they would pay with: an invoice in an email, a link in a chat, a QR code on a web page. The EPC QR code (“GiroCode”) needs a second device to scan it. On the same phone the payer copies the name, the IBAN, the amount and the reference into the banking app one by one.
A payto://iban link carries the same request as the code, in a form one app can hand to another. When your app accepts it, the payer taps the link and your transfer form opens filled in. The payer checks it and authorises it as usual. The scheme is RFC 8905; the payto handoff profile fixes how a SEPA credit transfer is written and read.
A link
payto://iban/DE89370400440532013000?amount=EUR:25.00&receiver-name=Example%20Payee&message=Invoice%202026-001
iban/[BIC/]IBAN: the beneficiary's account, the BIC first when presentamount=EUR:25.00: the amount in euro, absent when the payer decides itreceiver-name: the beneficiary name, always presentmessage: the unstructured remittance information, up to 140 characters
1. Register the scheme
Android, on the activity that opens the transfer form:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="payto" android:host="iban" />
</intent-filter>
iOS, in Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>payto</string>
<key>CFBundleURLSchemes</key>
<array><string>payto</string></array>
</dict>
</array>
2. Read it strictly
Refuse the whole link when anything is wrong, and never fill in part of a transfer:
- the IBAN passes its check digits and the BIC, when present, its format
- the amount is
EUR:and digits with a point, never a comma receiver-nameis present and not blank- no control characters, line breaks or bidirectional formatting characters in any value
- an unknown option, a repeated option or an
instructionrefuses the link. The exceptions aresender-name,receiver-postal-codeandreceiver-town, which describe the parties rather than the payment and are ignored
In JavaScript or TypeScript, including React Native, @euvena/qr does this with decodePaytoUri. Kotlin and Swift versions are planned.
3. Show it before anything happens
- Open the ordinary transfer form with the values filled in, showing the name, the full IBAN, the amount and the message.
- Authorise exactly as for a transfer the payer typed in, with no step skipped. Any app on the phone can open a link.
- Run your usual checks, including Verification of Payee.
- Let the payer choose the account to pay from. The link never names it.
Test links
Open this page on a phone with your build installed and tap each link. The account is the widely published example IBAN, which may still belong to someone, so stop at the filled-in form and do not authorise. Use your own test account for an end-to-end run.
- payto://iban/DE89370400440532013000?amount=EUR:25.00&receiver-name=Example%20Payee&message=Invoice%202026-001Transfer form with name, IBAN, €25.00 and the message
- payto://iban/COBADEFFXXX/DE89370400440532013000?receiver-name=Example%20PayeeForm with the BIC and no amount: the payer enters it
- payto://iban/DE89370400440532013000?amount=EUR:12,50&receiver-name=Example%20PayeeRefused: comma in the amount
- payto://iban/DE89370400440532013001?receiver-name=Example%20PayeeRefused: IBAN check digits
- payto://iban/DE89370400440532013000?receiver-name=Example%20Payee&instruction=E2E-1Refused: end-to-end identifier
More
The full guide and the Euvena repository, where the Euvena wallet emits these links. Questions and reports of apps that accept them are welcome in the issue tracker.