Open Paddle Checkout from iOS apps
Get a step-by-step guide on how to implement Paddle Checkout as an external payment flow for your iOS app, letting you go direct to customers while remaining compliant.
With recent developments in legislation around the App Store, you can direct users in the United States to an external checkout for purchases in iOS apps.
You can use Paddle Checkout to quickly and securely collect for payment for digital products. It presents customers with a secure, optimized payment workflow that handles all parts of the checkout process — minimal frontend coding required.
We're working on a starter kit that you can use to deploy a checkout for iOS apps in a couple of clicks. If you'd like to be the first to get access, join the waitlist.
What are we building?
In this tutorial, we'll create a payment flow that lets your iOS app users make purchases through Paddle Checkout.
What's included with Paddle
With Paddle Checkout, you get:
Global tax compliance
As a merchant of record, Paddle calculates, collects, and remits taxes for you.
Chargeback protection
Paddle handles chargebacks, fights fraud, and stops card attacks.
Lower fees than IAPs
Go directly to your users and save on fees, while boosting customer LTV.
Integrated with Paddle Retain
Beat churn and boost revenue with a suite of best-in-class retention tools.
Built-in buyer support
Customers can self-serve with the portal, and Paddle handles order inquiries.
One single payment platform
Turn on new payment methods with no code or merchant accounts needed.
What's not covered
This tutorial walks you through launching Paddle Checkout from an iOS app. It doesn't cover:
Handling authentication
We assume you already have a way to identify your users, like Firebase or Sign in with Apple.
Native in-app purchases
We'll launch Paddle Checkout in Safari then redirect users back to your app. Paddle Checkout supports Apple Pay with no additional setup, along with PayPal, Google Pay, and other popular payment options.
Subscription lifecycle management
You can use Paddle to handle all parts of the subscription lifecycle, including updating payment methods and canceling subscriptions using the prebuilt customer portal. We cover that elsewhere in our docs.
Overview
Implement a payment workflow for your iOS app in six steps:
Create products and prices in Paddle that match your in-app purchase options.
Include Paddle.js on a page on your website, then handle the post-purchase redirect back to your app.
Create a transaction in Paddle
Build logic in your backend to create a customer and a transaction in Paddle, ready for checkout.
Launch a checkout from your iOS app
Add a button to your app that creates a transaction in Paddle and opens Paddle Checkout in Safari.
Handle fulfillment and provisioning using webhooks
Process webhooks to fulfill purchases after a customer completes a checkout.
Verify your payment flow works correctly using test card details.
1. Map your product catalog
Before we implement Paddle Checkout, we need to set up our product catalog in Paddle to match the in-app purchases offered in-app.
Model your pricing
A complete product in Paddle is made up of two parts:
- A product entity that describes the item, like its name, description, and an image.
- At least one related price entity that describes how much and how often a product is billed.
You can create as many prices for a product as you want to describe all the ways they're billed.
In this example, we'll create a single product and single price for a one-time item called Lifetime Access
.
Create products and prices
You can create products and prices using the Paddle dashboard or the API.
Go to Paddle > Catalog > Products.
Click New product.
Enter details for your new product, then click Save when you're done.
Under the Prices section on the page for your product, click New price.
Enter details for your new price. Set the type to One-time to create a one-time price.
Click Save when you're done.
2. Add Paddle.js to your website
Paddle.js is a lightweight JavaScript library that lets you build rich, integrated billing experiences using Paddle. We can use Paddle.js to securely open a checkout, capture payment information, and launch our success workflow.
When you add Paddle.js to a page, you can redirect to that page and append a _ptxn
query parameter with the value of a transaction ID in Paddle to launch a checkout automatically.
You can create a new page, or add to an existing one like your homepage.
Include and initialize Paddle.js
You can install and import Paddle.js using a JavaScript package manager.
Install Paddle.js using
npm
,yarn
, orpnpm
.11npm install @paddle/paddle-jsImport Paddle.js, then initialize by calling the
initializePaddle()
function with a configuration object.We'll get a client-side token in the next step.
123451import { initializePaddle } from '@paddle/paddle-js'; 2 3const paddle = await initializePaddle({ 4 token: 'CLIENT_SIDE_TOKEN' 5});
Get a client-side token
Client-side tokens are for authenticating with Paddle in your frontend. We need one to securely open Paddle Checkout.
Go Paddle > Developer tools > Authentication
Click the Client-side tokens tab, then click New client-side token.
Give your client-side token a name and description, then click Save.
From the list of client-side tokens, click the … action menu next to the client-side token you just created, then choose Copy token from the menu.
Paste your token as
CLIENT_SIDE_TOKEN
in the code you copied.
Build a success workflow
After users complete a purchase successfully, we need to bounce users back to our app.
To do this, we can use an event callback function. Paddle.js emits events throughout the checkout process when key things happen. An event callback function is some code that we run when a specific event occurs.
In our case, when Paddle.js emits a checkout.completed
event, we're going to redirect to a screen in our app.
Import Paddle.js events, then update your Paddle configuration object to include an eventCallback
:
12345678910111213141import { initializePaddle } from '@paddle/paddle-js';
2import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js';
3
4const paddle = await initializePaddle({
5 token: 'CLIENT_SIDE_TOKEN',
6 eventCallback: (event: PaddleEventData) => {
7 if (event.name === CheckoutEventNames.CHECKOUT_COMPLETED) {
8
9 setTimeout(() => {
10 window.location.href = `myapp://example-redirect?transactionId=${event.data?.id}`;
11 }, 3000);
12 }
13 }
14});
Replace myapp://example-redirect
with a universal link in your iOS app, but keep the ?transactionId=${event.data?.id}
part. This is important as it gives us reference for the checkout in our app.
When you're done, deploy your page to your website.
Set your default payment link
Your default payment link is a quick way to open Paddle Checkout for a transaction. It's also used in emails from Paddle that let customers manage purchases that are recurring. You need to set a default payment link before you can launch a checkout.
We'll set our default payment link to the page where we just added Paddle.js:
Go to Paddle > Checkout > Checkout settings.
Enter the page where you added Paddle.js under the Default payment link heading.
Click Save when you're done.
3. Create a transaction in Paddle
Transactions are the central billing entity in Paddle. They capture and calculate revenue for a customer purchase, and represent what they see when they open a checkout.
We'll create a transaction with details about what our customer is purchasing in our backend, then extract the checkout link to launch a checkout. We can use the transaction ID to match with a completed purchase later.
Install Paddle
First, add the Paddle SDK to your backend. Paddle has SDKs for Node.js, Python, PHP, and Go.
Install using npm
, yarn
, or pnpm
. For example:
11npm install @paddle/paddle-node-sdk
Get an API key
API keys are for authenticating with Paddle in your backend. We need one to create a transaction.
Go Paddle > Developer tools > Authentication
Click the New API key button.
Give your key a name and description, then set an expiry date.
Under permissions, check Write for customers and transactions. You can always edit permissions later.
Click Save when you're done, then copy the API key.
Store this safely in your credential manager or secret store.
Treat your API key like a password. Keep it safe and never share it with apps or people you don't trust.
Set up the endpoint
You need to set up an endpoint to call from your iOS app to create the transaction in Paddle and return the checkout URL.
12345678910111213141516171819201const express = require('express');
2const cors = require('cors');
3const bodyParser = require('body-parser');
4
5// Import Paddle SDK
6const { Paddle } = require('@paddle/paddle-node-sdk');
7
8// Express setup
9const app = express();
10const PORT = process.env.PORT || 3000;
11app.use(cors());
12app.use(bodyParser.json());
13app.use(bodyParser.urlencoded({ extended: true }));
14require('dotenv').config();
15
16// Initialize Paddle
17// Assumes you have a Paddle API key in the .env file
18const paddle = new Paddle(process.env.PADDLE_API_KEY);
19
20app.post("/paddle/create-transaction", async (req, res) => {
Create a customer
If your user is signed in to your app, create a customer in Paddle for them.
Customers are lightweight entities that hold high-level details, like name and email address. They have related address and business entities.
121314151617181920212223242526272829303112app.use(bodyParser.json());
13app.use(bodyParser.urlencoded({ extended: true }));
14require('dotenv').config();
15
16// Initialize Paddle
17const paddle = new Paddle(process.env.PADDLE_API_KEY);
18
19app.post("/paddle/create-transaction", async (req, res) => {
20 try {
21 // 1. Fetch or create a Paddle customer
22 const { userId } = req.body;
23
24 const existingUser = await User.findOne({ where: { id: userId } });
25
26 if (!existingUser) {
27 return res.status(404).json({ error: 'User not found' });
28 }
29
30 // Check if customer already exists in Paddle
31 if (!existingUser.paddleCustomerId) {
We recommend storing the customer ID against your authentication provider, so you can associate the customer with their purchases in Paddle.
Create a transaction and extract the URL
To set up a checkout, create a transaction with:
- The customer ID of this customer.
- The items the customer is purchasing, which include the price ID we set up earlier and a quantity for each.
Once created, extract the checkout.url
and return it to your app.
If you've integrated with RevenueCat for order fulfilment, include an object of custom data containing a unique identifier for RevenueCat.
323334353637383940414243444546474849505132 email: existingUser.email,
33 name: existingUser.name
34 });
35
36 // Update the user with the Paddle customer ID
37 await existingUser.update({ paddleCustomerId: customer.id });
38 existingUser.paddleCustomerId = customer.id;
39 }
40
41 // 2. Create the transaction
42 // (Optional) Grab RevenueCat metadata field ID passed from the iOS app.
43 // You can use any name to pass through. We use revenuecatId.
44 const { revenuecatId } = req.body;
45
46 // Grab the items from the request body
47 // This is an array of objects with a price_id and quantity for each item the customer is purchasing
48 const { items } = req.body;
49
50 if (!Array.isArray(items) || items.length === 0) {
51 return res.status(400).json({ error: "Items array is required and can't be empty" });
4. Launch a checkout
Update your iOS app to add a button that:
Checks to see if in-app purchases are allowed on the device
Checks to see if a user already purchased the item.
Calls your backend endpoint to create a Paddle transaction.
Opens the URL returned by your endpoint in Safari.
Listens for when the app is reopened via URL scheme after the checkout is completed.
Extracts the transaction ID from the redirect URL query parameter.
Updates the UI and enters into your order fulfilment workflow.
Remember to register a custom URL scheme for your redirect in your
Info.plist
file if you haven't already.
5. Handle fulfillment and provisioning
If you use the RevenueCat x Paddle integration to handle entitlements, you're all set!
Here's how it works:
Paddle automatically sends data to RevenueCat about the completed checkout.
RevenueCat grants the user an entitlement based on your product configuration.
Use the RevenueCat SDK to check entitlement status in your iOS app.
In our example, we'll grant users access when they've completed a purchase for our Lifetime Access
product.
Build a webhook handler
When a customer creates or completes a transaction, Paddle sends a webhook to your endpoint. You can store details of the transaction in your database and associate it with the user's account.
Add a new endpoint to the existing server-side code as set up in Set up the endpoint.
12345678910111213141516171819201app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => {
2 try {
3 // You can verify the webhook signature here
4 // We don't cover this in the tutorial but it's best practice to do so
5 // https://developer.paddle.com/webhooks/signature-verification
6
7 const payload = JSON.parse(req.body.toString());
8 const { data, event_type } = payload;
9 const occurredAt = payload.occurred_at;
10
11 // Listen for vital events from Paddle
12 switch (event_type) {
13 // 1. Record transactions in the database
14
15 // Handle a new transaction
16 // You can create a Transaction database to store records and associate them to a user
17 case 'transaction.created':
18 // Find the user associated with this transaction
19 const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } });
20
Provision user access
When you receive the transaction.completed
webhook, you can use the details to handle order fulfilment and provisioning.
The example below updates a user's access permissions in your database. After this, your iOS app can check for the 'lifetimeAccess' permission to unlock premium features.
444546474849505152535455565758596061626344 await completedTransaction.update({
45 status: data.status,
46 subscriptionId: data.subscription_id,
47 invoiceId: data.invoice_id,
48 invoiceNumber: data.invoice_number,
49 billedAt: data.billed_at,
50 updatedAt: data.updated_at
51 });
52
53 // 2. Provision access to your app
54 // Fetch the user associated with this transaction
55 const user = await User.findOne({ where: { id: completedTransaction.userId } });
56
57 if (user) {
58 // Fetch the items from the transaction
59 const purchasedItems = data.items || [];
60
61 // Add what access the user has based on the items they purchased
62 // For this example, we're using access permissions and storing them in the user model on an accessPermissions field
63 // We also map the Paddle product IDs to the access permissions
Create a notification destination
To start receiving webhooks, create a notification destination. This is where you can tell Paddle which events you want to receive and where to deliver them to.
Go to Paddle > Developer Tools > Notifications.
Click New destination.
Give your destination a name.
Make sure notification type is set to webhook — this is the default.
Enter the URL for your webhook handler, then check the transaction.completed box. You can always edit events later.
Click Save destination when you're done.
6. Take a test payment
We're now ready to test! If you're using a sandbox account, you can take a test payment using our test card details:
Email address | An email address you own |
Country | Any valid country supported by Paddle |
ZIP code (if required) | Any valid ZIP or postal code |
Card number | 4242 4242 4242 4242 |
Name on card | Any name |
Expiration date | Any valid date in the future. |
Security code | 100 |
Next steps
That's it. Now you've built a checkout flow using Paddle Billing, you might like to hook into other features of the Paddle platform.
Learn more about Paddle
When you use Paddle, we take care of payments, tax, subscriptions, and metrics with one unified platform. Customers can self-serve with the portal, and Paddle handles any order inquiries for you.
Build a web checkout
Our tutorial creates a transaction, then passes that transaction to Paddle.js. You can also use Paddle.js to build pricing pages and signup flows on the web, then redirect people to your app.
Build advanced subscription functionality
Paddle Billing is designed for subscriptions as well as one-time items. You can use Paddle to build workflows to pause and resume subscriptions, flexibly change billing dates, and offer trials.
- Open Paddle Checkout from iOS apps
- What are we building?
- What's included with Paddle
- What's not covered
- Overview
- 1. Map your product catalog
- Model your pricing
- Create products and prices
- 2. Add Paddle.js to your website
- Include and initialize Paddle.js
- Get a client-side token
- Build a success workflow
- Set your default payment link
- 3. Create a transaction in Paddle
- Install Paddle
- Get an API key
- Set up the endpoint
- Create a customer
- Create a transaction and extract the URL
- 4. Launch a checkout
- 5. Handle fulfillment and provisioning
- 6. Take a test payment
- Next steps
- Learn more about Paddle
- Build a web checkout
- Build advanced subscription functionality