Quick Start
Platform Examples
Checkout Customization
Mobile Recipes
start(...)
call, with abandoned-session recovery built in. Reach for a manual WebView only
if none of them fits your stack.Prerequisites
Before integrating Dodo Payments into your mobile app, ensure you have:- Dodo Payments Account: Active merchant account with API access
- API Credentials: API key and webhook secret key from your dashboard
- Mobile App Project: Android, iOS, React Native, or Flutter application
- Backend Server: To securely handle checkout session creation
Integration Workflow
The mobile integration follows a secure 4-step process where your backend handles API calls and your mobile app manages the user experience.status is only a UI hint for what to show the user. Always grant access from the payment.succeeded / subscription.active webhook on your backend — never from the mobile result alone.Backend: Create Checkout Session
Checkout Session API Docs
Mobile: Get Checkout URL
- iOS (Swift)
- Android (Kotlin)
- React Native (JavaScript)
- Flutter (Dart)
Mobile: Open Checkout in Browser
Pick your mobile SDK
Backend: Handle Payment Completion
Choose Your SDK
Every mobile SDK exposes the same contract: onestart(...) call opens Dodo’s
hosted checkout in the platform’s native browser surface and returns a typed
CheckoutResult whose status is succeeded, failed, cancelled,
pending, or expired. None of them holds an API key or calls the Dodo
Payments API, and all four support abandoned-session recovery.
Android
com.dodopayments.api:checkout-android opens a Chrome Custom Tab. Requires minSdk 23.iOS
dodopayments-mobile-sdk-ios opens SFSafariViewController. Requires iOS 16+.React Native
@dodopayments/react-native-checkout, a Turbo Module over both native cores. Requires React Native 0.76+.Flutter
dodopayments_checkout, a Pigeon channel over both native cores. Requires Flutter 3.44+.Registering a Callback URL Scheme
All four SDKs hand control back to your app through a custom URL scheme that you choose, for examplemyapp://checkout/return. Register it once per
platform:
- Android
- iOS
- Expo
checkout_url in the platform’s system
browser (Android Custom Tabs / iOS SFSafariViewController) and intercept the
navigation to your return_url, then read the status and payment_id query
parameters. The SDKs above do exactly this for you.Appearance Customization
Every SDK accepts an optionalcustomization parameter on start(...) /
CheckoutParams that controls the native browser surface’s appearance and
behavior - the toolbar, buttons, and presentation. This is separate from the
checkout page’s own theme, which you configure server-side via
customization.theme_config on the
checkout session.
Options are grouped by platform because Android’s Custom Tab and iOS’s
SFSafariViewController expose different native controls. All fields are
optional; omitting customization entirely uses each platform’s default
appearance.
Android - Custom Tab
Android - Custom Tab
default shows the system “X” icon; back draws a back arrow instead.iOS - SFSafariViewController
iOS - SFSafariViewController
pageSheet presents as a card with swipe-to-dismiss; fullScreen covers the whole screen.presentationStyle is fullScreen - pageSheet keeps the bars pinned regardless of this setting.- React Native
- Flutter
- Android (Kotlin)
- iOS (Swift)
Checkout Page Customization
The Appearance Customization section above controls the native browser surface - toolbar, buttons, color scheme. The checkout page itself - what fields appear, the theme, which payment methods show - is configured server-side when you create the checkout session. These parameters have the most impact on mobile conversion. The parameters below sit in three different places on the checkout session request - the Where it goes column tells you which object each one belongs in. Getting this wrong is the most common mistake: a parameter placed in the wrong object is silently ignored.
show_order_details: false moves the contact and payment fields above the fold, instead of behind the order summary.
minimal_address: true to collect only a postcode instead of the full street, city, and state fields:

minimal_address: true reduces the billing address to a single postcode field.
theme: "system" so the checkout follows the device’s light or dark mode preference:

With theme: system, the checkout follows the device's light or dark appearance automatically.
Full checkout session parameter reference
Mobile-Optimized Recipes
Each recipe below is a complete checkout session request body. Copy the one that matches your scenario, swap in your product ID, and pass it to your backend’s session-creation endpoint.Minimal Mobile Checkout - fastest path to payment
Minimal Mobile Checkout - fastest path to payment
- Node.js SDK
- Python SDK
One-Click Returning Customer - saved card, instant confirmation
One-Click Returning Customer - saved card, instant confirmation
confirm: true to skip the checkout form entirely.- Node.js SDK
- Python SDK
status in the deep-link return is a UI hint only. Confirm access by listening for the payment.succeeded webhook on your backend.Subscription with Free Trial - trial before first charge
Subscription with Free Trial - trial before first charge
- Node.js SDK
- Python SDK
subscription.active webhook - not when the mobile SDK returns. See Subscription Integration Guide for the full webhook flow.On-Demand Mandate - save a card for future variable charges
On-Demand Mandate - save a card for future variable charges
- Node.js SDK
- Python SDK
Subscription Flows from Mobile
Subscriptions are created through the same checkout session flow used for one-time payments - the mobile SDK opens the hosted checkout, the customer subscribes, and your app handles the deep-link return. The subscription lifecycle is then managed entirely on the backend.Regular Recurring Subscriptions
For fixed-interval billing (monthly, annual), create a checkout session with a subscription product and a deep-linkreturn_url. Your backend receives subscription.active when the subscription is confirmed.
On-Demand Subscriptions
On-demand subscriptions let you authorize a customer’s payment method once and charge variable amounts later - ideal for wallet top-ups, pay-as-you-go, and any scenario where the charge amount is not known in advance. See the On-Demand Mandate recipe above for the full request body. Key mobile considerations:- Set
show_on_demand_tag: falseso the checkout page does not show “subscription” or “on-demand” language. For card-tokenization use cases, customers do not expect subscription terminology. - After the mandate is authorized, your backend receives
subscription.active. Store thesubscription_id- you will use it for all future charges.
Subscription with Free Trial
Passsubscription_data.trial_period_days in the checkout session to offer a trial before the first billing cycle. The customer authorizes their payment method during trial signup; the first charge happens automatically when the trial ends. See the Subscription with Free Trial recipe above for the full request body.
Upgrades and Downgrades
Plan changes are made via API on your backend, not through a new checkout session. Dodo Payments calculates proration automatically. To give customers a self-service option, embed or link to the Customer Portal.Subscription Integration Guide
On-Demand Subscriptions
Upgrade / Downgrade
Customer Portal
Reducing Checkout Drop-Offs
Mobile checkouts see higher abandonment than web - smaller screens, more distractions, and longer forms all contribute. The fastest improvements come from the checkout session configuration itself.Optimize the Form
Pre-Fill Customer Data
Every field the customer does not have to type is a reason not to abandon:- New customers - set
customer.emailandcustomer.namefrom your auth session. - Returning customers - set
customer.customer_idto pre-fill all stored details automatically. - Currency - always pass
billing_currencyandbilling_address.countrytogether.
Recovery Tools
Abandoned Cart Recovery
Payment Retries
Subscription Dunning
Recovery Overview
Best Practices
- Security: Never ship an API key in your app. Create checkout sessions on your backend and pass only the resulting
checkout_urlto the client. - Authority: Treat
CheckoutResult.statusas a UI hint. Grant access only after your backend confirms the payment. - User Experience: Show a loading state while your backend creates the session, and handle
cancelledas a normal outcome rather than an error. - Testing: Use test mode and test cards, and verify the return-URL round trip on a real device as well as a simulator.
- Conversion: Set
show_order_details: falseandminimal_address: truefor the best mobile checkout completion rates. Moving payment methods above the fold and reducing form fields are the two highest-impact changes you can make. - Currency: Always pass both
billing_currencyandbilling_address.countryexplicitly - if either is missing, adaptive currency may change the billing currency based on the customer’s IP address. - On-demand billing: Set
show_on_demand_tag: falsewhen using on-demand subscriptions for card tokenization. Customers using a wallet top-up flow do not expect to see “subscription” language. - Recovery: Enable abandoned cart recovery in your Dodo Payments dashboard to automatically re-engage customers who do not complete checkout.
Troubleshooting
Common Issues
- Callback never arrives: The scheme in
returnUrlmust match the one you registered. On Android that is thedodoCallbackSchememanifest placeholder; on iOS and React Native it is theInfo.plistURL type. - Checkout returns to the browser instead of your app (iOS): You haven’t forwarded the incoming URL. Call
DodoCheckout.handleOpenURL(url)from.onOpenURL,scene(_:openURLContexts:), or a React NativeLinkinglistener. PLATFORM_ERRORon Android: Most often a scheme mismatch. It can also appear if yourMainActivitysetsandroid:taskAffinity=""(the stockflutter createdefault), which lets some OEM builds lose the in-flight checkout.ALREADY_IN_PROGRESS: A checkout is still open. Await or dismiss the previous one before starting another.- Build fails with an unresolved placeholder: You added the Android SDK but never set
manifestPlaceholders["dodoCallbackScheme"]. - Payment succeeded but access wasn’t granted: Expected if you’re keying off the mobile result. Grant access from the
payment.succeeded/subscription.activewebhook instead. - Apple Pay / Google Pay not showing on mobile: The checkout is loading inside an embedded WebView (
WKWebView/ AndroidWebView), which suppresses wallets and can break 3-D Secure. Open it with the SDK or in the system browser (Custom Tabs /SFSafariViewController) instead.
Additional Resources
- Payment Integration Guide
- Webhook Documentation
- Testing Process
- Technical FAQs
- Checkout Session Customization
- On-Demand Subscriptions
- Subscription Upgrade/Downgrade
- Abandoned Cart Recovery
- Customer Portal
