Payment Methods
Every way a user can deposit or withdraw in the ProphetX embed — what they see, the amount limits, and what your integration has to do for each.
The deposit and withdraw flows offer the methods below. You don't integrate each provider: the embed runs them all. What changes per method is what the user sees, which callbacks fire, and, for a few methods, something your app has to set up.
| Method | Deposit | Withdraw | Your integration needs |
|---|---|---|---|
| Online Bank (AeroPay) | Yes | Yes | Mobile: appLink, for banks that log in with OAuth |
| Crypto (ZeroHash) | Yes | — | Nothing |
| Debit Cards (PayNearMe) | Yes | Yes | Minimum SDK version; allow="payment" if you embed the iframe yourself |
| Apple Pay (PayNearMe) | Yes | — | Minimum SDK version; allow="payment"; mobile: appLink |
| Google Pay (PayNearMe) | Yes | — | Minimum SDK version; allow="payment"; mobile: appLink |
| Cash at Retail (PayNearMe) | Yes | — | Minimum SDK version |
| Wire Transfer | Yes | Yes | Nothing |
Which methods a user sees
Wire Transfer is shown to every user. Every other method appears only when it's enabled for the user's account: the embed reads the user's token and shows a method only when its permission is granted.
| Method | Permission |
|---|---|
| Online Bank | deposit-aeropay, withdraw-aeropay |
| Crypto | deposit-zerohash |
| Debit Cards | deposit-pnm-card, withdraw-pnm-card |
| Apple Pay | deposit-pnm-apple-pay |
| Google Pay | deposit-pnm-google-pay |
| Cash at Retail | deposit-pnm-cash-at-retail |
Every permission in the table needs four gates: kyc, terms, email-verify and not-suspended. Online Bank also needs phone-verify. If any of the four is unmet, even if only terms are still unaccepted, the user sees only Wire Transfer. No error fires; check gates before you open a flow — see User prerequisite and permission errors.
Apple Pay also appears only on devices that can pay with it.
Amounts and limits
The embed's amount step uses the same range for every method, and checks it as the user types:
| Flow | Minimum | Maximum |
|---|---|---|
| Deposit | $9.99 | $100,000 |
| Withdrawal | $1 | $10,000, and never more than the user's balance |
Online Bank narrows it further (see below). Crypto and Wire Transfer don't use the amount step: the crypto amount is chosen in ZeroHash's window, and a wire is arranged with support. A user with less than $1 available can't start a withdrawal; the flow ends with onCancel.
Online Bank
The user links a bank account and pays from it, or withdraws to it, through AeroPay. The tile reads Online Bank.
What the user sees:
- The first time, a text-message code to confirm it's them. Their ProphetX profile needs a phone number and an email for this; if either is missing, the embed says so.
- Their linked bank accounts, or a bank-linking screen if they have none.
- Then the deposit or withdrawal goes ahead from the chosen account.
Limits:
| Flow | Limit |
|---|---|
| Deposit | $25,000 per day, and at most $50,000 still being processed |
| Withdrawal | $25,000 per transaction |
What your integration needs: on web, nothing. In a React Native or Flutter app, some banks log in with OAuth and refuse to run inside a WebView, so the login leaves your app. Pass appLink and set up the return — see Returning to your app. Without it, a user who picks one of those banks can't finish.
Callbacks:
onSuccessfires when the payment completes, and also when the bank has accepted it but it's still settling (the user sees that it's processing). Refresh the balance either way.onErrorwithDEPOSIT_FAILEDorWITHDRAWAL_FAILEDmeans the payment was refused; themessagesays why, for your logs.KYC_REQUIREDon a withdrawal means the user must re-verify first — openuseIdUpload.
Crypto
The user deposits crypto or stablecoins through ZeroHash. The tile reads Crypto. Deposits only: crypto withdrawals aren't available.
What the user sees: the embed opens ZeroHash's own window.
- A first-time user verifies their identity with ZeroHash there. While ZeroHash is still reviewing it, the embed waits, then asks the user to come back later.
- The user picks the amount inside ZeroHash's window, not in the embed's amount field. ZeroHash applies its own limits.
- Crypto isn't available in every region; a user in a restricted region is told so.
What your integration needs: nothing.
Callbacks: onSuccess fires when ZeroHash reports the funds sent, with the amount the user chose there; the deposit may still be settling, so refresh the balance again later. onError carries one of the ZEROHASH_* codes when the deposit can't go ahead — see the deposit error table.
Debit Cards
Debit card deposits and withdrawals, through PayNearMe. The tile reads Debit Cards.
Deposits: the user enters the card in PayNearMe's form inside the embed, for the amount they chose. Saved cards are offered again next time.
Withdrawals:
- They go only to a debit card the user has already deposited with by debit card. A card used through Apple Pay or Google Pay doesn't count, and a user with no such card doesn't see Debit Cards for withdrawals.
- The user picks the card and confirms. The withdrawal then goes to ProphetX for review.
What your integration needs: the minimum SDK version, and allow="payment" if you embed the iframe yourself.
Callbacks:
- Deposit:
onSuccessfires once the money is in the user's ProphetX balance, and also when the payment was accepted but is still settling ("Deposit Processing"). ThetransactionIdis a reference for your records, not a backend transaction ID; to confirm the funds, read the wallet balance. - Withdrawal:
onSuccessmeans the withdrawal is under review. The amount has left the user's ProphetX balance and is held while ProphetX reviews it. Once approved, it's sent to the card; if it's rejected, the amount goes back to the balance. The user is told the result by email. A user can have one withdrawal under review at a time. - Failures:
onErrorwithPAYNEARME_DEPOSIT_FAILEDorPAYNEARME_WITHDRAWAL_FAILED, with PayNearMe's code insidemessage. A withdrawal that got no answer isWITHDRAWAL_UNCONFIRMED.
Apple Pay
Apple Pay deposits, through PayNearMe. The tile appears only where Apple Pay can finish: in Safari on a device with Apple Pay set up, and in iOS apps through the React Native and Flutter SDKs. It's hidden in other browsers and on Android.
What the user sees:
- On web: the Apple Pay sheet opens over the embed.
- In an iOS app: Apple doesn't allow Apple Pay inside a WebView, so the embed opens PayNearMe's page in the device browser to finish, then waits for the money to arrive.
What your integration needs: the minimum SDK version; allow="payment" if you embed the iframe yourself; and in an iOS app, appLink to bring the user back.
Callbacks: the same as a Debit Card deposit.
Google Pay
Google Pay deposits, through PayNearMe. Google Pay can't run inside a frame, so the embed always opens PayNearMe's page to finish it: a new tab on web, the device browser in a mobile app. The embed waits for the money to arrive, and the user can ask it to check again.
What your integration needs: the minimum SDK version; allow="payment" if you embed the iframe yourself; and in a mobile app, appLink to bring the user back.
Callbacks: the same as a Debit Card deposit.
Cash at Retail
The user gets a barcode and pays cash at a participating store, through PayNearMe. The money arrives when they pay, not when the barcode is issued.
What your integration needs: the minimum SDK version.
Callbacks: there's no onSuccess when the barcode is issued, because no money has moved yet. You get onCancel when the user closes the embed. The balance goes up after they pay at the store, so refresh it when the user comes back.
Wire Transfer
For large amounts. Selecting it shows instructions; nothing is sent from the embed. The user contacts ProphetX support to arrange the wire.
| Flow | What the user is told |
|---|---|
| Deposit | For purchases of $5,000 or more (minimum $1,000); can take up to 72 hours |
| Withdrawal | For withdrawals of $5,000 or more; can take up to 72 hours; documents are required for a new bank account |
What your integration needs: nothing.
Callbacks: none for the wire itself, which happens outside the embed. onCancel fires when the user closes the instructions.
Integration requirements
Minimum SDK versions for card and wallet payments
Debit Cards, Apple Pay, Google Pay and Cash at Retail need these SDK versions or newer. Older SDKs can't finish these payments.
| SDK | Minimum version | Why |
|---|---|---|
@prophetx/sdk-js | 0.5.0 | The embed iframe allows payment requests (allow="payment") |
@prophetx/sdk-react | 0.6.0 | The same |
@prophetx/sdk-react-native | 0.4.3 | The WebView passes the payment widget's result back to the embed |
prophetx_sdk (Flutter) | 0.5.0 | The same, for Flutter |
All four versions are published on npm and pub.dev.
Embedding the iframe yourself
If you load the embed URL in your own <iframe> instead of using an SDK, add allow="payment". Without it, the browser blocks Apple Pay and Google Pay inside the frame, and the Apple Pay option doesn't appear.
<!-- Only if you load the embed URL yourself instead of using an SDK. -->
<iframe
src="https://embed.prophetx.co/deposit?..."
allow="payment"
></iframe>The React Native and Flutter SDKs load the embed as the WebView's own page, so there's nothing to set there.
Returning to your app (React Native and Flutter)
Some payments finish in the device browser, outside your app:
- Online Bank, with a bank that logs in with OAuth.
- Google Pay.
- Apple Pay on iOS.
The embed stays mounted the whole time; the return only has to bring your app back to the front. Your app has three things to do, and all three are required.
1. Pass your app link as appLink on every deposit and withdraw. It's a bare link — yourapp://, never yourapp://some/path — because the goal is to reopen the app as it is, not to navigate anywhere. (It's deliberately not ID Upload's redirectUrl, which carries a path on purpose.)
const deposit = useDeposit();
const withdraw = useWithdraw();
deposit.open({
appLink: "yourapp://",
onSuccess: (transactionId) => console.log("deposit success", transactionId),
});
withdraw.open({
appLink: "yourapp://",
onCancel: () => console.log("closed"),
});2. Register that URL scheme with the OS, so the browser has somewhere to send the user. If you already integrated ID Upload, you've done this — one scheme covers both.
<!-- iOS: ios/Runner/Info.plist (Flutter) or ios/YourApp/Info.plist (React Native) -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
</dict>
</array><!-- Android: android/app/src/main/AndroidManifest.xml, inside the LAUNCHER <activity> -->
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Scheme only. No android:host, no android:pathPrefix. -->
<data android:scheme="yourapp" />
</intent-filter>
</activity>On Android, keep launchMode="singleTop" (or equivalent) so the return reuses the running app; a cold start has no embed left to return to.
3. Consume the link without navigating. When the link arrives, handle it and do nothing: no push, no pop, no replace, no rebuild of the screen holding the WebView. A normal deep-link handler routes; this one must not.
import { useEffect } from "react";
import { Linking } from "react-native";
useEffect(() => {
const sub = Linking.addEventListener("url", ({ url }) => {
if (url.startsWith("yourapp://")) return; // the embed's return: ignore it
// ...handle your other deep links here
});
return () => sub.remove();
}, []);Don't add the scheme to a React Navigation linking config: that makes it resolve to a screen and navigate.
How to tell it's wrong:
| What your app did | What the user sees |
|---|---|
Didn't register the scheme, or didn't pass appLink | The app never comes back. The bank widget sits on "Almost there! Bank authenticated, returning to Aerosync." The bank link actually succeeded, but onSuccess never fires. |
| Let the router handle the link | Your home screen is pushed over the open embed and dismisses it; the user loses the flow they were halfway through. |
For card and wallet payments, a missing appLink doesn't stop the payment, but the user has to switch back to your app themselves.
Handling the outcome
The callbacks are the same for every method:
- Refresh the user's balance after
onSuccess, and afteronCancel: Cash at Retail and Wire Transfer end withonCancel, and some payments are still settling whenonSuccessfires. - Treat the wallet balance as the source of truth for whether money arrived.
- Don't close the modal on
onSuccessoronError; the embed shows its own success or retry screen, andonCancelfires when the user closes it.
Every error code, with what the embed shows after it, is in Error Codes.
Updated 2 days ago
