---
title: "How to add blockchain payments to a mobile fintech app: architecture, onboarding, quotes, and push notifications"
seoTitle: "How to add blockchain payments to a mobile fintech app"
description: "Add stablecoin payins and payouts to an iOS or Android app: keep API keys on your server, onboard users in-app, show live quotes, and push webhook updates."
date: "2026-10-16"
updated: "2026-10-16"
category: "payments"
author: "BlindPay Team"
howto:
  name: "How to add blockchain payments to a mobile fintech app"
  steps:
    - name: "Put a backend between the app and the payments API"
      text: "The app talks only to your server. Your server holds the payments API key, calls the API, and returns only what the screen needs. Never ship the key inside the app bundle."
    - name: "Onboard the user inside the app"
      text: "Generate a terms of service link on the server, open it in an in-app browser, catch the redirect that carries the tos_id, then create the customer with KYC data collected in your own screens."
    - name: "Show a live quote with a countdown"
      text: "Request the quote from your server, display the recipient amount and fees, and count down to expires_at, which is in epoch milliseconds. When it expires, fetch a new quote instead of executing the old one."
    - name: "Execute with an idempotency key"
      text: "Create one idempotency key per tap on the confirm button and have your server send it, with the exact same request body, on every retry. A dropped mobile connection then cannot create a second payout."
    - name: "Turn webhooks into push notifications"
      text: "Receive payin and payout webhooks on your server, verify the signature, update your database, and send a push notification to the user's device. The app reads the latest status from your server when it opens."
faq:
  - q: "Can a mobile app call a stablecoin payments API directly?"
    a: "It should not. Payment API keys usually carry full read and write access, and anything shipped in an app bundle can be extracted. BlindPay's docs say never to expose API keys in mobile apps, and OWASP lists improper credential usage, including hardcoded credentials, as the first risk in its 2024 Mobile Top 10. Route every call through your own backend."
  - q: "How do users complete KYC in a mobile app?"
    a: "Collect identity data in your own screens and send it to your server, which creates the customer through the API. With BlindPay, the user first accepts terms of service on a hosted page opened from your app, which returns a tos_id to your redirect URL. KYC Standard reviews take about 60 seconds; Enhanced KYC and KYB take 3 hours to 1 business day."
  - q: "How should a mobile app display a stablecoin quote?"
    a: "Show the amount the recipient gets, the fees, and a countdown to the quote's expiry. BlindPay payout quotes expire 5 minutes after creation and return expires_at in epoch milliseconds. Use the server's expiry, not a timer started on the phone, and request a fresh quote when it passes. Never execute a stale quote."
  - q: "How do push notifications work for blockchain payments?"
    a: "The payments provider sends webhooks to your server, not to the phone. Your server verifies each webhook signature, stores the new status, and then sends a push notification through Apple or Google. For BlindPay payouts, listen for payout.new, payout.update, and payout.complete, and read the status field, because a failed payout can arrive as an update."
  - q: "Do mobile users need a crypto wallet to use blockchain payments?"
    a: "No. A user can send a bank transfer that settles into stablecoins, or receive a payout in local currency, without seeing a wallet. If your app holds stablecoins, you choose between wallets your users control, which need an on-chain approval or signature per payout, and provider-managed wallets, which need no signing but are custodial."
  - q: "How do you test mobile payment flows without real money?"
    a: "Use a development instance. On BlindPay, development instances auto-approve KYC, use USDB as a test stablecoin on testnets, and complete payouts automatically. Set a payout quote's request_amount to 66600 to force a failed payout or 77700 to force a refund, then confirm your app's screens and push notifications handle each outcome."
---

To add blockchain payments to a mobile fintech app, put your own backend between the app and the payments API. The server holds the API key, creates customers, requests quotes, and executes payins and payouts. The app collects input, shows a live quote with a countdown, and receives push notifications that your server sends after verifying each webhook.

None of this needs the user to understand a blockchain. The [blockchain payments vs crypto payments](/resources/more/blockchain-payments-vs-crypto-payments) guide covers why: the stablecoin is the settlement layer, and the user sees dollars, reais, or pesos.

**Key takeaways**

- The API key lives on your server. A key in an app bundle is a key an attacker can extract.
- Onboarding is a hosted terms of service page plus KYC data from your own screens.
- Quotes expire. Count down from the server's `expires_at`, and re-quote rather than retry.
- Mobile networks drop requests. One idempotency key per confirm tap stops duplicate payouts.
- Webhooks go to your server first, then become push notifications.

## What does the architecture look like?

Three parts: the app, your backend, and the payments API. The app never talks to the payments API directly.

| Component | Runs where | Holds | Does |
| --- | --- | --- | --- |
| Mobile app | User's phone | Your own session token | Collects input, shows quotes and statuses |
| Your backend | Your servers | Payments API key, webhook secret | Calls the API, stores state, sends pushes |
| Payments API | Provider | Customers, quotes, payouts | Converts, settles, pays out |
| Push service | Apple or Google | Device tokens | Delivers notifications |

> **Definition:** A **backend for frontend** is a thin server layer that exists only to serve one app. It authenticates your user, calls third-party APIs with secrets the app must not hold, and returns only the fields a screen needs.

This is the same shape you would use for any card or bank API. The blockchain part sits behind the payments API, so the app code looks like any other fintech app.

## Why can't the app hold the API key?

Because anything inside an app bundle can be read by someone who downloads it. BlindPay's [API key docs](/docs/learn/api-keys) say it directly: never expose API keys in client-side code or mobile apps.

The stakes are high for payments. A BlindPay key carries full read and write access to its instance, so a leaked key could create payouts. OWASP ranks [improper credential usage as M1 in its 2024 Mobile Top 10](https://owasp.org/www-project-mobile-top-10/2023-risks/m1-improper-credential-usage.html), and hardcoded credentials are the first example it gives.

On the server side, two extra controls help. Restrict each key to your servers' IPv4 addresses with `ip_whitelist`, and keep separate keys for your development and production instances. BlindPay also ships official SDKs for Node.js, Python, Go, PHP, and Swift; whichever one you use, run it where the key is safe.

## How does onboarding work on a phone?

The user accepts terms of service on a hosted page, then your server creates the customer with the KYC data your app collected. Two screens of your own, one hosted page.

1. **Generate the terms link.** Your server calls BlindPay's terms of service endpoint and gets back a URL, per the [terms of service docs](/docs/learn/terms-of-service). The page shows your instance logo and name, and uses your accent color on the accept button.
2. **Open it in an in-app browser.** The terms can only be accepted by the user, client side, on app.blindpay.com, so the page must load on the phone. Set a `redirect_url` you control. On mobile, a verified link such as an iOS [universal link](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) or an [Android App Link](https://developer.android.com/training/app-links) can send the user straight back into your app.
3. **Catch the tos_id.** The redirect carries a `tos_id` query parameter. Send it to your server.
4. **Create the customer.** Your server creates the customer with the `tos_id` and the identity fields from your screens.
5. **Wait for the review.** KYC Standard takes about 60 seconds and is automatic. KYC Enhanced and KYB Standard take 3 hours to 1 business day, per the [sandbox vs production guide](/docs/learn/sandbox-vs-production). A `customer.update` webhook tells your server when the status changes.

Design the waiting state. A user who finishes onboarding at 11pm and gets a manual review needs a screen that says what happens next, and a push when it clears.

## How should a quote look on a small screen?

Lead with the amount the recipient gets, then fees, then a countdown. Users read the top number and the timer, so put the firm numbers there.

BlindPay payout quotes expire 5 minutes after creation and return `expires_at` as epoch milliseconds, per the [payout quotes docs](/docs/payout-quotes). Start the countdown from that value, not from when the phone received the response, since a slow network can eat 10 seconds. When the timer hits zero, disable the confirm button and fetch a new quote.

| Screen element | Source field | Note |
| --- | --- | --- |
| Recipient gets | `receiver_amount` | Minor units, so 10000 means 100.00 |
| You send | `sender_amount` | Includes fees if the sender covers them |
| Countdown | `expires_at` | Epoch milliseconds |
| Fee lines | `flat_fee`, `partner_fee_amount`, `billing_fee_amount` | Show each one, not only a total |
| Rate | `blindpay_quotation` | The rate net of BlindPay's fee |

Two quote settings change the copy. `currency_type: receiver` fixes the amount that lands, which suits "pay exactly 500 reais". `cover_fees: true` adds fees on top of what the sender pays. The [quote screen guide](/resources/more/cross-border-quote-screen-fee-disclosure) covers fee disclosure, and [handling expired quotes](/resources/more/handle-expired-quotes-typescript-python) has the re-quote logic in TypeScript.

## How do you stop a flaky connection from paying twice?

With an idempotency key per user action, reused on every retry. Phones switch between Wi-Fi and cellular mid-request all the time, and a timeout does not tell you whether the payout was created.

Generate the key when the user taps confirm, send it to your server, and have the server pass it as the `Idempotency-Key` header. BlindPay replays the original response for the same key and an identical body, for 24 hours, per its [idempotency docs](/docs/learn/idempotency). The body check is byte for byte, so your server must store and resend the exact body, not rebuild it.

| Situation | What the server does |
| --- | --- |
| Request timed out | Retry with the same key and same bytes |
| `409 idempotency_key_in_flight` | Wait for the `Retry-After` seconds, then retry |
| `422 idempotency_key_payload_mismatch` | Bug: the body changed. Stop and log |
| User taps confirm again after a new quote | New action, new key |

The [idempotency keys guide](/resources/more/stablecoin-api-idempotency-keys) goes deeper on key design.

## Do your users need a crypto wallet?

No. Most mobile fintech users never see one. Bank transfers in and local bank payouts out cover the common flows, and the stablecoin settles between them.

If your app holds stablecoin balances, you pick one of two models. A wallet the user controls means the app asks for an on-chain authorization per payout: an ERC-20 approve on EVM chains, a signed transaction on Stellar, or a token delegation on Solana. A BlindPay-managed wallet (beta) needs no signing on the phone, but it is custodial, so read the [custodial vs non-custodial wallet comparison](/resources/more/custodial-vs-non-custodial-vs-mpc-wallets) before choosing.

For user-controlled EVM wallets, register the address with a signed message rather than a pasted address. BlindPay recovers the address from the signature, so a typo cannot send funds to the wrong place.

## How do webhooks become push notifications?

Your server receives the webhook, verifies it, stores the status, and then sends the push. The phone never receives the raw webhook.

Verify the `svix-id`, `svix-timestamp`, and `svix-signature` headers against the raw body before trusting a payload, and use `svix-id` to drop duplicate deliveries, per the [webhook verification docs](/docs/learn/webhooks-verification). Then map events to messages:

| Event | Push copy example |
| --- | --- |
| `customer.update` | "Your account is verified" (or "We need one more document") |
| `virtualAccount.complete` | "Your US account details are ready" |
| `payin.complete` | "Your deposit arrived" |
| `payout.new` | "Your transfer is on its way" |
| `payout.update` | Read `status`: on hold, failed, or in progress |
| `payout.complete` | Read `status`: completed or refunded |

Keep pushes short and let the app fetch details from your server. Lock screens show the first line to anyone nearby, so leave amounts and names out of the notification text. [Payout statuses explained](/resources/more/stablecoin-payout-statuses-explained) lists what each status means for the user.

## What should a mobile team look for in a blockchain payments provider?

Look for a server-side API with clear mobile guidance, not a mobile SDK that wants your key on the phone. A short checklist:

- **Server-side auth** with IP allowlisting and separate keys per environment.
- **Hosted onboarding pieces** that open in an in-app browser and redirect back.
- **Quotes with an explicit expiry** you can count down from.
- **Idempotency** on every mutating call.
- **Signed webhooks** for every state change, so pushes are timely.
- **A development environment** with test tokens and forced outcomes.

BlindPay covers each of these. Development instances auto-approve KYC, use USDB on testnets, and complete payouts automatically. A payout quote with `request_amount` of 66600 forces a failed payout and 77700 forces a refund, so you can test every push and screen before launch. The [blockchain payment API checklist](/resources/more/how-to-choose-a-blockchain-payment-api) has the full evaluation list.

## What to do next

Sketch your three-box architecture, then wire one flow end to end on a development instance: terms, customer, quote, payout, push. [Start with the BlindPay payout quickstart](/docs/quickstart-payout) and run it from your backend, not the app.
