Skip to content
Tolinku
Tolinku
Sign In Start Free
Use Cases · · 6 min read

Deep Linking and PSD2: Open Banking Considerations

By Tolinku Staff
|
Tolinku e commerce deep linking dashboard screenshot for use cases blog posts

PSD2 (the Payment Services Directive 2) changed how European fintech apps handle payments and account access. It introduced Strong Customer Authentication (SCA), mandated open banking APIs, and created a regulatory framework for third-party providers (TPPs). Deep links are central to PSD2 flows because they handle the redirects between banking apps, third-party apps, and authentication screens.

This guide covers how deep links interact with PSD2 requirements. For fintech compliance broadly, see fintech compliance and deep links. For fintech security, see security best practices for fintech deep links.

PSD2 creates three scenarios where deep links are essential:

  1. SCA redirects. When a payment requires Strong Customer Authentication, the user must be redirected to their bank's app to authenticate, then redirected back.
  2. Account access consent. When a TPP (like a budgeting app) requests access to a user's bank account, the user is redirected to the bank's app to grant consent.
  3. Payment initiation. When a TPP initiates a payment, the user is redirected to their bank's app to authorize it.

Each of these flows involves at least two app-to-app redirects, and deep links are how those redirects work on mobile.

Strong Customer Authentication (SCA) Redirects

SCA requires two of three authentication factors: something the user knows (password/PIN), something they have (phone/card), and something they are (biometric). In mobile apps, this often means redirecting to the bank's app for biometric authentication.

SCA Redirect Flow

1. User initiates payment in merchant app
2. Merchant app calls PSP (Payment Service Provider) API
3. PSP returns SCA redirect URL → bank app deep link
4. Merchant app opens bank app via deep link
5. User authenticates in bank app (biometric + PIN)
6. Bank app redirects back to merchant app via callback deep link
7. Merchant app receives authorization and completes payment

Implementation

// Step 4: Open bank app for SCA
func initiatePaymentWithSCA(paymentIntent: PaymentIntent) {
    guard let scaRedirectURL = paymentIntent.scaRedirectURL else {
        // SCA not required, complete payment directly
        completePayment(paymentIntent)
        return
    }

    // Store payment context for when the user returns
    pendingPayment = paymentIntent

    // Open the bank's app via Universal Link
    UIApplication.shared.open(scaRedirectURL, options: [:]) { opened in
        if !opened {
            // Bank app not installed, fall back to web-based SCA
            self.showWebSCA(url: scaRedirectURL)
        }
    }
}

// Step 7: Handle callback from bank app
func handleSCACallback(_ url: URL) {
    let params = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
    let status = params?.first(where: { $0.name == "status" })?.value
    let authCode = params?.first(where: { $0.name == "auth_code" })?.value

    guard let payment = pendingPayment else {
        showError("No pending payment found.")
        return
    }

    if status == "authenticated", let authCode = authCode {
        completePayment(payment, authCode: authCode)
    } else {
        showPaymentFailed(reason: "Authentication was declined or cancelled.")
    }

    pendingPayment = nil
}

The callback URL must be registered as a Universal Link (iOS) or App Link (Android):

https://links.merchantapp.com/payments/sca/callback?status=authenticated&auth_code=ABC123

Register the route:

/payments/sca/callback    → Handle SCA authentication result

Under PSD2, Account Information Service Providers (AISPs) can access bank account data with the user's consent. The consent flow uses deep links to redirect between the AISP's app and the bank's app.

1. User requests to connect bank account in budgeting app (AISP)
2. AISP app opens bank app via deep link with consent request
3. Bank app shows consent screen (which accounts, what data, how long)
4. User approves consent in bank app
5. Bank app redirects back to AISP app with authorization code
6. AISP app exchanges code for access token

AISP Side (Budgeting App)

func connectBankAccount(bank: Bank) {
    let consentURL = bank.buildConsentURL(
        clientId: "AISP-CLIENT-ID",
        scope: "accounts,balances,transactions",
        redirectURI: "https://links.budgetapp.com/connect/bank/callback",
        state: generateState()
    )

    UIApplication.shared.open(consentURL)
}

func handleBankCallback(_ url: URL) {
    let params = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
    let code = params?.first(where: { $0.name == "code" })?.value
    let state = params?.first(where: { $0.name == "state" })?.value

    guard validateState(state) else {
        showError("Invalid session. Please try again.")
        return
    }

    if let code = code {
        exchangeCodeForToken(code)
    }
}

Bank Side

The bank app receives the consent request deep link and shows the consent screen:

func handleConsentRequest(_ url: URL) {
    guard authManager.isAuthenticated else {
        pendingDeepLink = url
        showBiometricLogin()
        return
    }

    let params = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
    let clientId = params?.first(where: { $0.name == "client_id" })?.value
    let scope = params?.first(where: { $0.name == "scope" })?.value
    let redirectURI = params?.first(where: { $0.name == "redirect_uri" })?.value

    guard let tpp = registeredTPPs[clientId] else {
        showError("Unrecognized third party.")
        return
    }

    showConsentScreen(
        tppName: tpp.name,
        requestedScope: scope,
        redirectURI: redirectURI
    )
}

Payment Initiation (PIS)

Payment Initiation Service Providers (PISPs) can initiate payments from a user's bank account. The flow is similar to AIS consent but includes payment details.

https://links.bankapp.com/psd2/payment?
  pisp=PISP-CLIENT-ID&
  amount=50.00&
  currency=EUR&
  recipient=Merchant+Name&
  iban=DE89370400440532013000&
  reference=ORDER-789&
  redirect_uri=https://links.merchantapp.com/payments/callback&
  state=random_state_value

The bank app opens, shows the payment details, requires SCA, and redirects back to the PISP's app after authorization.

Handling Fallbacks

Not all users have their bank's app installed. PSD2 flows must handle this gracefully:

func openBankForSCA(scaURL: URL, bank: Bank) {
    UIApplication.shared.open(scaURL, options: [:]) { opened in
        if !opened {
            // Option 1: Open bank's mobile website
            if let webURL = bank.webAuthURL {
                let safariVC = SFSafariViewController(url: webURL)
                self.present(safariVC, animated: true)
            }
            // Option 2: Show "install bank app" message
            else {
                self.showBankAppRequired(bank: bank)
            }
        }
    }
}

On Android, use App Links verification to ensure the bank's app handles the intent:

fun openBankForSCA(scaUrl: String) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(scaUrl))

    try {
        startActivity(intent)
    } catch (e: ActivityNotFoundException) {
        // Fall back to web-based authentication
        val webIntent = Intent(Intent.ACTION_VIEW, Uri.parse(scaUrl))
        webIntent.setPackage("com.android.chrome")
        startActivity(webIntent)
    }
}

Security Requirements

State Parameter

Every PSD2 redirect must include a state parameter to prevent CSRF attacks. Generate a random value, store it, and verify it when the user returns:

func generateState() -> String {
    let state = UUID().uuidString
    UserDefaults.standard.set(state, forKey: "psd2_state")
    return state
}

func validateState(_ state: String?) -> Bool {
    let stored = UserDefaults.standard.string(forKey: "psd2_state")
    UserDefaults.standard.removeObject(forKey: "psd2_state")
    return state != nil && state == stored
}

Redirect URI Validation

Banks must validate that the redirect URI matches the registered TPP. Do not accept arbitrary redirect URIs from deep links.

Certificate Validation

Under PSD2, TPPs must use eIDAS certificates for API communication. While this is server-side, it affects the deep link flow because the bank must verify the TPP's identity before showing the consent screen.

Regional Considerations

PSD2 applies to the European Economic Area (EEA). Similar regulations exist in other regions:

Region Regulation Deep Link Impact
EU/EEA PSD2 SCA redirects, TPP consent flows
UK UK Open Banking Similar to PSD2, uses UK-specific standards
Australia CDR (Consumer Data Right) Data sharing consent flows
Brazil Open Finance Brazil Payment initiation and consent
India Account Aggregator Consent-based data sharing

Each regulation has its own redirect flow patterns, but the deep linking principles are the same: redirect to the bank app for consent/authentication, then redirect back with the result.

Tolinku for PSD2 Flows

Tolinku supports the route patterns PSD2 redirect flows need. Define callback routes in the Tolinku dashboard. Universal Links and App Links ensure that PSD2 redirects open the correct app reliably on both iOS and Android.

For fintech compliance, see fintech compliance and deep links. For fintech deep linking broadly, see deep linking for fintech and banking apps.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.