Skip to main content

Quick Start

Get your mobile payment integration running in 4 simple steps

Platform Examples

Complete code examples for Android, iOS, React Native, and Flutter

Checkout Customization

Configure themes, pre-fill, and 14 mobile-specific parameters

Mobile Recipes

Copy-paste checkout configs for 5 common mobile scenarios
Dodo Payments ships an official checkout SDK for Android, iOS, React Native, and Flutter. Each one wraps the pattern documented below (open the checkout URL, capture the return, parse the result) behind a single typed 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.
The deep-link 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.
1

Backend: Create Checkout Session

Checkout Session API Docs

Learn how to create a checkout session in your backend using Node.js, Python, and more. See complete examples and parameter references in the dedicated Checkout Sessions API documentation.
Security: Checkout sessions must be created on your backend server, never in the mobile app. This protects your API keys and ensures proper validation.
2

Mobile: Get Checkout URL

Your mobile app calls your backend to get the checkout URL. Authenticate this request with the signed-in user’s own session token.
Security: Mobile apps only communicate with your backend, never directly with Dodo Payments API.
3

Mobile: Open Checkout in Browser

Open the checkout URL in a secure in-app browser for payment processing. Or skip the manual setup entirely with the official checkout SDK for your platform.

Pick your mobile SDK

Install steps and setup instructions for Android, iOS, React Native, and Flutter.
4

Backend: Handle Payment Completion

Process payment completion via webhooks and redirect URLs to confirm payment status.

Choose Your SDK

Every mobile SDK exposes the same contract: one start(...) 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+.
The status you get back is a UI hint, not proof of payment. Confirm every payment from your backend via the payment.succeeded / subscription.active webhook, or by retrieving the payment with your secret key.

Registering a Callback URL Scheme

All four SDKs hand control back to your app through a custom URL scheme that you choose, for example myapp://checkout/return. Register it once per platform:
android/app/build.gradle
The SDK’s own manifest already declares the redirect activity, so there is no manifest XML to add.
Prefer to build it yourself? Open the 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.
Do not open checkout inside an embedded WebView (WKWebView / Android WebView). This is the single most common mobile integration problem: an embedded WebView suppresses Apple Pay and Google Pay, and can also break 3-D Secure challenges and saved-card autofill - so customers see fewer payment options and more failures. Always use the SDK, or open the checkout_url in the system browser (Custom Tabs / SFSafariViewController). That native browser surface is exactly why Apple Pay and Google Pay keep working.

Appearance Customization

Every SDK accepts an optional customization 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.
Color
Toolbar background color.
Color
Navigation bar color.
Color
Divider color above the navigation bar.
'default' | 'back'
default shows the system “X” icon; back draws a back arrow instead.
'start' | 'end'
Which side of the toolbar the close button appears on.
boolean
Shows the toolbar’s share icon.
boolean
Shows the page title under the URL in the toolbar.
boolean
Lets the toolbar auto-hide as the page scrolls.
boolean
Shows “Bookmark this page” in the overflow menu.
boolean
Shows “Download page” in the overflow menu.
'system' | 'light' | 'dark'
Forces light or dark appearance regardless of the device’s system setting.
'done' | 'close' | 'cancel'
Label or icon for the dismiss button.
'pageSheet' | 'fullScreen'
pageSheet presents as a card with swipe-to-dismiss; fullScreen covers the whole screen.
boolean
Lets the toolbar collapse on scroll. Only visible when presentationStyle is fullScreen - pageSheet keeps the bars pinned regardless of this setting.
'system' | 'light' | 'dark'
Forces light or dark appearance regardless of the device’s system setting.

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.
Always pass billing_currency and billing_address.country together. If either is omitted, adaptive currency may silently change the billing currency based on the customer’s IP address. One merchant saw a US subscription switch to EUR when their customer traveled to Europe - because the billing country was not explicitly set.
Biggest single conversion lift on mobile: set show_order_details: false and minimal_address: true. Moving payment methods above the fold and reducing form fields are the two highest-impact changes you can make.
Side-by-side checkout: order details expanded (fields below the fold) vs collapsed (fields at the top)

show_order_details: false moves the contact and payment fields above the fold, instead of behind the order summary.

Set minimal_address: true to collect only a postcode instead of the full street, city, and state fields:
Side-by-side checkout: full billing address form vs postcode-only

minimal_address: true reduces the billing address to a single postcode field.

Set theme: "system" so the checkout follows the device’s light or dark mode preference:
Side-by-side checkout: same page rendered in light mode and dark mode

With theme: system, the checkout follows the device's light or dark appearance automatically.

Payment method availability varies by product type. Apple Pay and Cash App are supported for non-zero recurring subscriptions. For one-time payments, all enabled methods are available.

Full checkout session parameter reference

See every available parameter, type, and default value in the Checkout Sessions guide.

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.
Use this when you want the shortest possible form: payment methods at the top, only a postcode required for address, no discount field, theme matches the device.
See Checkout Sessions for all available parameters and their defaults.
Use this when the checkout page must feel like part of your app. Set your brand colors, a custom font, and a localized pay-button label.
Branded mobile checkout with a custom dark navy palette applied via theme_config
theme_config accepts separate dark and light objects so the palette adapts to the device’s current appearance. See Checkout Sessions for the full color key reference.
Use this for signed-in users who have paid before. Combine a customer ID, their saved payment method, and confirm: true to skip the checkout form entirely.
The status in the deep-link return is a UI hint only. Confirm access by listening for the payment.succeeded webhook on your backend.
Use this for subscription products that offer a free trial period before the first billing cycle.
Grant feature access when your backend receives the subscription.active webhook - not when the mobile SDK returns. See Subscription Integration Guide for the full webhook flow.
Use this to tokenize a customer’s card for later charges (wallet top-ups, pay-as-you-go, BNPL) without showing a “subscription” label. The customer authorizes their payment method once; you charge variable amounts on demand later.
This is the pattern used by apps that charge based on usage - for example, an astrology app that charges per session from a pre-authorized card, rather than on a fixed schedule.
On-demand charges require a minimum of 1 USD (100 cents). Amounts below 1 USD will be rejected with "value out of range". For a zero-amount authorization, use mandate_only: true as shown above, then charge at least 1 USD in subsequent calls.
See On-Demand Subscriptions for the full charge flow, webhook events, and retry policies.

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-link return_url. Your backend receives subscription.active when the subscription is confirmed.
Apple Pay and Cash App are supported for non-zero recurring subscriptions.
For the complete backend webhook flow, see the Subscription Integration Guide.

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: false so 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 the subscription_id - you will use it for all future charges.
Minimum charge is 1 USD (100 cents). On-demand charges below 1 USD will be rejected with "value out of range". Either charge at least 1 USD, or use mandate_only: true to authorize without charging and collect the first real amount later.
Avoid rapid-fire retries. If a previous charge is still processing, a new charge on the same subscription fails with "Cannot create new charge as previous payment is not successful yet". This is especially common with Indian payment methods (UPI, Indian debit/credit cards) where RBI mandate rules can hold a transaction in processing state for up to 48 hours. Add a cooldown check in your charge logic before retrying.
See On-Demand Subscriptions for the complete charge endpoint, webhook events, and retry policies.

Subscription with Free Trial

Pass subscription_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

Full backend setup: webhook flow, access provisioning, cancellation

On-Demand Subscriptions

Mandate authorization, variable charges, and retry policies

Upgrade / Downgrade

Proration strategies, plan changes, and seat adjustments

Customer Portal

Self-service subscription management for your customers

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.email and customer.name from your auth session.
  • Returning customers - set customer.customer_id to pre-fill all stored details automatically.
  • Currency - always pass billing_currency and billing_address.country together.

Recovery Tools

Abandoned Cart Recovery

Automated email sequences for incomplete checkouts

Payment Retries

Smart retry logic for failed subscription renewals

Subscription Dunning

Re-engagement emails for lapsed subscriptions

Recovery Overview

All recovery tools and their combined revenue impact
Test cart abandonment emails before enabling them. Create a checkout session in live mode and enter invalid card details. The failed payment triggers the recovery email flow, letting you preview exactly what your customers receive.

Best Practices

  • Security: Never ship an API key in your app. Create checkout sessions on your backend and pass only the resulting checkout_url to the client.
  • Authority: Treat CheckoutResult.status as 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 cancelled as 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: false and minimal_address: true for 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_currency and billing_address.country explicitly - 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: false when 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 returnUrl must match the one you registered. On Android that is the dodoCallbackScheme manifest placeholder; on iOS and React Native it is the Info.plist URL 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 Native Linking listener.
  • PLATFORM_ERROR on Android: Most often a scheme mismatch. It can also appear if your MainActivity sets android:taskAffinity="" (the stock flutter create default), 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.active webhook instead.
  • Apple Pay / Google Pay not showing on mobile: The checkout is loading inside an embedded WebView (WKWebView / Android WebView), 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

For questions or support, contact support@dodopayments.com.
Last modified on August 20, 2026