# Paddle Developer Docs — full documentation Concatenated plain-text content of the Paddle Developer Docs at https://developer.paddle.com. Changelog entries and API reference are excluded — see their individual `.md` siblings (listed in https://developer.paddle.com/llms.txt) for those. --- # Build and deploy Next.js app with Vercel and Supabase URL: https://developer.paddle.com/get-started/starter-kits/nextjs-saas Get a step-by-step overview of how to build a Next.js app with Paddle Billing, including a localized pricing page, integrated inline checkout, and screens for customers to manage their payments. This tutorial walks through deploying the Paddle Next.js SaaS starter kit to Vercel, hooking it up to your Paddle account, and taking a test payment. By the end, you'll have a working three-tier subscription app integrated with Supabase for auth and Paddle webhooks for syncing customer data. For a quick overview of what's in the kit before you dive in, see the [Next.js starter kit reference](/sdks/starter-kits/nextjs). Check out the live demo of the starter kit to explore how it works ![Grid of logos used in the starter kit: Paddle, Supabase, Next.js, and Vercel.](/src/assets/images/tmp-build/vercel-logos-20240912.svg) By the end of this tutorial you'll have: - Set up Paddle Billing in sandbox. - Created a notification destination for syncing data. - Created products and prices for a SaaS app. - Deployed a Next.js app to Vercel. - Taken a test payment. - Learned how to transition from sandbox to live. ## Before you begin ### Sign up for Paddle Paddle is a complete merchant-of-record platform for modern software businesses. Our API-first platform takes care of payments, localization, and subscription management for you. Sign up for a sandbox account for this tutorial at [sandbox-login.paddle.com/signup](https://sandbox-login.paddle.com/signup). You can transition to a live account later when you're ready to sell. Live accounts require account verification before you can launch a checkout or sell on the Paddle platform. ### Sign up for Vercel and Supabase - [Vercel](https://vercel.com/) hosts and deploys web apps using serverless infrastructure, designed for [Next.js](https://nextjs.org/). - [Supabase](https://supabase.com/) provides databases, authentication, and other backend features. The starter kit uses Supabase for user management and for syncing customer data with Paddle via webhooks. You'll also need a Git provider to store your code. Vercel's deploy flow walks you through setting up an account with [GitHub](https://github.com/) (recommended), [GitLab](https://gitlab.com/), or [Bitbucket](https://bitbucket.org/). ### Set up your local environment To work on the starter kit locally, you'll need an IDE like [Visual Studio Code](https://code.visualstudio.com/) plus: - [Node.js](https://nodejs.org/en/download/package-manager/current) v20 or later - [npm](https://www.npmjs.com/), [Yarn](https://yarnpkg.com/), or [pnpm](https://pnpm.io/) You don't need a local environment to get a working demo on Vercel — come back to this later when you're ready to start building. ## Overview Create and deploy a Next.js app integrated with Paddle Billing in four steps: 1. [**Start deploy to Vercel**](#start-deploy-to-vercel) Clone the repo, integrate with Supabase, configure Paddle variables, and deploy. 2. [**Set up your catalog**](#set-up-your-product-catalog) Create products and prices in Paddle, then update your app to use them. 3. [**Add your website and test**](#add-your-website-to-paddle-and-test) Add your website to Paddle and take a test payment. 4. [**Build your app, then transition to live**](#build-your-app-then-go-live) Build on top of the starter kit, then switch to a live account. ## Start deploy to Vercel Deploy the starter kit to Vercel to create a project ready to configure: {% external-link url="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-nextjs-starter-kit&env=PADDLE_API_KEY,PADDLE_NOTIFICATION_WEBHOOK_SECRET,NEXT_PUBLIC_PADDLE_ENV,NEXT_PUBLIC_PADDLE_CLIENT_TOKEN&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6&external-id=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-nextjs-starter-kit%2Ftree%2Fmain" icon="vercel" %} Deploy Paddle.js SaaS starter kit to Vercel ### Create Git repo First, create a clone of the starter kit repo. This creates a copy of the code in a repo under your Git provider, so you can build your app on top of the project. Click **Continue with GitHub**, **Continue with GitLab**, or **Continue with Bitbucket** to connect your Git provider to Vercel, then enter a name for your repo. ![Screenshot of the deploy to Vercel workflow, showing the Get started section. It shows three buttons to continue with GitHub, GitLab, and Bitbucket.](/src/assets/images/tmp-build/vercel-github-connect-20240918.png) The repo name becomes the name of your project in Vercel and is used for deploy preview URLs. If the name is taken, Vercel appends characters to your project name when creating the URL. ### Integrate with Supabase Next, click **Add** to walk through integrating with Supabase. ![Screenshot of the deploy to Vercel workflow, showing the add integration section. It shows Supabase with an Add button.](/src/assets/images/tmp-build/vercel-supabase-auth-20240918.png) Give your project any name. We recommend using a password manager to generate and store a secure password. Make sure **Create sample tables with seed data** is checked. This creates tables in Supabase to store customer and subscription data for the app. ![Screenshot of the new project modal in Supabase. It shows fields for project name, password, and region. It shows a checkbox that says 'create sample tables with seed data' that's checked.](/src/assets/images/tmp-build/vercel-supabase-config-20240917.png) ### Configure Paddle variables Supply variables so the app can interact with your Paddle account. ![Screenshot of the deploy to Vercel workflow, showing the configure project section. It shows four required environment variables.](/src/assets/images/tmp-build/vercel-paddle-variables-20240912.png) | Variable | Purpose | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `PADDLE_API_KEY` | API key for backend interaction — syncing customer and subscription data with Supabase. | | `NEXT_PUBLIC_PADDLE_CLIENT_TOKEN` | Client-side token for frontend interaction — localized prices and opening checkouts. | | `PADDLE_NOTIFICATION_WEBHOOK_SECRET` | Secret key for verifying that webhooks came from Paddle and haven't been tampered with. | | `NEXT_PUBLIC_PADDLE_ENV` | `sandbox` for sandbox accounts; `production` for live accounts. | #### Get an API key and client-side token [Client-side tokens](/paddle-js/about/client-side-tokens) and [API keys](/api-reference/about/authentication) are used for authentication. Even if you already have them, create new ones for this app. API keys need [permissions](/api-reference/about/permissions) to perform actions. For this app, you need **Subscription: Write** to read and cancel subscriptions, and **Transaction: Read** to access transactions. 1. Go to **Paddle > Developer tools > Authentication**. 2. Click New API key. 3. Fill in the details, then select the **Subscription: Write** permission. 4. Click Save, then Copy key. 5. Paste your key as `PADDLE_API_KEY` on the Vercel deploy screen. 6. On the authentication screen in Paddle, click the **Client-side tokens** tab. 7. Click New client-side token. 8. Give the token a name and description. 9. Click Save, then Copy key. 10. Paste the token as `NEXT_PUBLIC_PADDLE_CLIENT_TOKEN`. ![Illustration of the create API key drawer in Paddle.](/src/assets/images/tmp-build/api-key-create-2-new-20250407.svg) ![Illustration of the copy API key modal.](/src/assets/images/tmp-build/api-key-create-5-copy-20250407.svg) As you extend your app, your API key may need more permissions. You can [edit the API key](/api-reference/about/authentication) to add them. #### Create a webhook destination Paddle sends webhooks when something important happens in your account — a customer is updated, a new subscription is created, and so on. The starter kit uses webhooks to keep the app in sync with Paddle. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click New destination. 3. Give the destination a name. 4. Keep notification type as **webhook** (the default). 5. Enter `https://.vercel.app/api/webhook` as the URL, replacing `` with your Vercel project name. 6. Check **Select all events**. 7. Click Save destination. 8. From the destinations list, click next to your destination, then Edit destination. 9. Copy the secret key and paste it as `PADDLE_NOTIFICATION_WEBHOOK_SECRET`. ![Illustration of the new destination drawer in Paddle.](/src/assets/images/tmp-build/create-webhook-destination-20240912.svg) ![Illustration of the edit destination drawer with the secret key field highlighted.](/src/assets/images/tmp-build/copy-webhook-secret-key-20240912.svg) See [create a notification destination](/webhooks/about/notification-destinations) for more. #### Set your environment For `NEXT_PUBLIC_PADDLE_ENV`: - `sandbox` for a sandbox account. - `production` for a live account. We recommend sandbox for this tutorial. Live accounts must be approved by Paddle before you can open checkouts. ### Review and deploy Review your settings, then click **Deploy**. Wait for Vercel to build. ![Screenshot of the complete screen for the deploy to Vercel workflow.](/src/assets/images/tmp-build/vercel-deploy-success-20240917.png) Your deploy preview won't work end-to-end yet — the pricing page doesn't display prices and the get-started buttons don't open checkouts. You'll fix these in the next step. ## Set up your product catalog Specify how products in your app map to products in Paddle. ### Model your pricing A product in Paddle has: - A product entity describing the item — name, description, image. - At least one related price entity describing how much and how often it's billed. The template's three-tier pricing page has `Free`, `Basic`, and `Pro`, each with monthly and annual options. Mirror this in Paddle as three products with two prices each. ### Create products and prices You can [create products and prices](/build/products/create-products-prices) using the Paddle dashboard or the API. 1. Go to **Paddle > Catalog > Products**. 2. Click New product. 3. Enter details, then Save. 4. Under **Prices**, click New price. 5. Enter details. Set billing period to **Monthly** for a monthly price. 6. Click Save. 7. Repeat for an **Annually** price. ![Illustration showing the new product drawer in Paddle.](/src/assets/images/tmp-build/dashboard-create-product-20230831.svg) Repeat for each product until you have three products with two prices each. ### Update pricing constants Update the app with your new price IDs. Clone your Git repo locally and open `src/constants/pricing-tier.ts` — or edit directly on your Git platform. `pricing-tier.ts` contains constants used in the pricing page and checkout. Swap each `pri_` ID with one of your new price IDs: ```ts export interface Tier { name: string; id: 'starter' | 'pro' | 'advanced'; icon: string; description: string; features: string[]; featured: boolean; priceId: Record; } export const PricingTier: Tier[] = [ { name: 'Starter', id: 'starter', icon: '/assets/icons/price-tiers/free-icon.svg', description: 'Ideal for individuals who want to get started with simple design tasks.', features: ['1 workspace', 'Limited collaboration', 'Export to PNG and SVG'], featured: false, priceId: { month: 'pri_01hsxyh9txq4rzbrhbyngkhy46', year: 'pri_01hsxyh9txq4rzbrhbyngkhy46' }, }, { name: 'Pro', id: 'pro', icon: '/assets/icons/price-tiers/basic-icon.svg', description: 'Enhanced design tools for scaling teams who need more flexibility.', features: ['Integrations', 'Unlimited workspaces', 'Advanced editing tools', 'Everything in Starter'], featured: true, priceId: { month: 'pri_01hsxycme6m95sejkz7sbz5e9g', year: 'pri_01hsxyeb2bmrg618bzwcwvdd6q' }, }, { name: 'Advanced', id: 'advanced', icon: '/assets/icons/price-tiers/pro-icon.svg', description: 'Powerful tools designed for extensive collaboration and customization.', features: [ 'Single sign on (SSO)', 'Advanced version control', 'Assets library', 'Guest accounts', 'Everything in Pro', ], featured: false, priceId: { month: 'pri_01hsxyff091kyc9rjzx7zm6yqh', year: 'pri_01hsxyfysbzf90tkh2wqbfxwa5' }, }, ]; ``` To get a price ID: 1. Go to **Paddle > Catalog > Products** and click a product. 2. Click next to a price, then Copy price ID. 3. Paste the ID as `priceId.month` or `priceId.year`. Add all your price IDs, then commit and push to `main`. Vercel rebuilds automatically. When the build's done, your pricing page should display prices — but you can't checkout yet. That's the next step. ## Add your website to Paddle and test To keep the Paddle platform safe, you must add your website to Paddle before launching a checkout from it. This protects you as a seller — only you can sell your products. ### Get your website approved For sandbox accounts, website approval is instant. You still need to add your domain. Website approval makes sure you own the domains where you use Paddle Checkout and that your products meet Paddle's acceptable use policy. 1. Go to **Paddle > Checkout > Website approval**. 2. Click Add a new domain, enter your Vercel demo app URL, then Submit for Approval. 3. Wait for approval. For sandbox, you'll see a green "Approved" status immediately. Live accounts may take a few days while the Paddle verification team reviews your site. See website verification on the Paddle help center ### Set your default payment link Your default payment link opens Paddle Checkout for a transaction. It's also used in emails from Paddle that let customers manage their subscription. 1. Go to **Paddle > Checkout > Checkout settings**. 2. Enter your Vercel deploy URL under **Default payment link**. 3. Click Save. ![Checkout settings screen with the default payment URL field highlighted.](/src/assets/images/tmp-build/default-payment-link-20250127.svg) See [set your default payment link](/build/transactions/default-payment-link). ### Check your notification destination endpoint If your project name was taken, Vercel appended characters to your deploy URL. If that URL doesn't match `https://.vercel.app`, update the notification destination URL in Paddle. You can find your deploy URL in the Vercel dashboard. ### Test Open your Vercel demo site. Prices should now render on the pricing page. ![Screenshot of the pricing page from the app. It shows three tiers, which show prices, with a toggle to switch from monthly to annual.](/src/assets/images/tmp-build/vercel-complete-demo-20240912.png) Click **Get started** to launch a checkout. Use [test card details](/concepts/payment-methods/card) for sandbox: An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` Any name Any valid date in the future. `100` After checkout, click **Sign in**. The subscriptions and payments pages pull information from Paddle about the customer's subscriptions and transactions. ### Troubleshoot If prices don't appear, the checkout doesn't load, or you see "Something went wrong," open your browser console for specific error messages from Paddle.js. On macOS Chrome, `⌘` + `⌥` + `J` opens it. Check that: - You pushed all changes to `main` and Vercel deployed successfully. - Products and prices are created correctly. Prices should be recurring so Paddle creates a subscription when checkout completes. - Your `pri_` IDs are correct. Sandbox and live IDs don't cross environments. - Your default payment link matches the domain you're testing on. - Your notification destination URL is `https:///api/webhook`. - `NEXT_PUBLIC_PADDLE_CLIENT_TOKEN` is a valid token (starts with `test_` for sandbox, `live_` for live). - `NEXT_PUBLIC_PADDLE_ENV` is `sandbox` or `production` to match your account. - `PADDLE_API_KEY` is valid. - Supabase tables were created successfully. Use the [Vercel](https://vercel.com/docs) or [Supabase](https://supabase.com/docs) docs for platform-specific issues. ## Build your app, then go live You're done. Use this starter kit as a basis for building a SaaS app on Paddle Billing. When you're ready to take real payments: 1. Sign up for a live account, then follow the [go-live checklist](/build/go-live-checklist). 2. Update Vercel environment variables for your live account. 3. Create new schemas in Supabase for live data. 4. [Set up Paddle Retain](/build/retain) to recover failed payments. ## Next steps That's it. Now you've built a Next.js app with Paddle Billing and deployed it to Vercel, you might like to hook into other features of the Paddle platform. ### Do more with Paddle.js Our starter kit passes prices to Paddle.js to display localized pricing on our pricing page and open a checkout to create subscriptions. Paddle.js includes a bunch of properties and settings you can pass that give you more control over how opened checkouts work. For example, you can prepopulate a discount, set the language that Paddle Checkout uses, or restrict payment options shown to customers. See how to pass customer and order information into Paddle.js to prefill checkout forms. Control checkout behavior by passing additional settings to Paddle.js. Customize the look and feel of your inline checkout to match your brand. ### Build advanced subscription functionality Paddle Billing supports flexible subscription workflows: pause and resume, change billing dates, offer trials, and more. Let customers temporarily suspend or reactivate their subscriptions with your app. Give customers the flexibility to adjust their billing dates to suit their needs. Learn how to offer and manage free trials to attract more signups. ### Integrate with Paddle Retain [Paddle Retain](/concepts/retain) combines subscription expertise with algorithms trained on billions of datapoints to recover failed payments, reduce churn, and proactively upgrade plans. Set up rules and automations to recover failed payments and handle dunning intelligently. Capture feedback at cancel and use flows to reduce churn in your SaaS. Let Retain automatically upgrade customers when optimal for their billing cycle. --- # Build with Lovable Payments, powered by Paddle URL: https://developer.paddle.com/get-started/ai/lovable Use Lovable Payments to add payments to your app, powered by Paddle. Lovable handles the entire process — just prompt in chat. [Lovable](https://lovable.dev) comes with Lovable Payments, a built-in monetization layer powered by Paddle. You ask Lovable in chat to add payments, and let the agent set up your Paddle account, create products and prices, wire webhooks, and add the customer portal. Once enabled, you get a new payments tab in your project to manage your monetization. Sign up for Lovable to add payments to your app in a prompt ## Before you begin You'll need: - **Lovable Pro plan or higher**: built-in payments aren't available on the free tier. - **Lovable Cloud**: Lovable activates this during setup if it isn't on already. - **Authentication** in your app (recommended) to bind purchases to a specific user. Ask Lovable to add it if you don't have it yet. ## Overview Add payments to your app in five steps: 1. Ask Lovable to add payments 2. Create your Paddle account 3. Define your catalog 4. Test in the preview 5. Go live ## Ask Lovable to add payments Open your project and ask Lovable in chat to add payments. For best results, be specific about the product and pricing. ```markdown Add a $29/month Pro plan to my app. ``` ```markdown Set up checkout for my $197 digital course. ``` ```markdown Add three subscription tiers: Starter $9/mo, Pro $29/mo, Enterprise $99/mo. ``` Lovable picks a provider based on what you're selling. For digital products and global SaaS, it recommends **Paddle**. The rest of this guide assumes you've selected Paddle when prompted. ## Create your Paddle account Lovable opens an **Enable payments** dialog and walks you through Paddle account creation in-app. Enter your details here and submit. There's no separate Paddle signup. Your email address must not already be tied to a Paddle account. If yours is, use a plus alias like `you+lovable@example.com`. ## Define your catalog If you haven't already, tell Lovable what you sell. The agent creates the corresponding products and prices in Paddle and adds checkout UI to your app. ```markdown Create three pricing tiers: Starter $9/month, Pro $29/month, Enterprise $99/month. Add them to a pricing page. ``` ```markdown Add a 14-day free trial to the Pro plan. ``` ```markdown Create a 20% discount code LAUNCH that applies to the first three months. ``` You can change tiers, add trials, or restructure pricing by prompting again. Manage products through Lovable rather than Paddle. ## Test in the preview The preview environment runs in test mode by default, which relates to the sandbox environment in Paddle. Use these test cards to complete checkouts: An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` (valid) `4000 0000 0000 0002` (declined) Any name Any valid date in the future. `100` Step through the full subscription lifecycle before going live: - Buy a plan and confirm features unlock for the customer. - Ask Lovable to simulate a renewal. - Cancel a subscription and confirm access continues until the period ends. - Trigger a failed renewal and confirm you handle it gracefully. - Walk a trial through to first charge. - Apply a discount code at checkout. A test mode banner appears in the preview and is hidden on your published site. ## Go live When you've tested your integration fully, switch to live from the Payments tab. Lovable walks you through the entire process: 1. **Run the readiness check.** Lovable verifies you have a privacy policy, terms of service, refund policy, and that your site content complies with Paddle's acceptable use policy. Fix anything that fails and rerun. 2. **Confirm products are published.** You need at least one product with a price on the published site. 3. **Complete verification (KYC/KYB).** Paddle walks you through the verification process. Provide your product info, answers to compliance questions, and personal or business details. 4. **Get your domains reviewed.** Paddle reviews your live domain to confirm it represents a real product with real content and legal pages. 5. **Set up payouts.** Once approved, add bank account details in Paddle's dashboard from the "Set up payouts" prompt. After approval, the live checkout starts taking real cards. Verification usually takes place within a day, but it may take several days depending on your location and product. Paddle may contact you to request additional documentation. ## Manage your payments Once payments are enabled, a **Payments** tab appears in your project toolbar. From there you can: - Toggle between test and live environments. - See revenue analytics, like net revenue, active subscriptions, and charts over 7/30/90 days. - Browse transaction history and adjustments (refunds, credits, chargebacks). - Track go-live progress against the readiness checklist. For anything Lovable doesn't manage, like checkout styling or payment method configuration, open the Paddle dashboard directly from the tab. ## Next steps - [Add payments to your app - Lovable](https://docs.lovable.dev/features/payments) - [Lovable Payments, powered by Paddle FAQ - Paddle.com](https://www.paddle.com/lovable-payments-faq) --- # Get started URL: https://developer.paddle.com/get-started Understand Paddle, find your use case, build a quickstart, and use AI tools to integrate faster. --- # Paddle for SaaS URL: https://developer.paddle.com/get-started/how-paddle-works/saas How Paddle handles billing, subscriptions, customer self-service, and global tax compliance for SaaS businesses. Paddle is a [merchant of record](https://mor.paddle.com/) platform built for modern SaaS businesses. It manages your payments, taxes, and subscriptions in a single integration, so your team can ship features faster. Companies like [CrashPlan](https://www.paddle.com/customers/crashplan-doubled-sign-ups-with-paddle), [Plausibe Analytics](https://www.paddle.com/customers/how-plausible-analytics-used-paddle-to-achieve-200-mrr), [n8n](https://www.paddle.com/customers/how-paddle-helped-n8n-io-grow-mrr), [Relay.app](https://www.paddle.com/customers/relayapp-live-in-weeks-sells-globally-without-barriers), and [TeamGantt](https://www.paddle.com/customers/team-gantt-boosts-conversions-retention-with-paddle) use Paddle to power their business. ## Key features for SaaS businesses Tax, compliance, one-click payment methods, and smart routing across 200+ countries are handled for you. Recurring billing, plan changes, proration, pauses, trials, and dunning come built in, with webhooks for the full lifecycle. Low maintenance, simple to integrate customer portal for self-service subscription workflows. Paddle Retain automatically recovers failed payments, reduces cancellation rates, and increases LTV. ## Pricing models you can build In Paddle, [products and prices](/build/products/create-products-prices) are flexible entities you compose into the structure you need. Products describe what you sell, and related prices describe how you sell a product. This means you can build any pricing structure you need, from flat, good-better-best, per-seat, usage-based, and feature based pricing. - **Recurring plans** with any billing period, optional [trials](/build/trials/cardless-trials), and multiple items on a subscription. - **Good-better-best**, like Starter, Pro, Enterprise, by creating one product per tier. - **Per-seat billing** by [adding seats as a quantity](/build/subscriptions/add-remove-products-prices-addons) on a price item. - **Add-ons** on top of a base plan, billed on the same cycle or a different one. - **Usage-based charges** for metered features, billed via custom items on the next invoice. - **Plan changes** with [proration](/concepts/subscriptions/proration) calculated to the minute, and your choice of whether to bill now, on the next renewal, or skip billing. - **One-time charges** alongside subscriptions for things like activation fees, overage, or single purchases. If you manage your catalog outside of Paddle, you can add non-catalog items to a transaction or subscription, too. ## Transactions and subscriptions [Transactions](/api-reference/transactions) and [subscriptions](/api-reference/subscriptions) are the core billing entities in Paddle: - When a transaction (checkout or invoice) is completed, Paddle creates a related subscription. - When a subscription renews, Paddle creates a new transaction to collect payment for it. - Upgrades, one-time charges, and other changes to a subscription create transactions, too. The entire subscription lifecycle can be understood in terms of these two entities, meaning it's easy to determine the state of a subscription at any given time. ## The subscription lifecycle The subscription billing engine in Paddle handles the complete subscription lifecycle, including signup, renewal, upgrade and downgrade, pause, and cancellation. ```mermaid flowchart LR S[sign up] --> A[trialing] A --> B[active] B <--> C[past_due] B --> D[paused] D --> B B --> E[canceled] C --> E ``` To handle fulfillment, you can listen for webhooks for the subscription lifecycle and update your database accordingly. Paddle includes over 50 webhooks, but a basic billing workflow involves listening to just two: | Event | Purpose | |---|---| | `subscription.created` | A new subscription exists. Save the customer and subscription IDs against your user, and grant access. | | `subscription.updated` | Something about the subscription has changed, like its status, items, scheduled changes, or billing period. Re-read the cached fields you store. | ## Self-service for your customers Customers expect to manage their own subscription without filing a support ticket. The customer portal gives customers a hosted page where they can: - Update payment methods. - View past invoices and download receipts. - Cancel, with a cancellation flows that surface offers before the customer leaves. You can [link to the portal](/build/customers/integrate-customer-portal) directly from your app, or generate per-customer authenticated sessions for a more seamless handoff. For a completely custom experience, you can build your own billing management screens in your app using the API or SDKs. Everything the portal offers is available for developers, too. ## Reduce churn with Paddle Retain Subscription businesses lose more revenue to failed payments and avoidable churn than they realize. [Paddle Retain](/concepts/retain) is fully integrated with Paddle Billing, recovering failed payments and reducing churn automatically. - **Payment recovery and dunning** for failed payments, using ML-tuned schedules and acquirer-specific logic across millions of transactions. - **Cancellation flows** salvage customers at the moment they want to cancel with offers and win-back paths. - **Term optimization** nudges customers from monthly to annual plans at renewal, lifting LTV. Retain works on top of Paddle Billing, using Paddle.js, Paddle Checkout, and the customer portal. ## Ready to scale Paddle helps you unlock new markets and win bigger deals: - **Localized pricing** in [30+ currencies](/concepts/sell/supported-currencies), set automatically from your base price or configured per country. - **Global payment methods** like [Apple Pay](/concepts/payment-methods/apple-pay), [Google Pay](/concepts/payment-methods/google-pay), [PayPal](/concepts/payment-methods/paypal), plus regional methods like [iDEAL](/concepts/payment-methods/ideal), [Bancontact](/concepts/payment-methods/bancontact), [Pix](/concepts/payment-methods/pix), and [UPI](/concepts/payment-methods/upi). No need to add individual processors. - **Tax compliance** in [200+ countries](/concepts/sell/supported-countries-locales), including VAT, GST, sales tax, and digital services taxes. - **Hybrid billing support** means you can move subscriptions to invoices for larger deals in one-click or API call, with no data duplication. ## Next steps Deploy a working SaaS app to Vercel in minutes. Walk through the five steps to get started end to end. Install the Paddle MCP or skills to add Paddle to your app. --- # Build a web checkout for your iOS app with the Web Monetization Kit URL: https://developer.paddle.com/get-started/starter-kits/web-monetization Step-by-step tutorial — deploy the Paddle Web Monetization Kit to Vercel, set up a product catalog, add a checkout button to your iOS app, and handle fulfillment using RevenueCat or webhooks. This tutorial walks through deploying the Paddle Web Monetization Kit to Vercel, setting up a product catalog, adding a checkout button to an iOS app, and handling fulfillment using either RevenueCat or webhooks. By the end, you'll have a complete external purchase flow for your iOS app. For a quick overview of what's in the kit before you dive in, see the [Web Monetization Kit reference](/sdks/starter-kits/web-monetization). ![Grid of logos used in the Web Monetization Kit: Paddle, RevenueCat, Next.js, and Vercel.](/src/assets/images/tmp-build/vercel-ios-logos-20250508.svg) ## What's not covered - **Authentication** — we assume you already identify users via [Firebase](https://firebase.google.com/), [Sign in with Apple](https://developer.apple.com/sign-in-with-apple/), or similar. - **Native in-app purchases** — Paddle Checkout opens in Safari and redirects users back to your app. Like the App Store, it supports [Apple Pay](/concepts/payment-methods/apple-pay) and [other popular payment methods](/concepts/payment-methods). - **Subscription lifecycle management** — pause, resume, cancel, update payment method. Use the prebuilt [customer portal](/concepts/sell/customer-portal). Covered separately. ## Before you begin ### Sign up for Paddle You can sign up for two kinds of account: - [Sandbox](/sdks/sandbox) — for testing and evaluation. - Live — for selling to customers. We recommend a sandbox account for this tutorial. Sign up at [sandbox-login.paddle.com/signup](https://sandbox-login.paddle.com/signup). Live accounts require account verification before you can launch a checkout or sell on the Paddle platform. ### Sign up for Vercel and a Git provider We deploy to [Vercel](https://vercel.com/), a serverless platform designed for [Next.js](https://nextjs.org/). Sign up free if you don't already have an account. You'll also need a Git provider. Vercel's deploy flow walks you through setting up [GitHub](https://github.com/) (recommended), [GitLab](https://gitlab.com/), or [Bitbucket](https://bitbucket.org/). ### Prep your iOS development environment Later, we add a button to your iOS app that opens the deployed checkout. You'll need: - Knowledge of iOS development, access to your project, and Xcode on macOS. - A [URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) configured so Paddle Checkout can redirect users back to your app. You don't need this to deploy the web checkout — come back to the iOS work later if a separate developer handles your iOS app. ## Overview Create and deploy a website checkout for your iOS app in six steps: 1. [**Start deploy to Vercel**](#start-deploy-to-vercel) Clone the repo, configure environment variables, and deploy. 2. [**Set up your product catalog**](#set-up-your-product-catalog) Create products and prices in Paddle, then update the app. 3. [**Add your website**](#add-your-website-to-paddle) Add the deploy URL to Paddle and get it approved. 4. [**Add a checkout button to your app**](#add-a-checkout-button-to-your-app) Open the checkout from your iOS app. 5. [**Handle fulfillment and provisioning**](#handle-fulfillment-and-provisioning) Use RevenueCat or process webhooks to fulfill purchases after a customer completes a checkout. 6. [**Take a test payment**](#test-the-complete-flow) Make a test purchase to make sure your purchase flow works correctly. ## Start deploy to Vercel To create a Vercel project ready for us to set up, click the button to get started: {% external-link url="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-mobile-web-payments-starter&env=APPLE_TEAM_ID,NEXT_PUBLIC_BUNDLE_IDENTIFIER,NEXT_PUBLIC_APP_REDIRECT_URL,NEXT_PUBLIC_PADDLE_CLIENT_TOKEN,NEXT_PUBLIC_PADDLE_ENV" %} Start one-click deploy to Vercel ### Create Git repo First, clone the starter kit repo. This creates a copy in your Git provider account so you can build on top of the project. Click **Continue with GitHub**, **Continue with GitLab**, or **Continue with Bitbucket** to connect your Git provider, then enter a name for your repo. ![Screenshot of the deploy to Vercel workflow, showing the Get started section. It shows three buttons to continue with GitHub, GitLab, and Bitbucket.](/src/assets/images/tmp-build/vercel-mobile-checkout-v2-new-project-20250710.png) The repo name becomes the Vercel project name and is used for deploy preview URLs. If the name is taken, Vercel appends characters to your project name. ### Configure environment variables Five variables need to be set: | Variable | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `APPLE_TEAM_ID` | Your Apple Developer team ID, required for [universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/) so you can link back to your app. | | `NEXT_PUBLIC_BUNDLE_IDENTIFIER` | Your iOS app's bundle ID, for example `com.example.myapp`. | | `NEXT_PUBLIC_APP_REDIRECT_URL` | Custom URL scheme that bounces users back to your app when their purchase completes, for example `myapp://example-redirect`. | | `NEXT_PUBLIC_PADDLE_CLIENT_TOKEN` | Client-side token for securely opening Paddle Checkout. | | `NEXT_PUBLIC_PADDLE_ENV` | `sandbox` for sandbox accounts; `production` for live accounts. | #### Get Apple team ID and bundle ID - **Team ID**: Sign in to your Apple Developer account and [find your team ID](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/). - **Bundle identifier**: Open your app in Xcode and check [the target summary pane](https://developer.apple.com/documentation/xcode/configuring-a-new-target-in-your-project). Paste each value as `APPLE_TEAM_ID` and `NEXT_PUBLIC_BUNDLE_IDENTIFIER` on the Vercel deploy screen. #### Get a client-side token [Client-side tokens](/api-reference/about/authentication) authenticate Paddle.js in your frontend. 1. Go to **Paddle > Developer tools > Authentication**. 2. Click the **Client-side tokens** tab, then New client-side token. 3. Give it a name and description, then Save. 4. Click next to the token, then Copy token. 5. Paste it as `NEXT_PUBLIC_PADDLE_CLIENT_TOKEN`. ![Paddle's Authentication dashboard showing the Client-side tokens tab with a list of tokens.](/src/assets/images/tmp-build/client-side-token-create-20250407.svg) #### Set your environment For `NEXT_PUBLIC_PADDLE_ENV`: - `sandbox` for a sandbox account. - `production` for a live account. We recommend sandbox for this tutorial. Live accounts must be approved by Paddle before you can open checkouts. ### Review and deploy Review your settings, then click **Deploy**. Wait for Vercel to build. ![Screenshot of the complete screen for the deploy to Vercel workflow. It says 'congratulations!' and there's a preview of the app.](/src/assets/images/tmp-build/vercel-ios-deploy-success-20250508.png) The deploy URL works and the marketing site renders. The pricing page and checkout flow won't work until the next step. ## Set up your product catalog Set up products and prices in Paddle to match the in-app items you want to sell. ### Model your pricing A [complete product](/build/products/create-products-prices) in Paddle has: - A product entity describing the item — name, description, image. - At least one related price entity describing how much and how often it's billed. For this example, we create a product called `Acme Guard` with monthly and annual prices. ![Illustration of the product catalog in Paddle. It shows a product called Acme Guard with monthly and annual prices.](/src/assets/images/tmp-build/vercel-mobile-checkout-v2-catalog-20250710.svg) ### Create products and prices You can [create products and prices](/build/products/create-products-prices) using the Paddle dashboard or the API. 1. Go to **Paddle > Catalog > Products**. 2. Click New product. 3. Enter details, then Save. 4. Under **Prices**, click New price. 5. Set the billing period to **Monthly**, then Save. 6. Repeat for an **Annually** price. 7. Click next to each price, then Copy ID. ![Prices list with the action menu open and copy ID selected.](/src/assets/images/tmp-build/copy-price-id-20250127.svg) ### Update prices in your app Clone your Git repo locally, then open `src/components/pricing/plan-select.tsx` in your IDE — or edit directly on your Git platform. `plan-select.tsx` contains a `plans` array used in the pricing page. Swap each `pri_` ID with one of yours. The `tag` is optional, useful for showing a discount label like `Save 17%`: ```ts export const plans = [ { priceId: "pri_01jx2rx1t30hxejpb5v0vav4nv", // Monthly price }, { priceId: "pri_01h1vjg3sqjj1y9tvazkdqe5vt", // Annual price tag: "Save 17%", } ]; ``` Commit and push to `main`. Vercel rebuilds automatically. ![Screenshot of the project page in Vercel showing a build in progress.](/src/assets/images/tmp-build/vercel-redeploy-20240917.png) When the build completes, your pricing page displays the new prices. Checkout still doesn't work — that's the next step. ## Add your website to Paddle Before you can launch a checkout, add your Vercel deploy URL to Paddle. ### Get your website approved Sandbox accounts get instant approval — you still need to add the domain. 1. Go to **Paddle > Checkout > Website approval**. 2. Click **Add a new domain**, enter your Vercel deploy URL, then **Submit for Approval**. 3. Wait for approval (instant on sandbox; a few days on live). See website verification on the Paddle help center ### Set your default payment link Your [default payment link](/build/transactions/default-payment-link) is used to open Paddle Checkout for transactions and in emails that let customers manage purchases. 1. Go to **Paddle > Checkout > Checkout settings**. 2. Enter your Vercel deploy URL under **Default payment link**. 3. Click Save. ![Checkout settings screen with the default payment URL field highlighted.](/src/assets/images/tmp-build/default-payment-link-20250127.svg) ## Add a checkout button to your app Add a button to your iOS app that: 1. Checks whether in-app purchases are allowed on the device. 2. Constructs a URL using your Vercel deploy URL with a `price_id` query parameter. 3. Opens the URL in Safari. ```swift import SwiftUI import StoreKit // required for checking device payment capabilities using SKPaymentQueue struct PurchaseView: View { let checkoutBaseURL = "https://paddle-mobile-web-payments-starter.vercel.app" // replace with your checkout URL let priceId = "pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with a price ID or set dynamically var body: some View { VStack { if SKPaymentQueue.canMakePayments() { Button("Buy now") { openCheckout() } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } else { Text("Purchases not available on this device.") .foregroundColor(.secondary) } } .padding() } func openCheckout() { let checkoutURL = "\(checkoutBaseURL)?price_id=\(priceId)" if let url = URL(string: checkoutURL) { UIApplication.shared.open(url) } } } ``` The `price_id` is hardcoded above. In a real app, set it dynamically based on the plan the user selects. ### Prefill information Pass [URL query parameters](/paddle-js/about/hosted-checkout) to prefill checkout fields and provide a smoother user experience. For example, pass the customer email and a unique identifier for use with RevenueCat: ```swift import SwiftUI import StoreKit struct PurchaseView: View { let checkoutBaseURL = "https://paddle-mobile-web-payments-starter.vercel.app" let priceId = "pri_01h1vjg3sqjj1y9tvazkdqe5vt" // From your auth platform / RevenueCat let appUserId = "85886aac-eef6-41df-8133-743cbb1daa4b" let userEmail = "sam@example.com" let countryCode = "US" let postalCode = "10021" var body: some View { VStack { if SKPaymentQueue.canMakePayments() { Button("Buy now") { openCheckout() } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } else { Text("Purchases not available") .foregroundColor(.secondary) } } .padding() } func openCheckout() { var urlComponents = URLComponents(string: checkoutBaseURL)! urlComponents.queryItems = [ URLQueryItem(name: "price_id", value: priceId), URLQueryItem(name: "app_user_id", value: appUserId), URLQueryItem(name: "user_email", value: userEmail), URLQueryItem(name: "country_code", value: countryCode), URLQueryItem(name: "postal_code", value: postalCode) ] if let url = urlComponents.url { UIApplication.shared.open(url) } } } ``` For the full list of supported parameters, see [hosted checkout URL parameters](/paddle-js/about/hosted-checkout). ## Handle fulfillment and provisioning After a customer completes a purchase, Paddle redirects them back to your app. Now you need to unlock the features they bought. Two options: If you use the [RevenueCat x Paddle integration](https://www.paddle.com/revenuecat-integration-beta) for entitlements, you're set: 1. Paddle sends data to RevenueCat about the completed checkout. 2. RevenueCat grants the user an entitlement based on [your product configuration](https://www.revenuecat.com/docs/offerings/products-overview). 3. Use the RevenueCat SDK to [check entitlement status](https://www.revenuecat.com/docs/customers/customer-info) in your iOS app. If you'd rather build your own fulfillment, use [webhooks](/webhooks). The example below grants users access when they purchase the `Acme Guard` product. #### Build a webhook handler This Node.js example uses Express. Adapt to your framework — see the [webhook signature verification guide](/webhooks/about/signature-verification) for examples in other languages. ```typescript app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => { try { // Verify the webhook signature first — see /webhooks/verify-signatures const payload = JSON.parse(req.body.toString()); const { data, event_type } = payload; const occurredAt = payload.occurred_at; switch (event_type) { case 'transaction.created': { const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } }); if (userForTransaction) { await Transaction.create({ transactionId: data.id, userId: userForTransaction.id, subscriptionId: data.subscription_id, status: data.status, amount: data.amount, currencyCode: data.currency_code, occurredAt, }); } break; } case 'transaction.completed': { const completedTransaction = await Transaction.findOne({ where: { transactionId: data.id } }); if (completedTransaction) { await completedTransaction.update({ status: data.status, subscriptionId: data.subscription_id, invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, billedAt: data.billed_at, updatedAt: data.updated_at, }); } break; } } res.json({ received: true }); } catch (error) { console.error('Error processing webhook:', error); return res.status(500).json({ error: error.message }); } }); ``` #### Unlock user access When you receive `transaction.completed`, update the user's access permissions. The example below maps Paddle product IDs to permission keys; your iOS app reads the permission to unlock features. ```typescript case 'transaction.completed': { const completedTransaction = await Transaction.findOne({ where: { transactionId: data.id } }); if (completedTransaction) { await completedTransaction.update({ status: data.status, subscriptionId: data.subscription_id, invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, billedAt: data.billed_at, updatedAt: data.updated_at, }); const user = await User.findOne({ where: { id: completedTransaction.userId } }); if (!user) break; const purchasedItems = data.items || []; const accessPermissions = user.accessPermissions ? JSON.parse(user.accessPermissions) : {}; // Map product IDs to permission keys. const productToPermission = { 'pro_01j4z97mq9pa4fkyy0wqenepkz': 'acmeGuardAccess', 'pro_01j4vjes1y163xfj1rh1tkfb65': 'acmeHotspotAccess', }; purchasedItems.forEach(({ price }) => { const permissionKey = productToPermission[price.product_id]; if (permissionKey) accessPermissions[permissionKey] = true; }); await user.update({ accessPermissions: JSON.stringify(accessPermissions), }); } break; } ``` #### Create a notification destination Tell Paddle where to deliver webhooks: 1. Go to **Paddle > Developer tools > Notifications**. 2. Click **New destination**. 3. Give it a name. 4. Keep notification type as **webhook** (the default). 5. Enter your webhook handler URL, then check **transaction.completed**. 6. Click **Save destination**. ![Illustration of the new destination drawer in Paddle. It shows fields for description, type, URL, and version. Under those fields, there's a section called events with a checkbox that says 'select all events'](/src/assets/images/tmp-build/create-webhook-destination-20240912.svg) See [create a notification destination](/webhooks/about/notification-destinations) for more. ## Test the complete flow ![Mobile app screen showing a 'Buy now for $60.00' button.](/src/assets/images/tmp-build/vercel-mobile-checkout-v2-test-step-1-20250710.svg) ![Paddle checkout screen with product details and Apple Pay option.](/src/assets/images/tmp-build/vercel-mobile-checkout-v2-test-step-2-20250710.svg) ![Mobile app screen showing 'You unlocked access for 1 year. Enjoy!' with a 'Start now' button.](/src/assets/images/tmp-build/vercel-mobile-checkout-v2-test-step-3-20250710.svg) Run through the purchase flow end-to-end. Use [test card details](/concepts/payment-methods/card) for sandbox: | Field | Value | | -------------------------- | ------------------------------------- | | **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` | Before going live, [verify your domain for Apple Pay](/concepts/payment-methods/apple-pay) so you can launch the Apple Pay modal directly from your checkout. ## Next steps That's it. Now you've built a website checkout for your iOS app, you might like to hook into other features of the Paddle platform. ### Customize the checkout Our tutorial set up an inline checkout where customers purchase your iOS products. When a customer interacts with the checkout, Paddle.js sends events that are used to show and update the order summary that customers see. While the checkout flow is already set up to function with all Paddle prices and currencies out-of-the-box, you might like to customize its appearance and change its behavior. ### Customize the inline checkout form Paddle.js initializes and opens the checkout in `src/hooks/use-paddle.tsx`. Edit the `checkout.settings` parameter on either method to customize appearance or remove fields. See [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) for the full list of settings. ### Customize the order summary `product-details.tsx` and `order-summary.tsx` in `src/components/checkout` hold the order summary above the inline checkout. Use the `checkoutData` object to access [Paddle.js event fields](/paddle-js/events/checkout-loaded). ### Pass a discount Extend your pricing page and checkout flow by passing a discount to reduce the amount a customer pays. - For your pricing page, add `discountId` in your request to `Paddle.PricePreview()`. The response includes a discount array that has information about the discount applied. Calculated totals in `details.lineItems` include discounts, where applicable. - For the checkout flow, add `discount_id` as a query parameter in the URL you use to open Paddle Checkout within your iOS app. The product summary shows the discount automatically, using `totals.discount` from the checkout event. ### 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. Accept global payment methods out of the box, including credit/debit cards, Apple Pay, and more. Get revenue metrics, customer analytics, and detailed sales reports in your dashboard. Allow customers to manage invoices, subscriptions, and account details without your intervention. --- # Build with Cursor URL: https://developer.paddle.com/get-started/ai/cursor Set up Cursor for the best possible Paddle workflow by connecting the MCP servers, installing agent skills, and adding a Paddle rules file. [Cursor](https://cursor.com) is a popular AI-powered code editor (IDE), built as a fork of VS Code. This guide runs through setting up Cursor for the best possible experience when building with Paddle. ## Before you begin You'll need: - A Paddle [sandbox account](/sdks/sandbox) - A [sandbox API key](/api-reference/about/authentication) with any permissions you plan to use. - Cursor installed. - A project, with a Paddle SDK installed. ## Install the Paddle MCP servers Paddle includes two kinds of MCP server. The [Paddle MCP servers](/sdks/ai/paddle-mcp) can create and update entities in your sandbox and live Paddle accounts, and [the docs MCP server](/sdks/ai/docs-mcp) can search the latest Paddle docs, API spec, and SDK references. ### Paddle docs MCP server Install the Paddle docs MCP in Cursor ### Paddle MCP servers Install the Paddle MCP (sandbox) in Cursor Install the Paddle MCP (live) in Cursor Before clicking Install, replace `YOUR_API_KEY` with your sandbox or live keys. For manual setup, see the [Paddle MCP server](/sdks/ai/paddle-mcp) and [docs MCP server](/sdks/ai/docs-mcp) reference pages. Start with a sandbox API key while you're evaluating. Scope your live API key narrowly, since the Paddle MCP server doesn't gate destructive operations on its own. See [Permissions and destructive actions](/sdks/ai/paddle-mcp#permissions-and-destructive-actions) for details. ## Install Paddle agent skills [Agent skills](/sdks/ai/agent-skills) give Cursor's agent drop-in instructions for specific Paddle workflows. Install all Paddle skills with the skills CLI: ```bash pnpm dlx skills add @PaddleHQ/paddle-agent-skills ``` ```bash yarn dlx skills add @PaddleHQ/paddle-agent-skills ``` ```bash npx skills add @PaddleHQ/paddle-agent-skills ``` This drops them into `.agents/skills/` in your project. From there, Cursor's agent picks the relevant skill automatically as you work. For example, it uses `paddle-webhooks` when you ask it to set up webhook handling, `paddle-checkout-web` when adding checkout to a page, and so on. See [agent skills](/sdks/ai/agent-skills) for the full list, manual install commands, and the discovery index URL. ## Add a Paddle rules file Cursor reads project rules from `.cursor/rules/*.mdc` files. A focused rules file tells the agent what conventions to follow when it writes Paddle code. For example, which SDK to use, how to handle the sandbox environment, and where to source current syntax. Create a `.cursor/rules/paddle.mdc` file: ```markdown --- description: Conventions for Paddle integration code globs: ["**/*.ts", "**/*.tsx", "**/*.py", "**/*.go", "**/*.php"] alwaysApply: true --- # Paddle integration rules When writing or modifying code that integrates with Paddle: - Always check current Paddle documentation via the paddle-docs MCP server before suggesting code. Do not rely on training data — the API and SDK evolve frequently. - Prefer the official Paddle SDK for the language in use (paddle-node-sdk, paddle-python-sdk, paddle-go-sdk, paddle-php-sdk) over raw HTTP calls. - All development uses the sandbox environment. Sandbox API keys contain `_sdbx`; sandbox client-side tokens are prefixed with `test_`. - Always verify webhook signatures before acting on the payload. In Node, use `paddle.webhooks.unmarshal()`. In Python, use `Verifier().verify()`. In Go, use `paddle.NewWebhookVerifier()`. In PHP, use `(new Verifier())->verify()`. - For destructive account changes (updating prices, archiving products), ask for explicit confirmation before calling the Paddle MCP server. - Use environment variables for API keys and webhook secrets. Never inline credentials into code. ``` Adjust the `globs` to match the languages in your project. Cursor loads this rule file whenever it edits matching files. ## Prompt your first integration With MCP servers connected, the Paddle skills installed, and the rules file in place, ask Cursor's agent to add a Paddle integration to your project. ```markdown Add a Paddle Checkout integration to this Next.js app. Use the paddle-docs MCP server to look up the current Paddle.js syntax. Then use the paddle MCP server to create three products in my sandbox account — Starter ($10/mo), Pro ($30/mo), Enterprise ($300/mo) — and generate a /pricing page that opens checkout for each tier. Wire up a /api/webhooks route that verifies the signature and logs transaction.completed events. ``` Cursor uses the Docs MCP for current syntax, the Paddle MCP to create the products in sandbox, and writes code that follows your rules file conventions. ## Best practices - **Mention Paddle and the relevant MCP server explicitly.** "Use the paddle-docs MCP server to look up X" beats hoping the agent calls it on its own. - **Be specific in prompts.** Concrete pricing, event names, and entity IDs work better than vague descriptions. - **Review changes before applying.** AI can make mistakes. Billing is an important part of your business, so double-check before applying changes. - **Never commit API keys.** Never commit API keys. Add `.cursor/mcp.json` to `.gitignore` if it contains secrets. - **Iterate on the rules file.** When the agent makes a recurring mistake, encode the fix as a rule. Evolve the file to match your project conventions. --- # Get started with Paddle in Node.js URL: https://developer.paddle.com/get-started/quickstart/node Install the Paddle Node.js SDK, initialize a client, make your first request, and verify webhook signatures. This quickstart walks through installing the Paddle Node.js SDK, initializing a client, making your first read-only API call, and verifying webhook signatures. By the end, you'll have a working sandbox client and webhook handler. This quickstart is a good starting point for building a backend for your app. For a complete full-stack app that you can build on top of, use the [Next.js SaaS starter kit](/get-started/starter-kits/nextjs-saas). ## Before you begin You'll need: - A [Paddle sandbox account](/sdks/sandbox). - A [sandbox API key](/api-reference/about/authentication) with permission to read and write products. - Node.js 20 or later. ## Install the SDK Install `@paddle/paddle-node-sdk` using your package manager. ```sh npm install @paddle/paddle-node-sdk ``` ```sh pnpm add @paddle/paddle-node-sdk ``` ```sh yarn add @paddle/paddle-node-sdk ``` ```sh bun add @paddle/paddle-node-sdk ``` View the Node.js SDK on GitHub ## Initialize the client Import the SDK and create a `Paddle` client. Pass your API key directly, and set the environment to `sandbox` while you're building. ```typescript import { Environment, LogLevel, Paddle } from '@paddle/paddle-node-sdk' const paddle = new Paddle(process.env.PADDLE_API_KEY!, { environment: Environment.sandbox, logLevel: LogLevel.verbose, }) ``` For production, drop the `environment` option (or set it to `Environment.production`) and lower `logLevel` to `LogLevel.error`: ```typescript const paddle = new Paddle(process.env.PADDLE_API_KEY!) ``` Sandbox API keys contain `_sdbx`. Sandbox and live keys are separate — using one against the other API returns a `forbidden` error. ## Make your first request List products to confirm the client is wired up. The SDK returns a `ProductCollection` that paginates lazily. Call `.next()` to fetch the first page. ```typescript import { Paddle, Product, ProductCollection } from '@paddle/paddle-node-sdk' const paddle = new Paddle(process.env.PADDLE_API_KEY!) async function listProducts() { const productCollection: ProductCollection = paddle.products.list() const firstPage: Product[] = await productCollection.next() console.log('First page of products:', firstPage) } listProducts() ``` If your sandbox account is new, the array is empty. Create a product in the dashboard or via `paddle.products.create()` to see results. ## Verify webhooks You can use [webhooks](/webhooks) to keep your app in sync with Paddle. For example, you can [provision access](/build/subscriptions/provision-access-webhooks) when a subscription is created, or revoke access when a subscription is cancelled. The SDK exposes a `paddle.webhooks.unmarshal()` helper that verifies the signature and parses the event in one call. Always verify the signature before acting on the payload. ```typescript import { Paddle, EventName } from '@paddle/paddle-node-sdk' import express, { Request, Response } from 'express' const paddle = new Paddle(process.env.PADDLE_API_KEY!) const app = express() app.post( '/webhooks', express.raw({ type: 'application/json' }), async (req: Request, res: Response) => { const signature = (req.headers['paddle-signature'] as string) || '' const rawRequestBody = req.body.toString() const secretKey = process.env.PADDLE_WEBHOOK_SECRET! try { const eventData = await paddle.webhooks.unmarshal( rawRequestBody, secretKey, signature, ) switch (eventData.eventType) { case EventName.TransactionCompleted: // Provision access, send a receipt, etc. break case EventName.SubscriptionUpdated: // Sync the subscription to your database. break } res.status(200).send('ok') } catch (err) { res.status(400).send('invalid signature') } }, ) app.listen(3000) ``` Use `express.raw()` middleware on the webhook route. `unmarshal` needs the raw request body string to verify the signature. For the full webhook setup flow, see [Verify webhook signatures](/webhooks/about/signature-verification). ## Next steps - Fork the [Next.js SaaS starter kit](/get-started/starter-kits/nextjs-saas) and deploy it to Vercel. - Use the [webhook simulator](/webhooks/simulator) to test webhook events. - Browse the [API reference](/api-reference) to see every endpoint Paddle exposes. - View the [Node.js SDK reference](/sdks/libraries/node) for the full SDK reference. --- # Paddle for mobile URL: https://developer.paddle.com/get-started/how-paddle-works/mobile How Paddle helps mobile app developers sell outside the App Store, with tax, chargebacks, and compliance out-of-the-box. With [developments in legislation around the App Store](https://www.paddle.com/blog/apple-vs-epic-app-store-changes), you can now link users in the to an external checkout for purchases in iOS apps. But, selling outside the App Store comes with complexity: payments, currency conversion, refunds, chargebacks, sales tax, and compliance become your responsibility. Paddle is a merchant of record platform for mobile app developers. As a merchant of record, you can integrate with Paddle and we take care of payments, tax, fraud, and compliance for you. Companies like [HubX](https://www.paddle.com/customers/hubx-sells-mobile-apps-on-the-web-with-paddle), [Runna](https://www.paddle.com/customers/how-mobile-app-runna-monetized-on-the-web), [PhotoMyne](https://www.paddle.com/customers/how-paddle-enhanced-photomynes-web2app), and [Letterboxd](https://www.paddle.com/customers/letterboxd) use Paddle to power their business. ## Key features for mobile app developers Go directly to your users and save on App Store fees, while boosting customer lifetime value. Reach price points and markets the app stores can't. Set your own prices, offer discounts, and offer custom plans. Open a URL, then listen for a deeplink return. Paddle handles the entire checkout — no billing stack to maintain. Handle fulfillment and entitlements with RevenueCat, or build your own workflow with webhooks. ## The link-out architecture When you integrate with Paddle, you don't need to plug in an entirely new billing stack or build a custom checkout page. Instead, you link out to a checkout and bounce users back to your app when they complete their purchase. A typical flow looks like this: ```mermaid sequenceDiagram participant App as Your app participant Browser as Checkout participant Paddle as Paddle participant Backend as Backend/RevenueCat App->>Browser: 1. Open checkout URL Browser->>Paddle: 2. Customer pays Paddle-->>Browser: Checkout completed Browser->>App: 3. Redirect back to your app App->>Backend: 4. Verify purchase Backend-->>App: Entitlement granted, feature unlocked ``` The work splits cleanly: - Paddle handles the entire checkout process, including payment processing, tax, fraud, and compliance. - RevenueCat or your backend handles entitlement, using webhooks or your own database. - Your app handles opening a URL and listening for the return deeplink. ## Integration options Depending on your needs, you can choose to host the entire checkout with Paddle, or build your own checkout page with Paddle.js. Paddle hosts the entire checkout. You configure a checkout link in the dashboard and open it from your app. Lowest setup. A Next.js app you deploy to Vercel — includes pricing, marketing, and legal pages. Ready to brand up for building a Web2App sales motion. Build your own checkout page with Paddle.js, your own backend creating transactions, and your own entitlements workflow. You can always start with a hosted checkout, then build your own checkout page using the starter kit or Paddle.js if you need more control. ## Identity across the boundary While mobile users are signed into your app, the browser they open for checkout is anonymous. You can pass identity information through [URL parameters](/paddle-js/about/hosted-checkout) when you open the checkout, so you can match the purchase back to the right user on your side. If you're using RevenueCat, you can pass the `app_user_id` (or a custom RevenueCat ID) through the checkout URL, and RevenueCat receives the entitlement event from Paddle. Your app uses the standard RevenueCat SDK to check entitlements on resume. When the customer returns via deeplink, include a `transactionId` query parameter on the redirect URL so your app can match the return to the original checkout. The Paddle.js [`checkout.completed`](/paddle-js/events/checkout-completed) event exposes this transaction ID. ## Grant entitlements ### Using RevenueCat If your team already manages entitlements through RevenueCat, the [RevenueCat x Paddle integration](https://www.paddle.com/revenuecat-integration-beta) syncs Paddle purchases automatically. ### Using Paddle webhooks and your backend If you're not using RevenueCat, or you want complete control over the entitlement workflow, you can use webhooks and your own backend to handle entitlement. Your backend listens for [`transaction.completed`](/webhooks/transactions/transaction-completed), updates your database, and exposes an entitlement endpoint your app calls on resume: 1. The app opens the checkout URL with `app_user_id` set to your user ID. 2. The customer pays. `transaction.completed` occurs in Paddle. 3. Your webhook handler matches the customer to the user and updates entitlements in your database. 4. The app resumes via deeplink and calls your entitlement endpoint. 5. Your backend returns the current entitlement state. The app unlocks features. Paddle has [webhooks](/webhooks) for the full subscription lifecycle, so you can use webhooks to handle subscription creation, updates, and cancellations, too. ## Next steps Learn about how hosted checkout works in more detail and how to set it up. Explore our starter kit for Vercel that includes everything you need to monetize on the web. Install the Paddle MCP or skills to add Paddle to your mobile app. --- # What is Paddle? URL: https://developer.paddle.com/get-started/how-paddle-works Paddle is the developer-first merchant of record, built for modern SaaS, mobile app, AI, and digital product companies. It helps you increase your revenue, decrease your costs, and reduce your risk. Paddle is an all-in-one payments and billing merchant of record for modern SaaS, mobile app, AI, and digital product companies. You can use Paddle to add checkout, subscriptions, global tax compliance, metrics, fraud protection, and retention tooling to your app through one unified API. Thousands of software businesses run on Paddle, including [n8n](https://www.paddle.com/customers/how-paddle-helped-n8n-io-grow-mrr), [Runna](https://www.paddle.com/customers/how-mobile-app-runna-monetized-on-the-web), [Nexus Mods](https://www.paddle.com/customers/helping-nexus-mods-unlock-the-gaming-community), [Relay.app](https://www.paddle.com/customers/relayapp-live-in-weeks-sells-globally-without-barriers), and [CrashPlan](https://www.paddle.com/customers/crashplan-doubled-sign-ups-with-paddle). [Lovable Payments](/get-started/ai/lovable) is powered by Paddle, empowering the new generation of software builders to monetize in minutes. Across our customer base, moving to Paddle typically delivers: {% card title="Higher payment acceptance" stat="~25%" statColor="blue" %} Global payment methods, smart routing, and fraud protection come as standard. {% card title="Improved net revenue retention" stat="~20%" statColor="green" %} Compliant workflows and world-class payment recovery help you retain more revenue. {% card title="Lower cost of revenue delivery" stat="~35%" statColor="purple" %} One single integration and full tax and regulatory compliance reduces your costs. ## What you get with one integration Payments is never just payments. Building a billing stack is a complex engineering effort that requires a lot of time and attention. It typically involves stitching together a bunch of systems and services, each with their own APIs and conventions. With Paddle, it's one integration: - **Global payments in minutes.** Accept payments in 30+ currencies across 200 markets, with one-click access to digital wallets and payment methods, routed across multiple acquirers. - **Modern subscription billing engine.** Offer any billing model, and handle all of the subscription lifecycle with proration, trials, pauses, upgrades, one-off charges, invoicing, and credit notes. - **Worldwide sales tax - handled.** Paddle calculates, collects, and remits taxes for you across the world. No need to register for VAT, GST, or sales tax in the countries where you operate. - **Enter new markets without the complexity.** Grow your sales volume by pricing for countries, offering localized payment methods, and automatically presenting prices in a way buyers expect. - **Compliance in a changing regulatory landscape.** Never think about payment, subscription, or invoicing legislation again. Paddle is fully compliant with regulations across the globe. - **World-class payment recovery.** Let Paddle Retain automatically recover failed payments for you, with a recovery rate of over 50%. - **Fraud, chargebacks, and buyer support.** Paddle screens transactions, fights chargebacks, and handles buyer support for you, so you can focus on growing your business. ## The merchant of record model A merchant of record (MoR) is a legal entity responsible for selling goods or services to an end customer. Your MoR manages payments and takes on the associated liabilities, such as collecting sales tax, ensuring PCI compliance, and honoring refunds and chargebacks. From your perspective: - You don't register for VAT, GST, or sales tax in the countries where you operate. - You don't carry chargeback or PCI scope. - You don't operate a buyer support function for billing issues. - You don't need local entities to accept local payment methods. From a customer's perspective: - Customers go through a modern checkout experience, with Apple Pay, Google Pay, PayPal, cards, and local payment methods. - Checkout appears branded or fully embedded as part of your app or website. - Paddle handles the transaction, including collecting sales tax and handling refunds and chargebacks. - Your name appears on the customer's statement alongside Paddle's. ## Built for developers and AI builders Paddle is built for developers and AI builders. We ship the developer surfaces you'd expect from a modern platform: Resource-oriented, versioned, with idempotency keys and structured errors. A rich event stream with a built-in simulator for local development. Official libraries for Node.js, PHP, Python, and Go for your backend. A modern JavaScript library for building checkout experiences in your frontend. MCP servers for agents, docs that plug in your IDE, and skills to integrate fast. Complete demo apps so you can build on a solid foundation, or rework for your app. Paddle is the merchant of record powering Lovable Payments, so you can take an app from prompt to payment in minutes. ## Multiple paths to integration You don't have to build a custom checkout to start selling. Most teams pick one of these and mix as needed: Fastest path for mobile apps. Configure in the dashboard, add to your app. Open a modern checkout in a few lines of code. Embed a checkout directly in your app or website, with full control over layout. Everything you need to build an app from signup to subscription management. Drop in a self-service portal for your customers to manage their subscriptions. Build fully integrated subscription management. ## Next steps - Learn more about how Paddle works for [SaaS](/get-started/how-paddle-works/saas), [mobile apps](/get-started/how-paddle-works/mobile), [AI companies](/get-started/how-paddle-works/ai-companies), and [one-time products](/get-started/how-paddle-works/digital-products). - Build your first checkout with our [quickstart](/get-started/quickstart). - [Grab a starter kit](/get-started/starter-kits) and build your first app. - Build with [Lovable Payments](/get-started/ai/lovable), powered by Paddle. --- # Build with Claude Code URL: https://developer.paddle.com/get-started/ai/claude-code Set up Claude Code for the best possible Paddle workflow by installing the Paddle plugin, connecting the Paddle MCP server, and adding a CLAUDE.md file. [Claude Code](https://code.claude.com/docs/en/overview) is an AI-powered coding assistant that helps you build features, fix bugs, and automate development tasks. It works as a CLI, IDE extension, or as part of the Claude Desktop app. This guide runs through setting up Claude Code for the best possible experience when building with Paddle. ## Before you begin You'll need: - A Paddle [sandbox account](/sdks/sandbox) - A [sandbox API key](/api-reference/about/authentication) with any permissions you plan to use. - Claude Code installed. - A project, with a Paddle SDK installed. ## Install the Paddle plugin The Paddle plugin bundles [Paddle agent skills](/sdks/ai/agent-skills) with the [docs MCP server](/sdks/ai/docs-mcp) and [Paddle MCP servers](/sdks/ai/paddle-mcp/). Open Claude Code, then run: ```bash /plugin marketplace add PaddleHQ/paddle-agent-skills /plugin install paddle@paddle-agent-skills ``` To update later, run `/plugin marketplace update paddle-agent-skills`. ## Set your Paddle API keys The plugin wires up two Paddle MCP servers so the agent can work in both sandbox and live environments. This is useful for porting data from one to the other. | MCP server | URL | API key env var | | ---------------- | ------------------------------------ | ------------------------ | | `paddle-sandbox` | `https://sandbox-mcp.paddle.com/mcp` | `PADDLE_SANDBOX_API_KEY` | | `paddle-live` | `https://mcp.paddle.com/mcp` | `PADDLE_LIVE_API_KEY` | They read API keys from environment variables. Export them in your shell profile (`~/.zshrc`, `~/.bashrc`, or equivalent): ```sh export PADDLE_SANDBOX_API_KEY= # Your sandbox API key export PADDLE_LIVE_API_KEY= # Your live API key ``` [Create API keys](/api-reference/about/authentication) at **Paddle > Developer tools > Authentication**. Restart Claude Code and your terminal after setting the variables so the MCP servers pick them up. The skills default to sandbox unless you've explicitly opted into live, so `PADDLE_SANDBOX_API_KEY` is the one to start with during development. Start with a sandbox API key while you're evaluating. Scope your live API key narrowly, since the Paddle MCP server doesn't gate destructive operations on its own. See [Permissions and destructive actions](/sdks/ai/paddle-mcp#permissions-and-destructive-actions) for details. ## Add Paddle conventions to your CLAUDE.md Claude Code reads project conventions from `CLAUDE.md` at the project root. A focused Paddle section tells it which SDK to use, how to handle environments, and how to verify webhooks. Create a `CLAUDE.md` file at the project root, or add it to your existing `CLAUDE.md` file. ```markdown ## Paddle integration When writing or modifying code that integrates with Paddle: - Always check current Paddle documentation via the `paddle-docs` MCP server before suggesting code. The Paddle API and SDKs evolve frequently — do not rely on training data alone. - Use the official Paddle SDK for the language in use: - Node.js → `@paddle/paddle-node-sdk` - Python → `paddle-python-sdk` (imports as `paddle_billing`) - Go → `github.com/PaddleHQ/paddle-go-sdk/v5` - PHP → `paddlehq/paddle-php-sdk` - All development uses the sandbox environment. Sandbox API keys contain `_sdbx`; sandbox client-side tokens are prefixed with `test_`. - Always verify webhook signatures before acting on the payload: - Node: `paddle.webhooks.unmarshal()` - Python: `Verifier().verify(request, secret)` - Go: `paddle.NewWebhookVerifier()` with `Middleware` - PHP: `(new Verifier())->verify($request, $secret)` - For destructive account changes (updating prices, archiving products, cancelling subscriptions), ask for explicit confirmation before calling the `paddle-sandbox` or `paddle-live` MCP server. - Use `paddle-sandbox` by default. Only call `paddle-live` when the prompt explicitly mentions live, production, or real customer data. - API keys and webhook secrets live in environment variables — never inline credentials into code. ``` Adjust the SDK list to match the language(s) your project uses. Claude Code will read this every time you start a session. ## Prompt your first integration With the plugin installed, the Paddle MCP server connected, and your `CLAUDE.md` file in place, ask Claude Code to scaffold a Paddle integration. ```markdown Add a Paddle Checkout integration to this Next.js app. Use the paddle-docs MCP server to look up the current Paddle.js syntax. Then use the paddle-sandbox MCP server to create three products in my sandbox account — Starter ($10/mo), Pro ($30/mo), Enterprise ($300/mo) — and generate a /pricing page that opens checkout for each tier. Wire up a /api/webhooks route that verifies the signature and logs transaction.completed events. ``` Claude Code plans first, then uses the docs MCP for current syntax, the Paddle MCP to create the products in sandbox, and writes code that matches your `CLAUDE.md` conventions. ## Best practices - **Mention Paddle and the relevant MCP server explicitly.** "Use the paddle-docs MCP server to look up X" beats hoping the agent calls it on its own. - **Use Plan mode first.** For complex integrations, enter plan mode to draft a plan before writing code. Approve, then run. - **Be specific in prompts.** Concrete pricing, event names, and entity IDs work better than vague descriptions. - **Review changes before applying.** Claude Code shows pending tool calls before executing. Review every tool call, especially anything destructive. - **Never commit API keys.** Keep API keys in environment variables. - **Create your own skills.** When you find yourself repeating the same instructions over and over, create a skill to help steer Claude Code. Run `/skill-creator` to walk through the process with Claude. --- # Get started with Paddle in Python URL: https://developer.paddle.com/get-started/quickstart/python Install the Paddle Python SDK, initialize a client, make your first request, and verify webhook signatures. This quickstart walks through installing the Paddle Python SDK, initializing a client, making your first read-only API call, and verifying webhook signatures. Python is a common choice for AI workloads, internal tooling, and data scripts that work with billing data. ## Before you begin You'll need: - A [Paddle sandbox account](/sdks/sandbox). - A [sandbox API key](/api-reference/about/authentication) with permission to read and write products. - Python 3.11 or later. ## 1. Install the SDK Install `paddle-python-sdk` from PyPI. The package imports as `paddle_billing`. ```sh pip install paddle-python-sdk ``` ```sh poetry add paddle-python-sdk ``` ```sh uv add paddle-python-sdk ``` View the Python SDK on GitHub ## 2. Initialize the client Read your API key from the environment, then create a `Client` with the sandbox environment while you're building. ```python from os import getenv from paddle_billing import Client, Environment, Options api_key = getenv("PADDLE_API_KEY") paddle = Client(api_key, options=Options(Environment.SANDBOX)) ``` For production, drop the `options` argument: ```python paddle = Client(getenv("PADDLE_API_KEY")) ``` Sandbox API keys contain `_sdbx`. Sandbox and live keys are separate — using one against the other API returns a `forbidden` error. ## 3. Make your first request List products to confirm the client is wired up. The SDK returns an iterable collection that paginates lazily. ```python from os import getenv from paddle_billing import Client, Environment, Options from paddle_billing.Resources.Products.Operations import ListProducts, ProductIncludes from paddle_billing.Exceptions.ApiError import ApiError paddle = Client(getenv("PADDLE_API_KEY"), options=Options(Environment.SANDBOX)) try: products = paddle.products.list(ListProducts(includes=[ProductIncludes.Prices])) for product in products: print(product.id, product.name) except ApiError as error: print(f"Paddle API error: {error}") ``` If your sandbox account is empty, the loop runs zero times. Create a product in the dashboard or via `paddle.products.create()` to see results. ## Verify webhooks You can use [webhooks](/webhooks) to keep your app in sync with Paddle. For example, you can [provision access](/build/subscriptions/provision-access-webhooks) when a subscription is created, or revoke access when a subscription is cancelled. The SDK ships a `Verifier` that works with any request object matching its protocol. Flask and Django requests work out of the box. Always verify the signature before acting on the payload. ```python from os import getenv from flask import Flask, request from paddle_billing.Notifications import Secret, Verifier from paddle_billing.Entities.Notifications import NotificationEvent app = Flask(__name__) secret = Secret(getenv("PADDLE_WEBHOOK_SECRET")) @app.route("/webhooks", methods=["POST"]) def webhook(): if not Verifier().verify(request, secret): return "invalid signature", 400 notification = NotificationEvent.from_request(request) if notification.event_type == "transaction.completed": # Provision access, send a receipt, etc. pass elif notification.event_type == "subscription.updated": # Sync the subscription to your database. pass return "ok", 200 ``` For the full webhook setup flow, including creating notification destinations, picking events, and retry behavior, see [Verify webhook signatures](/webhooks/about/signature-verification). ## Next steps - Use the [webhook simulator](/webhooks/simulator) to test webhook events. - Browse the [API reference](/api-reference) to see every endpoint Paddle exposes. - View the [Python SDK reference](/sdks/libraries/python) for the full SDK reference. --- # Paddle for AI companies URL: https://developer.paddle.com/get-started/how-paddle-works/ai-companies How Paddle lets AI companies sell as fast as they can build, with 200+ markets, 30+ currencies, and global merchant of record coverage. AI founders understand that AI has changed the way we build software. But while it's quicker than ever to launch an AI product, selling software across the globe with tax, fraud, and compliance is still a challenge. Paddle lets you sell as fast as you can build. It's a merchant of record platform that helps you unlock new markets, grow your revenue, and scale your business. Companies like [n8n](https://www.paddle.com/customers/how-paddle-helped-n8n-io-grow-mrr), [Relay.app](https://www.paddle.com/customers/relayapp-live-in-weeks-sells-globally-without-barriers), [Hesse.ai](https://www.paddle.com/customers/how-paddle-unlocked-a-global-market-for-hesse.ai), [PostNitro](https://www.paddle.com/customers/postnitro-conversion-uplift-with-paddle), and [Kaleido.ai](https://www.paddle.com/customers/kaleido-software-launch) use Paddle to power their business. ## Key features for AI companies Subscriptions, one-time charges, and custom items are all modeled as products and prices. Automatically convert prices to local currencies, and set country-specific prices for key markets. Tax, fraud, and 200+ payment methods covered for you — useful when you're shipping fast and scaling up. Integrate quickly with the Paddle MCP server, docs MCP, agent skills, and starter kits. ## Flexible pricing models In Paddle, [products and prices](/build/products/create-products-prices) are flexible entities you compose into the structure you need. Products describe what you sell, and related prices describe how you sell a product. This means you can build common AI pricing patterns: - **Common SaaS pricing patterns**, like good-better-best, per-seat, and add-on based pricing. - **Subscription plans with included quota** when you want to offer a base plan with a quota baked into your provisioning logic - **Overages** can be billed as one-time or non-catalog items on top of a subscription plan or zero-dollar subscription. - **Pay-as-you-go** when you sell credit packs as one-time products. - **Custom enterprise deals**, with manual invoicing for negotiated terms. Paddle supports multi-product checkouts and subscriptions, so you can bundle multiple products together and bill for them together. ## Metering and usage-based billing If you bill for usage, you can track usage in your app using a metering library like [OpenMeter](https://openmeter.io/), then bill for it in Paddle. A typical workflow looks like this: 1. **Set up the base plan as a recurring price** on whatever cycle you bill on. 2. **Track usage in your app**. Use a metering library or track usage in your app. 3. **Add a custom line item to the upcoming transaction** with the metered amount, unit price, and a description, or create an item for overage and bill for it. 4. **Paddle bills the customer** at the next renewal, with the plan and the metered usage as separate line items. You can also bill for overage or usage right away, instead of waiting for the next renewal. You can also bill **purely usage-based** (no base plan) by creating a zero-dollar subscription then adding items for usage each billing cycle, or sell **prepaid credits** as one-time products and track the balance in your application. The pricing engine doesn't enforce a model, so you can compose what you need. For complex models, like tiered usage rates, free quotas, overage on top of included credits, you can combine the building blocks. ## Build with AI The Paddle MCP servers are the native integration point if you're building with AI tools or shipping AI features yourself. Drop-in instructions that teach coding agents how to integrate Paddle correctly. Build pricing, debug billing, generate reports, onboard enterprise customers — all in natural language. Gives AI agents up-to-date Paddle knowledge — every guide, the OpenAPI spec, and SDK references. Use both together. The docs MCP server tells the agent how Paddle works; the Paddle MCP server lets it take actions for you. ## Built for global scale AI products are born global from day one. As a merchant of record, Paddle handles the global complexity for you: - **Tax compliance in [200+ countries](/concepts/sell/supported-countries-locales)** without you registering for VAT, GST, or sales tax in each jurisdiction. - [**30+ currencies**](/concepts/sell/supported-currencies) with automatic currency conversion and FX handling. - **Local payment methods** like [Apple Pay](/concepts/payment-methods/apple-pay), [Google Pay](/concepts/payment-methods/google-pay), [PayPal](/concepts/payment-methods/paypal), [iDEAL](/concepts/payment-methods/ideal), [UPI](/concepts/payment-methods/upi), [Pix](/concepts/payment-methods/pix), and more. - **Card processing** routed to the best acquirer for each transaction to maximize approval rates. Every market you'd otherwise have to register and build for is already covered, payment methods can be turned on in a couple of clicks, and you can bill in any currency you want. ## Ready to scale Paddle comes with [hybrid billing support](/concepts/sell/sales-assisted-invoice), so you can move from subscriptions to invoices in one-click or API call, with no data duplication. If you're landing enterprise deals early, Paddle handles the manual side: - **Automatic subscription invoicing** with custom payment terms, purchase order numbers, and reconciliation handled by Paddle. - **Custom pricing** per customer, depending on the deal you've signed. - **Tax-exempt billing** for customers with valid VAT/GST registration numbers. - **Wire transfer payment** alongside the standard card and digital wallets. - **Invoice payment links**, so customers can pay directly by card or other payment method from the invoice. You can use the [Paddle MCP server](/sdks/ai/paddle-mcp) to drive enterprise onboarding using natural language: paste your quote, ask the agent to set up the customer, and review the draft before the agent issues the first invoice. ## Where to start Deploy a working SaaS app to Vercel in minutes. Add the Node.js SDK to your project to integrate Paddle Billing. Install the Paddle MCP or skills to add Paddle to your app. --- # Quickstart URL: https://developer.paddle.com/get-started/quickstart Get a step-by-step overview of how to get started with Paddle — including creating your catalog, previewing your pricing page, opening a checkout, and listening to webhooks. This quickstart walks through integrating Paddle end to end, including sandbox setup, product catalog, pricing page, checkout, and webhooks. It's framework-agnostic, with all the moving parts in one place. If you'd rather start from a working app or jump straight to your SDK, skip to [Pick a starting point](#pick-a-starting-point) at the bottom. Install the [Paddle MCP server](/sdks/ai/paddle-mcp) and [docs MCP server](/sdks/ai/docs-mcp) in your AI client to integrate faster. ## What we're building By the end of this quickstart, you'll have a typical three-tier pricing page connected to [Paddle Checkout](/concepts/sell/self-serve-checkout), with webhooks flowing for provisioning. You'll learn how to: - Sign up for Paddle and set up an account. - Create products and prices. - Work with Paddle.js to present localized prices. - Open a checkout and take a test payment. - Create a notification destination and preview webhooks. Explore the working code for this quickstart using our getting started CodePen. ## Overview 1. [**Sign up for Paddle**](#sign-up-for-paddle) Create a sandbox account. 2. [**Set up your product catalog**](#set-up-your-product-catalog) Create products and prices for the items you offer. 3. [**Build your pricing page**](#build-your-pricing-page) Show products from your catalog on a pricing page. 4. [**Open a checkout**](#open-a-checkout) Launch a checkout from your pricing page, then take a test payment. 5. [**Listen for webhooks**](#listen-for-webhooks) Preview the webhooks that occur during checkout for handling provisioning. ## Sign up for Paddle Paddle has two types of account: - [Sandbox](/sdks/sandbox) — for testing and evaluation. - Live — for selling to customers. For the best experience while testing, **sign up for a sandbox account**. You can sign up for a live account later when your integration is ready. ## Set up your product catalog Your [product catalog](/build/products/create-products-prices) includes subscription plans, recurring addons, one-time charges, and things like additional seats. There's no rigid hierarchy — everything you offer is a product. Create products and related prices to start billing. ### Model your pricing A complete product in Paddle is made up of two parts: 1. A [product entity](/api-reference/products) that describes the item — name, description, image. 2. At least one related [price entity](/api-reference/prices) 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 it's billed. ![Illustration showing a pricing page. One of the prices is Enterprise at $3000/mo. There's an arrow pointing to enterprise that says 1. product. Another arrow points to $3000/mo saying 2. price.](/src/assets/images/tmp-build/get-started-product-price-20250122.svg) We'll start with a three-tier pricing structure — `Starter`, `Pro`, and `Enterprise` — each with monthly and annual options: | | Starter | Pro | Enterprise | |-------------|--------:|--------:|-----------:| | **Monthly** | $10.00 | $30.00 | $300.00 | | **Annual** | $100.00 | $300.00 | $3000.00 | We'll model this in Paddle as three products with two prices each: ```mermaid graph LR Starter["Product: Starter"] Pro["Product: Pro"] Enterprise["Product: Enterprise"] Starter_Monthly["Price: Starter (monthly)"] Starter_Yearly["Price: Starter (yearly)"] Pro_Monthly["Price: Pro (monthly)"] Pro_Yearly["Price: Pro (yearly)"] Enterprise_Monthly["Price: Enterprise (monthly)"] Enterprise_Yearly["Price: Enterprise (yearly)"] Starter --> Starter_Monthly Starter --> Starter_Yearly Pro --> Pro_Monthly Pro --> Pro_Yearly Enterprise --> Enterprise_Monthly Enterprise --> Enterprise_Yearly ``` ### Create products and prices You can [create products and prices](/build/products/create-products-prices) using the Paddle dashboard or the API. For now, we'll create products and prices for `Starter` and `Pro` only. Instead of letting customers self-serve `Enterprise`, we'll ask them to contact us. 1. Go to **Paddle > Catalog > Products**. 2. Click New product 3. Enter details for your new product, then click Save when you're done. 4. Under the **Prices** section on the page for your product, click New price 5. Enter details for your new price. Set the billing period to **Monthly** to create a monthly price. 6. Click Save when you're done. 7. Create another price, setting the billing period to **Annually** and Save ![New product drawer in Paddle. It shows fields for product name, tax category, and description](/src/assets/images/tmp-build/dashboard-create-product-20230831.svg) Repeat so you have two products for `Starter` and `Pro`, each with a monthly and annual price. ## Build your pricing page [Pricing pages](/build/checkout/build-pricing-page) show potential customers the subscription plans you offer and how much they cost. They're typically a key part of customer conversion. Paddle includes [Paddle.js](/paddle-js), a lightweight JavaScript library for securely interacting with Paddle in your frontend. Use Paddle.js to build pricing pages that show prospects prices localized for their country, displayed in their local currency with estimated taxes. ### Get a client-side token [Client-side tokens](/paddle-js/about/client-side-tokens) let you interact with the Paddle platform in frontend code, like webpages or mobile apps. They have limited access to the data in your system, so they're safe to publish. 1. Go to **Paddle > Developer tools > Authentication**. 2. Click the **Client-side tokens** tab. 3. Click New client-side token 4. Give your client-side token a name and description, then click Save 5. From the list, click the button next to the token you just created, then choose Copy token from the menu. ![Create client-side token drawer in Paddle. It shows fields for name and description.](/src/assets/images/tmp-build/client-side-token-create-20250407.svg) We'll use your client-side token in the next step. ### Update constants Now we've created a client-side token and products and prices, let's add them to a pricing page. We'll use the **getting started CodePen**. Open the getting started CodePen In the CodePen, change the values in the `CONFIG` constant at the top of the JavaScript section: | Value | Description | |------------------------|-------------------------------------------------------------------------------| | `clientToken` | Client-side token you copied in the last step. | | `prices.starter.month` | Paddle ID for the monthly price of the starter product we created previously. | | `prices.starter.year` | Paddle ID for the annual price for the starter product we created previously. | | `prices.pro.month` | Paddle ID for the monthly price for the pro product we created previously. | | `prices.pro.year` | Paddle ID for the annual price for the pro product we created previously. | You can get Paddle IDs for your prices using the dashboard: 1. Go to **Paddle > Catalog > Products**, then click the product you want to get a price ID for. 2. Click the button next to a price in the list, then choose Copy price ID from the menu. 3. Paste the ID as a value for `month` or `year` in `prices.starter` or `prices.pro`. ![Prices list in the Paddle dashboard, with the action menu open and copy ID selected.](/src/assets/images/tmp-build/copy-price-id-20250127.svg) [Paddle IDs](/api-reference/about/paddle-ids) are designed to be easily recognizable. Price IDs start with `pri_` and product IDs start with `pro_`. Check that you've copied price IDs and not product IDs. ### Test your pricing page If you've added a valid client-side token and passed your price IDs correctly, you should see your prices on your pricing page. Prices are fetched dynamically from Paddle.js, so changes you make in the dashboard show up on the next call. Use the monthly/annual toggle to change prices, then test how localized pricing works using the country dropdown at the bottom. We've included a country dropdown to simulate price localization for demo purposes. In [real implementations](/build/checkout/build-pricing-page), let Paddle.js auto-localize. ![CodePen getting started project with the country selector dropdown open.](/src/assets/images/tmp-build/get-started-pricing-page-20250131.png) 1. We define variables for elements in our pricing page using `document.getElementById()`. 2. We set `currentBillingCycle` to `month` and `currentCountry` to `US` — the defaults shown when customers first visit the page. 3. We call our `InitializePaddle()` function. This calls [`Paddle.Environment.set()`](/paddle-js/methods/paddle-environment-set) to set the sandbox environment, and [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) to authenticate with our client-side token. We also call `updatePrices()` to fetch prices from Paddle.js. 4. To handle plan switching, `updateBillingCycle()` updates `currentBillingCycle`, changes the visual state of the buttons, and calls `updatePrices()`. 5. In `updatePrices()`, we build a `request` object with our prices and `currentCountry`, call [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview), and update the UI. 6. To handle country changes, an event listener updates `currentCountry` and calls `updatePrices()`. If prices don't appear, open the console for specific Paddle.js error messages. #### Common problems Check that: - Your [client-side token](/paddle-js/about/client-side-tokens) is correct. Sandbox tokens start with `test_`. - The Paddle IDs for [price entities](/api-reference/prices) are correct. Price IDs start with `pri_`. - You haven't duplicated any IDs in your `Paddle.PricePreview()` request — they must be unique. ## Open a checkout [Paddle Checkout](/concepts/sell/self-serve-checkout) is where customers make purchases. You can use [Paddle.js](/paddle-js) to add an [overlay checkout](/concepts/sell/overlay-checkout) — a full-page checkout that's optimized for conversion. ### Set a default payment link Before opening a checkout, you need [a default payment link](/build/transactions/default-payment-link). Paddle uses your default payment link to generate URLs for customers to manage payments and subscriptions. For now, set your default payment link to your homepage or development environment URL. You can always change it later. 1. Go to **Paddle > Checkout > Checkout settings**. 2. Enter your website homepage under the **Default payment link** heading. If you don't have one, enter `https://localhost/`. 3. Click Save when you're done. ![Checkout settings screen showing the Paddle dashboard. The default payment URL is set to https://example.com/](/src/assets/images/tmp-build/default-payment-link-20250127.svg) You can turn on other payment methods on this screen, too. We'll see eligible payment methods when we open a test checkout in the next step. ### Take a test payment In the CodePen, click **Get started** for a plan to open an overlay checkout. Take a test payment using our [test card details](/concepts/payment-methods/card): An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` Any name Any valid date in the future. `100` ![CodePen getting started project with overlay checkout open. Card, PayPal, and Apple Pay are presented as payment methods. Fields for email address, card number, expiration date, and country are visible.](/src/assets/images/tmp-build/get-started-checkout-20250131.png) 1. Each pricing card has a button with an `onClick` handler that calls `openCheckout()`, passing the plan name as a variable. 2. We initialize Paddle as described in the previous step using `InitializePaddle()`. 3. `openCheckout()` checks Paddle is initialized, then calls [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) with an [array of `items`](/build/checkout/pass-update-checkout-items) and [object of `settings`](/build/checkout/set-up-checkout-default-settings). If checkout doesn't load or you see "Something went wrong," open the console for Paddle.js errors. #### Common problems Check that: - You added [a default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link**. - Your [client-side token](/paddle-js/about/client-side-tokens) is correct. Sandbox tokens start with `test_`. - The price IDs you passed are correct. Price IDs start with `pri_`. - You haven't duplicated any price IDs in your items list. - You're opening a checkout from [a supported country](/concepts/sell/supported-countries-locales). ## Listen for webhooks [Webhooks](/webhooks) tell you when something important happens in your Paddle account. Paddle includes webhooks for the full purchase and subscription lifecycle — from new customer to cancellation. You'll use webhooks to [handle provisioning and fulfillment](/build/subscriptions/provision-access-webhooks) and to keep your app in sync with Paddle. For example: provision an account when a subscription is created, revoke access when one cancels. ### Create a webhook destination To start receiving webhooks, [create a notification destination](/webhooks/about/notification-destinations) — where you tell Paddle which events you want and where to deliver them. For this quickstart, we'll use **Hookdeck Console** instead of spinning up a webhook endpoint server. Hookdeck Console lets you receive webhooks in a friendly interface, with no account or setup required. Open Hookdeck Console 1. Go to [Hookdeck Console](https://console.hookdeck.com/), then copy the webhook endpoint URL. Keep this tab open. 2. In a new tab, go to **Paddle > Developer Tools > Notifications**. 3. Click New destination 4. Give your destination a memorable name. 5. Make sure notification type is set to **webhook** and usage type to **platform** — these are the defaults. 6. Paste the webhook endpoint URL you copied from Hookdeck Console. 7. Check the **select all events** box. 8. Click Save destination when you're done. ![Illustration of the new destination drawer in Paddle. It shows fields for description, type, URL, and version. Under those fields, there's a section called events with a checkbox that says 'select all events'](/src/assets/images/tmp-build/create-webhook-destination-20240912.svg) ### Take another test payment Now we have a notification destination, run through checkout again to see the events. Open your CodePen and take another test payment. As you move through checkout, you should see webhooks land in Hookdeck. ![Hookdeck Console with a list of webhooks. The transaction.completed webhook is selected, and the JSON payload is visible.](/src/assets/images/tmp-build/get-started-webhooks-20250131.png) [Paddle.js also emits events](/paddle-js/events) during the checkout lifecycle. Frontend events contain information about items and totals you can use to build advanced workflows. As you move through checkout, Paddle creates and updates entities in your account. Webhooks fire each time an entity is created or updated. For provisioning and order fulfillment, these core webhooks are useful: | Webhook | Useful for | |-------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [`transaction.created`](/webhooks/transactions/transaction-created) | Transactions handle capturing payment and calculating revenue. Paddle Checkout creates a transaction, then updates it as the customer enters information and completes payment. | | [`customer.created`](/webhooks/customers/customer-created) | When a customer enters their email address, Paddle creates a customer for you. | | [`address.created`](/webhooks/addresses/address-created) | When a customer enters their country and ZIP/postal code, Paddle creates an address related to this customer. | | [`business.created`](/webhooks/addresses/address-created) | If a customer chooses to enter a tax/VAT number, Paddle creates a business. The option to add a tax number is presented in regions that require it, like most of Europe. | | [`transaction.paid`](/webhooks/transactions/transaction-paid) | When payment goes through successfully, the transaction status changes to `paid`. At this point you can be sure the customer paid and start provisioning. | | [`subscription.created`](/webhooks/subscriptions/subscription-created) | If a checkout is for recurring items, Paddle automatically creates a subscription for the customer, address, and business. Status is `active` or `trialing`, depending on the items on the subscription. | | [`transaction.completed`](/webhooks/transactions/transaction-completed) | Once payment is received and a subscription is created, Paddle continues processing — adding subscription details, an invoice number, and information about fees, payouts, and earnings. | If you don't see webhooks in Hookdeck Console, check the notification destination in Paddle to see if they were sent and whether they're queued for retry. #### Common problems Check that: - You set up [a notification destination](/webhooks/about/notification-destinations) correctly. Your webhook endpoint should look like `https://hkdk.events/azz5twg6es4g41`, not `https://console.hookdeck.com/`. - Usage type is set to **platform only** or **both**. The simulation-only option only works with the webhook simulator. - All events are checked. - Hookdeck accepted your webhooks. Go to **Paddle > Notifications**, click the button next to your destination, and choose View logs. ## Pick a starting point If you'd rather skip the framework-agnostic walkthrough and start from a working app or a specific SDK, pick a starting point: ### Starter kits Deploy a complete Paddle + Supabase + Vercel app. Includes a three-tier subscriptions, customer portal, webhook handling. The recommended starter for mobile apps. Includes marketing, legal, and pricing pages built in. ### Backend SDK quickstarts Install the Paddle Node SDK, initialize a client, and make your first request. Install the Paddle Python SDK — recommended for AI, data, and internal tooling. Install the Paddle Go SDK — designed for scale and `context.Context`-aware backends. Install the Paddle PHP SDK. Using Laravel? Use Cashier Paddle instead. ## Next steps That's it. Now you've covered the basics, we recommend starting your integration or learning more about the Paddle platform. Pre-flight checks before going live with Paddle. Make sure your integration is ready. Move from sandbox to live and start accepting real payments on Paddle. Let customers self-serve: manage their subscriptions and payments. Minimize churn with dunning, payment recovery, and cancellation flows. --- # Build with Codex URL: https://developer.paddle.com/get-started/ai/codex Set up Codex for the best possible Paddle workflow by installing the Paddle plugin, connecting the Paddle MCP server, and adding an AGENTS.md file. [Codex](https://developers.openai.com/codex) is OpenAI's AI coding agent. It runs as a CLI, an IDE extension, and on the web. This guide runs through setting up Codex for the best possible experience when building with Paddle. ## Before you begin You'll need: - A Paddle [sandbox account](/sdks/sandbox) - A [sandbox API key](/api-reference/about/authentication) with any permissions you plan to use. - Codex installed. - A project, with a Paddle SDK installed. ## Install the Paddle plugin The Paddle plugin bundles [Paddle agent skills](/sdks/ai/agent-skills) with the [docs MCP server](/sdks/ai/docs-mcp) and [Paddle MCP servers](/sdks/ai/paddle-mcp/). From your terminal, run: ```bash codex plugin marketplace add PaddleHQ/paddle-agent-skills ``` Then open Codex and install the `paddle` plugin from the plugin directory. To refresh later, run `codex plugin marketplace upgrade paddle-agent-skills`. ## Set your Paddle API keys The plugin wires up two Paddle MCP servers so the agent can work in both sandbox and live environments. This is useful for porting data from one to the other. | MCP server | URL | API key env var | | ---------------- | ------------------------------------ | ------------------------ | | `paddle-sandbox` | `https://sandbox-mcp.paddle.com/mcp` | `PADDLE_SANDBOX_API_KEY` | | `paddle-live` | `https://mcp.paddle.com/mcp` | `PADDLE_LIVE_API_KEY` | They read API keys from environment variables. Export them in your shell profile (`~/.zshrc`, `~/.bashrc`, or equivalent): ```sh export PADDLE_SANDBOX_API_KEY= # Your sandbox API key export PADDLE_LIVE_API_KEY= # Your live API key ``` [Create API keys](/api-reference/about/authentication) at **Paddle > Developer tools > Authentication**. Restart Codex and your terminal after setting the variables so the MCP servers pick them up. The skills default to sandbox unless you've explicitly opted into live, so `PADDLE_SANDBOX_API_KEY` is the one to start with during development. Start with a sandbox API key while you're evaluating. Scope your live API key narrowly, since the Paddle MCP server doesn't gate destructive operations on its own. See [Permissions and destructive actions](/sdks/ai/paddle-mcp#permissions-and-destructive-actions) for details. ## Add Paddle conventions to your AGENTS.md Codex reads project conventions from `AGENTS.md` at the project root. A focused Paddle section tells it which SDK to use, how to handle environments, and how to verify webhooks. Create an `AGENTS.md` file at the project root, or add this to your existing `AGENTS.md` file. ```markdown ## Paddle integration When writing or modifying code that integrates with Paddle: - Always check current Paddle documentation via the `paddle-docs` MCP server before suggesting code. The Paddle API and SDKs evolve frequently — do not rely on training data alone. - Use the official Paddle SDK for the language in use: - Node.js → `@paddle/paddle-node-sdk` - Python → `paddle-python-sdk` (imports as `paddle_billing`) - Go → `github.com/PaddleHQ/paddle-go-sdk/v5` - PHP → `paddlehq/paddle-php-sdk` - All development uses the sandbox environment. Sandbox API keys contain `_sdbx`; sandbox client-side tokens are prefixed with `test_`. - Always verify webhook signatures before acting on the payload: - Node: `paddle.webhooks.unmarshal()` - Python: `Verifier().verify(request, secret)` - Go: `paddle.NewWebhookVerifier()` with `Middleware` - PHP: `(new Verifier())->verify($request, $secret)` - For destructive account changes (updating prices, archiving products, cancelling subscriptions), ask for explicit confirmation before calling the `paddle-sandbox` or `paddle-live` MCP server. - Use `paddle-sandbox` by default. Only call `paddle-live` when the prompt explicitly mentions live, production, or real customer data. - API keys and webhook secrets live in environment variables — never inline credentials into code. ``` Adjust the SDK list to match the language(s) your project uses. Codex will read this every time you start a session. ## Prompt your first integration With the plugin installed, the Paddle MCP server connected, and your `AGENTS.md` file in place, ask Codex to scaffold a Paddle integration. ```markdown Add a Paddle Checkout integration to this Next.js app. Use the paddle-docs MCP server to look up the current Paddle.js syntax. Then use the paddle-sandbox MCP server to create three products in my sandbox account — Starter ($10/mo), Pro ($30/mo), Enterprise ($300/mo) — and generate a /pricing page that opens checkout for each tier. Wire up a /api/webhooks route that verifies the signature and logs transaction.completed events. ``` Codex plans first, then uses the docs MCP for current syntax, the Paddle MCP to create the products in sandbox, and writes code that matches your `AGENTS.md` conventions. ## Best practices - **Mention Paddle and the relevant MCP server explicitly.** "Use the paddle-docs MCP server to look up X" beats hoping the agent calls it on its own. - **Be specific in prompts.** Concrete pricing, event names, and entity IDs work better than vague descriptions. - **Review changes before applying.** Codex shows pending tool calls before executing. Review every tool call, especially anything destructive. - **Never commit API keys.** Keep API keys in environment variables. - **Create your own skills.** When you find yourself repeating the same instructions over and over, package them as a skill. Run the `$plugin-creator` skill in Codex to walk through it. --- # Get started with Paddle in Go URL: https://developer.paddle.com/get-started/quickstart/go Install the Paddle Go SDK, initialize a client, make your first request, and verify webhook signatures. This quickstart walks through installing the Paddle Go SDK, initializing a client, making your first read-only API call, and verifying webhook signatures. The Go SDK is built around `context.Context` throughout, useful for backends that need cancellation, timeouts, and request-scoped values. ## Before you begin You'll need: - A [Paddle sandbox account](/sdks/sandbox). - A [sandbox API key](/api-reference/about/authentication) with permission to read and write products. - Go 1.21 or later. ## Install the SDK Add the SDK to your Go module: ```sh go get github.com/PaddleHQ/paddle-go-sdk/v5 ``` View the Go SDK on GitHub ## Initialize the client For sandbox, use `paddle.NewSandbox()`. For production, use `paddle.New()`. ```go package main import ( "log" "os" paddle "github.com/PaddleHQ/paddle-go-sdk/v5" ) func main() { client, err := paddle.NewSandbox(os.Getenv("PADDLE_API_KEY")) if err != nil { log.Fatal(err) } _ = client } ``` For production, swap `NewSandbox` for `New`: ```go client, err := paddle.New(os.Getenv("PADDLE_API_KEY")) ``` Sandbox API keys contain `_sdbx`. Sandbox and live keys are separate — using one against the other API returns a `forbidden` error. ## Make your first request List products to confirm the client is wired up. The SDK returns a paginated collection. Use `Iter` to walk through results. ```go package main import ( "context" "fmt" "log" "os" paddle "github.com/PaddleHQ/paddle-go-sdk/v5" ) func main() { client, err := paddle.NewSandbox(os.Getenv("PADDLE_API_KEY")) if err != nil { log.Fatal(err) } ctx := context.Background() products, err := client.ListProducts(ctx, &paddle.ListProductsRequest{ IncludePrices: true, }) if err != nil { log.Fatal(err) } err = products.Iter(ctx, func(p *paddle.Product) (bool, error) { fmt.Printf("%s %s\n", p.ID, p.Name) return true, nil }) if err != nil { log.Fatal(err) } } ``` Pass a cancellable `context.Context` through your call chain so the SDK can abort in-flight requests when the parent context is cancelled. ## Verify webhooks You can use [webhooks](/webhooks) to keep your app in sync with Paddle. For example, you can [provision access](/build/subscriptions/provision-access-webhooks) when a subscription is created, or revoke access when a subscription is cancelled. The Go SDK's `WebhookVerifier` ships with a `Middleware` helper that wraps your handler and rejects unverified requests automatically. ```go package main import ( "log" "net/http" "os" paddle "github.com/PaddleHQ/paddle-go-sdk/v5" ) func main() { verifier := paddle.NewWebhookVerifier(os.Getenv("PADDLE_WEBHOOK_SECRET")) handler := verifier.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Request is verified. Decode and dispatch the event. // Best practice: store the event in a queue or DB and return 200 fast. w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"success":true}`)) })) http.Handle("/webhooks", handler) log.Fatal(http.ListenAndServe(":3000", nil)) } ``` If you'd rather verify manually, call `Verify` directly: ```go ok, err := verifier.Verify(r) if err != nil || !ok { http.Error(w, "invalid signature", http.StatusBadRequest) return } ``` For the full webhook setup flow, including creating notification destinations, picking events, and retry behavior, see [Verify webhook signatures](/webhooks/about/signature-verification). ## Next steps - Use the [webhook simulator](/webhooks/simulator) to test webhook events. - Browse the [API reference](/api-reference) to see every endpoint Paddle exposes. - View the [Go SDK reference](/sdks/libraries/go) for the full SDK reference. --- # Paddle for digital products URL: https://developer.paddle.com/get-started/how-paddle-works/digital-products Sell software licenses, digital downloads, plugins, courses, and other one-off purchases globally. Paddle handles payments, tax, fraud, and global compliance. Sell your e-books, software licenses, courses, plugins, and other downloadable files anywhere in the world with Paddle as your merchant of record. Payments, localization, tax, and global compliance handled out-of-the-box. Companies like [Nexus Mods](https://www.paddle.com/customers/helping-nexus-mods-unlock-the-gaming-community), [Fortinet](https://www.paddle.com/customers/how-fortinet-training-institute-optimized-self-serve-billing), [MotionVFX](https://www.paddle.com/customers/how-motionvfx-increased-conversions-with-paddle) and [MacPaw](https://www.paddle.com/customers/macpaw-move-to-saas) use Paddle to power their business. ## Key features for digital product businesses Paddle handles tax registration and sales tax. You don't need a tax department to sell globally on day one. Automatically convert prices to local currencies, and set country-specific prices for key markets. Paddle routes payments to the best acquirer for that sale to get the best possible success rate. Integrate quickly with the Paddle MCP server, docs MCP, agent skills, and starter kits. ## One-time products In Paddle, products and prices are flexible entities you compose into the structure you need. Products describe what you sell, and related prices describe how you sell a product. A digital product in Paddle is typically a [product](/build/products/create-products-prices) with a price that has no recurring interval set. This is how the flow works: 1. **Create a product** with one or more one-time prices in your Paddle catalog. 2. **Build a pricing page or buy button** that calls Paddle.js with the price ID. 3. **Customer pays** through Paddle Checkout. You can choose overlay checkout for a simple integration, or inline checkout for a more branded experience. 4. **Paddle sends** a [`transaction.completed`](/webhooks/transactions/transaction-completed) webhook. Your webhook handler grants access, sends a license key, or unlocks the download. The one-time purchase flow isn't a watered-down version of subscriptions. You get the same optimized checkout experience, webhooks, AI tooling, and SDKs that we offer for subscriptions. ## Pricing flexibility One-time products don't have to be flat prices: - **Multiple price points per product** Offer "Pro license $99" and "Team license $499" on the same product. - **Localized pricing** Set per-region prices in [30+ currencies](/concepts/sell/supported-currencies), or let Paddle auto-localize from your base price. - **Discount codes and promotional pricing** Apply percentage or fixed-amount discounts at checkout, scoped to specific products or campaigns. - **Custom amounts at checkout** Build more complex workflows by creating a transaction in your backend. Pass a one-off price into the transaction for "name your price" or quote-driven sales. - **Bundle pricing** Paddle supports multi-product checkout, so you can combine multiple products into a single transaction with a single payment. You can launch promotions, regional sales, or customer-specific deals without redeploying. ## Going global without a tax department Selling digital products across borders quickly turns into a tax problem: VAT in the EU and UK, GST in Australia, India, and Singapore, sales tax across US states, country-specific digital services taxes. Paddle is a merchant of record, which means: - **Tax is calculated and added at checkout** automatically based on the customer's location. - **Paddle remits** the tax to the right authority — not you. - **Customers see compliant invoices** with the correct VAT/GST treatment, downloadable from their email or the customer portal. - **No registration thresholds to track** in each jurisdiction, because Paddle handles this for you. - **You can offer [local payment methods](/concepts/payment-methods)** in a couple of clicks, since Paddle has local entities across the world. [Nexus Mods saw a 9x revenue uplift in China](https://www.paddle.com/customers/helping-nexus-mods-unlock-the-gaming-community) after switching to Paddle and unlocking local payment methods and tax compliance they couldn't have built themselves. ## Optimized checkout experience Paddle Checkout works out of the box for digital products: - [**Overlay checkout**](/concepts/sell/overlay-checkout) opens over your existing site, with minimal disruption to your design. - [**Inline checkout**](/concepts/sell/branded-integrated-inline-checkout) embeds the form directly in your page for full visual control. - [**Hosted checkout**](/concepts/sell/hosted-checkout-mobile-apps) is fully hosted by Paddle, for Mac or iOS apps only. All three support Apple Pay, Google Pay, PayPal, cards, and local methods like iDEAL, Bancontact, UPI, and Pix — selected automatically based on the customer's region. After payment, Paddle sends the customer a receipt email with a downloadable invoice. The [customer portal](/concepts/sell/customer-portal) gives them a place to access past purchases without having to ask you. ## Where to start Walk through the five-step framework-agnostic onboarding experience. Create products and prices in your Paddle catalog. Drop in Paddle.js, render localized prices, and wire up a buy button on your site. --- # Starter kits URL: https://developer.paddle.com/get-started/starter-kits Starter kits include everything you need to get started with Paddle. Starter kits are production-ready apps you can clone, configure, and deploy. They include everything you need to get started with Paddle. For a high-level overview of what each kit ships with, see the [reference pages under SDKs and tools](/sdks/starter-kits). --- # Build with AI tools URL: https://developer.paddle.com/get-started/ai Build your Paddle integration with AI tools. Build your Paddle integration faster with AI tooling. Cursor, Claude Code, Codex, and Gemini CLI connect to Paddle through the [Paddle MCP server](/sdks/ai/paddle-mcp), the [docs MCP server](/sdks/ai/docs-mcp), and [agent skills](/sdks/ai/agent-skills). Lovable comes with built-in Paddle support through Lovable Payments. For a high-level overview of agents, MCP, and the underlying tooling, see the [Agents and AI](/sdks/ai) reference pages. ## Get started ## Learn more --- # Build with Gemini CLI URL: https://developer.paddle.com/get-started/ai/gemini-cli Set up Gemini CLI for the best possible Paddle workflow by installing the Paddle extension, connecting the Paddle MCP server, and adding a GEMINI.md file. [Gemini CLI](https://github.com/google-gemini/gemini-cli) is an AI coding agent made by Google. It runs in your terminal and integrates with Gemini models. This guide runs through setting up Gemini CLI for the best possible experience when building with Paddle. ## Before you begin You'll need: - A Paddle [sandbox account](/sdks/sandbox) - A [sandbox API key](/api-reference/about/authentication) with any permissions you plan to use. - Gemini CLI installed. - A project, with a Paddle SDK installed. ## Install the Paddle extension The Paddle Gemini extension bundles the [Paddle agent skills](/sdks/ai/agent-skills) with the [docs MCP server](/sdks/ai/docs-mcp) and the [Paddle MCP servers](/sdks/ai/paddle-mcp/). Gemini auto-discovers the skills and connects the MCP servers when you install it. From your terminal, run: ```bash gemini extensions install PaddleHQ/paddle-agent-skills ``` Restart Gemini CLI for the extension to load. ## Set your Paddle API keys The extension wires up two Paddle MCP servers so the agent can work in both sandbox and live environments. This is useful for porting data from one to the other. | MCP server | URL | API key env var | | ---------------- | ------------------------------------ | ------------------------ | | `paddle-sandbox` | `https://sandbox-mcp.paddle.com/mcp` | `PADDLE_SANDBOX_API_KEY` | | `paddle-live` | `https://mcp.paddle.com/mcp` | `PADDLE_LIVE_API_KEY` | The MCP servers read API keys from environment variables. Export them in your shell profile (`~/.zshrc`, `~/.bashrc`, or equivalent): ```sh export PADDLE_SANDBOX_API_KEY= # Your sandbox API key export PADDLE_LIVE_API_KEY= # Your live API key ``` [Create API keys](/api-reference/about/authentication) at **Paddle > Developer tools > Authentication**. Restart Gemini CLI and your terminal after setting the variables so the MCP servers pick them up. Default to sandbox during development, so `PADDLE_SANDBOX_API_KEY` is the one to start with. Start with a sandbox API key while you're evaluating. Scope your live API key narrowly, since the Paddle MCP server doesn't gate destructive operations on its own. See [Permissions and destructive actions](/sdks/ai/paddle-mcp#permissions-and-destructive-actions) for details. ## Add Paddle conventions to your GEMINI.md Gemini CLI reads project conventions from `GEMINI.md` at the project root. A focused Paddle section tells it which SDK to use, how to handle environments, and how to verify webhooks. Create a `GEMINI.md` file at the project root, or add this to your existing `GEMINI.md` file. ```markdown ## Paddle integration When writing or modifying code that integrates with Paddle: - Always check current Paddle documentation via the `paddle-docs` MCP server before suggesting code. The Paddle API and SDKs evolve frequently — do not rely on training data alone. - Use the official Paddle SDK for the language in use: - Node.js → `@paddle/paddle-node-sdk` - Python → `paddle-python-sdk` (imports as `paddle_billing`) - Go → `github.com/PaddleHQ/paddle-go-sdk/v5` - PHP → `paddlehq/paddle-php-sdk` - All development uses the sandbox environment. Sandbox API keys contain `_sdbx`; sandbox client-side tokens are prefixed with `test_`. - Always verify webhook signatures before acting on the payload: - Node: `paddle.webhooks.unmarshal()` - Python: `Verifier().verify(request, secret)` - Go: `paddle.NewWebhookVerifier()` with `Middleware` - PHP: `(new Verifier())->verify($request, $secret)` - For destructive account changes (updating prices, archiving products, cancelling subscriptions), ask for explicit confirmation before calling the `paddle-sandbox` or `paddle-live` MCP server. - Use `paddle-sandbox` by default. Only call `paddle-live` when the prompt explicitly mentions live, production, or real customer data. - API keys and webhook secrets live in environment variables — never inline credentials into code. ``` Adjust the SDK list to match the language(s) your project uses. Gemini CLI loads this every time you start a session. ## Prompt your first integration With the extension installed, the Paddle MCP servers connected, and your `GEMINI.md` file in place, ask Gemini to scaffold a Paddle integration. ```markdown Add a Paddle Checkout integration to this Next.js app. Use the paddle-docs MCP server to look up the current Paddle.js syntax. Then use the paddle-sandbox MCP server to create three products in my sandbox account — Starter ($10/mo), Pro ($30/mo), Enterprise ($300/mo) — and generate a /pricing page that opens checkout for each tier. Wire up a /api/webhooks route that verifies the signature and logs transaction.completed events. ``` Gemini uses the docs MCP for current syntax, the Paddle MCP to create the products in sandbox, and writes code that matches your `GEMINI.md` conventions. ## Best practices - **Mention Paddle and the relevant MCP server explicitly.** "Use the paddle-docs MCP server to look up X" beats hoping the agent calls it on its own. - **Be specific in prompts.** Concrete pricing, event names, and entity IDs work better than vague descriptions. - **Review tool calls before approving them.** Gemini CLI prompts before executing MCP tools. Read every call, especially anything destructive. - **Never commit API keys.** Keep API keys in environment variables. - **Create your own skills.** When you find yourself repeating the same instructions over and over, package them as a SKILL.md file in your project's `skills/` directory. Gemini auto-discovers them. --- # Get started with Paddle in PHP URL: https://developer.paddle.com/get-started/quickstart/php Install the Paddle PHP SDK, initialize a client, make your first request, and verify webhook signatures. This quickstart walks through installing the Paddle PHP SDK, initializing a client, making your first read-only API call, and verifying webhook signatures. Use [Laravel Cashier Paddle](/sdks/community/laravel-cashier) instead. It's maintained by the Laravel team and comes with framework-native conventions for payments, billing, and subscriptions. ## Before you begin You'll need: - A [Paddle sandbox account](/sdks/sandbox). - A [sandbox API key](/api-reference/about/authentication) with permission to read and write products. - PHP 8.1 or later, with [Composer](https://getcomposer.org/) installed. ## Install the SDK Install `paddlehq/paddle-php-sdk` with Composer: ```sh composer require paddlehq/paddle-php-sdk ``` View the PHP SDK on GitHub ## Initialize the client Create a `Client` with your API key. Use the `SANDBOX` environment while you're building. ```php use Paddle\SDK\Client; use Paddle\SDK\Environment; use Paddle\SDK\Options; $paddle = new Client( apiKey: getenv('PADDLE_API_KEY'), options: new Options(Environment::SANDBOX), ); ``` For production, drop the `options` argument: ```php $paddle = new Client(getenv('PADDLE_API_KEY')); ``` Sandbox API keys contain `_sdbx`. Sandbox and live keys are separate — using one against the other API returns a `forbidden` error. ## Make your first request List products to confirm the client is wired up. The SDK returns an iterable that paginates automatically. ```php use Paddle\SDK\Client; use Paddle\SDK\Environment; use Paddle\SDK\Options; use Paddle\SDK\Exceptions\ApiError; $paddle = new Client( apiKey: getenv('PADDLE_API_KEY'), options: new Options(Environment::SANDBOX), ); try { $products = $paddle->products->list(); foreach ($products as $product) { echo $product->id . ' ' . $product->name . PHP_EOL; } } catch (ApiError $error) { echo 'Paddle API error: ' . $error->getMessage(); } ``` If your sandbox account is empty, the loop runs zero times. Create a product in the dashboard or via `$paddle->products->create()` to see results. ## 4. Verify webhooks You can use [webhooks](/webhooks) to keep your app in sync with Paddle. For example, you can [provision access](/build/subscriptions/provision-access-webhooks) when a subscription is created, or revoke access when a subscription is cancelled. The SDK's `Verifier` accepts any [PSR-7](https://www.php-fig.org/psr/psr-7/) `RequestInterface`, like Guzzle, Symfony, Laravel (`Illuminate\Http\Request::toPsrRequest()`), and most modern PHP HTTP frameworks all support this. Always verify the signature before acting on the payload. ```php use GuzzleHttp\Psr7\ServerRequest; use Paddle\SDK\Entities\Event; use Paddle\SDK\Notifications\Secret; use Paddle\SDK\Notifications\Verifier; $request = ServerRequest::fromGlobals(); $secret = new Secret(getenv('PADDLE_WEBHOOK_SECRET')); if (!(new Verifier())->verify($request, $secret)) { http_response_code(400); exit('invalid signature'); } $event = Event::fromRequest($request); switch ($event->eventType) { case 'transaction.completed': // Provision access, send a receipt, etc. break; case 'subscription.updated': // Sync the subscription to your database. break; } http_response_code(200); echo 'ok'; ``` For the full webhook setup flow, including creating notification destinations, picking events, and retry behavior, see [Verify webhook signatures](/webhooks/about/signature-verification). ## Next steps - Explore [Laravel Cashier Paddle](/sdks/community/laravel-cashier) for a Laravel-native way to build with Paddle. - Use the [webhook simulator](/webhooks/simulator) to test webhook events. - Browse the [API reference](/api-reference) to see every endpoint Paddle exposes. - View the [PHP SDK reference](/sdks/libraries/php) for the full SDK reference. --- # Credit or debit cards URL: https://developer.paddle.com/concepts/payment-methods/card Let customers pay with credit or debit cards. No configuration required. Credit and debit cards are the most popular way to pay online globally. Paddle supports all major types of card, including Visa, Mastercard, American Express, Maestro, Cartes Bancaires, Dankort, UnionPay, JCB, Diners Club, and Discover. [Paddle Checkout](/concepts/sell/self-serve-checkout) makes it as easy as possible to pay with card. It presents the card form immediately alongside other payment methods, formats card numbers automatically, shows the card type, and validates form fields before customers hit pay. Card is available as a payment option right away when using Paddle. ![Illustration showing card network logos supported by Paddle. From left to right: Visa, Mastercard, American Express, Maestro, Cartes Bancaires, Dankort, UnionPay, JCB, Diners Club, and Discover.](/src/assets/images/logos/card-schemes.svg) Paddle supports the most popular card networks for global payment acceptance. There's over 17bn credit, debit, and prepaid cards worldwide as of Oct 2023. Americans hold an average of 3.9 active credit cards across multiple banks. ## How it works Cards are always on for Paddle Checkout. The payment page presents the card form right away, alongside other payment method options. Sometimes customers are asked to authenticate a purchase using 3D Secure 2 (3DS2). Depending on the bank or card provider, customers may be prompted to enter a code sent by SMS or using their banking app. If successful, Paddle Checkout shows a success screen or enters your success workflow. ## Accept cards You don't need to do anything to enable card payments. They're always enabled for Paddle accounts. ## Save cards at checkout You can let customers securely save their card for future purchases. 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, Check **Allow buyers to opt in to save their payment methods for future purchases**. 3. Click **Save** to apply. ## Test cards You can test cards using your Paddle sandbox account. Open a checkout, then use these card details to test payment by card: | Card type | Card number | | ------------------------------------------------------ | ------------------------------------------------------ | | Valid Visa debit card | 4000 0566 5566 5556 | | Valid card without 3DS | 4242 4242 4242 4242 | | Valid card with 3DS | 4000 0038 0000 0446 | | Declined card | 4000 0000 0000 0002 | | Initial success, subsequent decline | 4000 0027 6000 3184 | Enter any cardholder name and a valid expiry date in the future. You can't use real card details when working with your Paddle sandbox account. --- # Concepts URL: https://developer.paddle.com/concepts Understand how Paddle works and learn about the key parts of the Paddle platform. --- # Paddle Checkout URL: https://developer.paddle.com/concepts/sell/self-serve-checkout Put the power of Paddle in your app with Paddle Checkout. Drop an overlay checkout into your app with a few lines of code, or fully integrate Paddle with your subscription management workflow using inline checkout. Use [Paddle.js](/paddle-js) to add a checkout to your app or website, letting customers sign up and pay for subscriptions. Choose from overlay checkout or inline checkout, depending on how you want to build your integration. Paddle automatically creates a subscription when a checkout completes, [ready for you to provision](/build/subscriptions/provision-access-webhooks). Present multi-product subscriptions and complex billing scenarios clearly. Short purchase journey and intelligent routing to the best acquirer for payment success. Add your brand colors and choose a theme, or seamlessly integrate into your app. Sell in over 200 markets with local languages, currencies, and payment methods. Transition subscriptions between automatic collection or billing by invoice. Include Paddle.js and launch an overlay checkout with just a few lines of code. ## Ways to integrate **Integrate Paddle in just a few lines of code. Launch an overlay to capture payment.** Add in minutes. Launches an overlay that includes your logo and brand color. Choose a one-page or multi-page checkout experience. Works with [all supported payment methods](/concepts/payment-methods). Works across all supported [countries](/concepts/sell/supported-countries-locales) and [currencies](/concepts/sell/supported-currencies). 3DS2 support. Data stored in a fully PCI-1-compliant vault. [**Learn more about overlay checkout →**](/concepts/sell/overlay-checkout) **Build integrated checkout experiences. Capture payment directly in your app.** More engineering resource needed. Fully embedded into your app or website, with [no-code customization](/build/checkout/brand-customize-inline-checkout) of colors and borders. Choose a one-page or multi-page checkout experience. Works with [all supported payment methods](/concepts/payment-methods). Works across all supported [countries](/concepts/sell/supported-countries-locales) and [currencies](/concepts/sell/supported-currencies). 3DS2 support. Data stored in a fully PCI-1-compliant vault. [**Learn more about inline checkout →**](/concepts/sell/branded-integrated-inline-checkout) ## Go live with checkout Whether you choose overlay or inline checkout, go live in a few steps: 1. **Sign up for Paddle** Create your Paddle [sandbox](/sdks/sandbox) and production accounts, then complete initial setup. 2. **Create product catalog** Create [products and prices](/build/products/create-products-prices), including [country-specific prices](/build/products/offer-localized-pricing) and [discounts](/build/products/offer-discounts-promotions-coupons). 3. **Integrate checkout** [Drop an overlay checkout](/build/checkout/build-overlay-checkout) into your app with a few lines of code, or [build a fully integrated experience](/build/checkout/build-branded-inline-checkout) using inline checkout. [Customize and brand](/build/checkout/brand-customize-inline-checkout) your checkout using the Paddle dashboard. 4. **Handle provisioning and subscription lifecycle events** Paddle automatically creates a subscription when checkout completes. Use webhooks or the event stream to [provision your app](/build/subscriptions/provision-access-webhooks), and handle other subscription lifecycle events like upgrades, downgrades, and cancellations. 5. **Start transacting** You're ready to start selling with Paddle. ## Hybrid billing With Paddle, you can conquer upmarket and downmarket — all from the same integrated platform. - **Win larger customers with invoices** Create and issue bespoke invoices and offer payment by bank transfer, perfect for larger-dollar deals like enterprise plans. - **Easy upsells** Present customers who pay by invoice with a checkout for smaller charges, like adding users or modules mid-cycle. - **Scale with customers** Meet the needs of growing customers by transitioning customers who pay using Paddle Checkout to billing by invoice. --- # Payment Recovery URL: https://developer.paddle.com/concepts/retain/payment-recovery-dunning Send optimized payment failure notifications, automatically retry payment methods, and show frictionless payment recovery forms in your app or website. Payment failures for a subscription are the most common form of churn for SaaS businesses. Payment Recovery, part of [Paddle Retain](/concepts/retain), automatically recovers subscriptions that are at risk of churning because of payment failure and expired payment methods. You don't need to set up complicated retry logic or spend hours writing email templates to use Payment Recovery — we've analyzed billions of datapoints to optimize payment retries and notifications for you. You can [integrate with Paddle Billing](/build/retain) in a couple of clicks, with no additional scripts required. {% card title="recovery rate for failed payments" stat="50%+" statColor="blue" %} Paddle Retain has the highest payment recovery rate on the market. {% card title="overall cut in involuntary churn" stat="17%" statColor="green" %} Optimized retries and notifications reduce churn without any intervention. Boost recovery success with optimized timing based on 15+ datapoints. Retain automatically retries failed payment methods over a 30-day window. Customers can securely update their details without logging in to your app. Send whitelabeled payment recovery notifications by email, in your app, and by SMS. ![No-login Payment Recovery form over the AeroEdit sample app pricing page. It says that $100 is overdue; it includes buttons to pay by digital wallet like Apple Pay, Google Pay, or PayPal; and it has a form for card details.](/src/assets/images/tmp/payment-recovery-hero-20250107.svg) ## How it works Involuntary churn happens when a customer unintentionally drops a subscription because of a problem with their [payment method.](/concepts/payment-methods) It typically happens when payment fails for a subscription renewal and it's automatically canceled before the next billing period starts. Payment Recovery, part of Paddle Retain, gets to work to automatically recover failed payments for you. This process is called dunning. By default, dunning takes place over a 30-day period. While other solutions simply retry payments, Payment Recovery is built on top of algorithms that use billions of data points to recover failed payments using optimized retry logic, whitelabeled email outreach, and no-login payment method update forms. With Payment Recovery, you can: - **Retry payments at the best time for success** Automatically retry payments over the dunning window, and turn on Tactical Retries to retry failed payments at optimal times based on the payment method type, customer location, and other details. - **Send optimized payment recovery emails** Email customers when a payment fails, with messaging written by experts and optimized across hundreds of thousands of transactions. - **Push payment recovery notifications** Prompt customers to update expired credit cards when using your web app or landing on your commercial website — securely handled by Paddle.js. - **Send payment recovery messages by SMS** Reach customers by text message to further boost payment recovery rates. - **Choose what happens at the end of the dunning period** When all payment recovery attempts are exhausted, Paddle Retain automatically pauses or cancels subscriptions for you. When a failed payment goes through successfully because of Payment Recovery, a subscription changes from past due to active. The next billing date stays the same, and it renews as normal. ## Customer experience Payment Recovery sends a short, personal, plaintext email to let the customer know that their payment failed. It includes a prominent link to update their payment method. If the customer doesn't take any action, Payment Recovery tries the payment method on file again as part of the dunning process and sends an email each time. ![An email from Retain, with the subject 'Your payment for AeroEdit failed'. Includes a payment recovery link highlighted in yellow.](/src/assets/images/tmp/journey-payment-recovery-email-20250107.svg) As well as payment recovery emails, Payment Recovery notifies customers that they need to update their subscription when they're using your web app or land on your commercial website. In-app notifications reach customers when they're using your product, maximizing the likelihood of payment recovery. ![Payment Recovery notification prompting the user to update their payment method.](/src/assets/images/tmp/journey-payment-recovery-in-app-20250107.svg) If you want, you can turn on SMS notifications for payment recovery. Customers receive a text message with a link they can use to update their details. SMS is a reliable way of reaching customers, with 90% of text messages being read within three minutes of receipt. ![Payment Recovery SMS in an illustration of an iPhone. It includes a link to update payment method.](/src/assets/images/tmp/journey-payment-recovery-sms-20250107.svg) When customers click the link in an email or SMS from Payment Recovery, they're taken to a page on your website to enter updated information. They don't need to log in. Digital wallets like Apple Pay, Google Pay, and PayPal are presented, making payment method update quick and easy. When a failed payment goes through successfully, a subscription changes from past due to active. ![Payment Recovery form that a user sees when they follow any links in Retain notifications. It includes buttons to pay by Apple Pay or Google Pay, PayPal, or other digital wallets. Users can enter details into the card form, too.](/src/assets/images/tmp/journey-payment-recovery-form-20250107.svg) --- # Trials URL: https://developer.paddle.com/concepts/subscriptions/trials Offer trials for subscriptions to lower barriers to entry and increase conversion. Choose whether to require payment details at sign up or not, depending on your business strategy. Trials are a powerful way to let customers try before they buy, giving you a chance to demonstrate your product's value before paying. They also give you a source of high-intent leads, with the option to require payment details to capture more serious leads. Paddle gives you full control over the trial experience and lifecycle, with comprehensive management available using the API, SDKs, and dashboard. Let customers experience value before committing, reducing initial barriers to purchase. Optionally capture payment details to qualify customers and convert more predictably. Extend, activate early, and make changes during trials to match your billing strategy. Paddle Checkout presents customers with compliant trial workflows based on customer jurisdiction. Choose between card-required and cardless trials to match your conversion strategy. Reduce the number of billing-related requests by letting customers try before they buy. ## How it works Trials are a version of your app that customers can access for free for a limited time. They might also include other limitations, like offering a subset of features or a limited number of users. They're typically used for subscriptions, but you might use them for one-time apps too. By showing a potential customer that your product fits their needs, trials create trust in your offering and reduce reluctance to pay, boosting your conversion rate in the process. They also increase customer satisfaction, since customers are more likely to be happy with a product they've tried and found to be a good fit for their needs. ### Trial types Flexible trial types let you optimize conversion across different customer segments. Some customers prefer to try before providing payment details, while others are ready to commit upfront. In Paddle, there are two kinds of trial: Customers must enter payment details at signup, but aren't charged until the trial ends. Qualifying higher-intent leads and enabling automatic conversion. You'll typically see a lower signup rate as customers may be hesitant to enter payment details. Use Paddle Checkout to capture payment details and create a subscription. Customers can start their trial immediately without entering a credit card. Removing the biggest barrier to trial signup, getting more leads in the door. You'll typically see a lower conversion rate since customers haven't committed to paying yet. Use the API to create a transaction for a subscription with a trial period, then use Paddle Checkout to capture payment details later. "Card-required" and "cardless" are commonly used terms for these trial types in the SaaS and app space, but customers aren't limited to cards. They can use digital wallets or local payment options for subscriptions, too. ### Trial lifecycle You have complete control over the trial lifecycle in Paddle. You can: - **Let customers upgrade or downgrade** during trials to match their needs. - **Extend trials** to give customers more time to experience value. - **Activate early** to realize value from customers when they're ready to pay. - **Add one-time charges** during trials and bill them at the end of the trial period. [Webhooks](/webhooks) occur throughout the trial lifecycle, so you can keep your app in sync with Paddle. ## Customer journey Pass items with a trial period to Paddle.js to open a checkout for them. Customers securely enter payment details and start their trial. Paddle Checkout handles compliance for you, making sure that the signup workflow is compliant with card network rules and evolving international legislation around trials. Paddle automatically creates a subscription for the customer when the checkout transaction is completed, [ready for you to provision](/build/subscriptions/provision-access-webhooks). The payment method the customer used is held on file for renewals or subscription changes. When the trial period is getting close to ending, Paddle sends an email to the customer to remind them that the trial is ending. The email includes a link to the customer portal, where customers can manage their payment method. When the trial period ends, Paddle automatically charges the payment method on file and transitions the subscription to `active` status. Use the Paddle API to create a transaction for a customer with a list of items that have a trial period, but don't require payment details. Since the transaction doesn't require payment details, it's automatically completed. Paddle automatically creates a subscription for the customer when the transaction is completed, [ready for you to provision](/build/subscriptions/provision-access-webhooks). There's no payment method on file for the customer at this point. To improve conversion rates, you can email the customer about the trial ending. You should include a link to your payment workflow, where customers can enter their payment method. Before the end of the trial period, customers enter your payment workflow to enter their payment details. Payment workflows are powered by Paddle.js, which handles securely capturing card details or payment using another payment method. When the trial period ends, Paddle automatically charges the payment method on file and transitions the subscription to `active` status. --- # Cancellation Flows URL: https://developer.paddle.com/concepts/retain/cancellation-flows-surveys Reduce churn through customer cancellations by integrating an offboarding journey that presents customers with dynamic salvage attempts, as well as capturing cancellation insights for your team. It costs five times more to acquire new customers than to retain existing ones. Cancellation Flows, part of [Paddle Retain](/concepts/retain), presents customers with a dynamic offboarding experience that's designed to save customers and capture valuable insights for your team. Cancellation Flows is fully customizable, so you can choose the kind of ways you want to rescue customers. Once configured, Cancellation Flows gets to work automatically — no intervention required on your behalf. You can [integrate with Paddle Billing](/build/retain) in a couple of clicks, with no additional scripts required. {% card title="salvage rate" stat="25-30%" statColor="blue" %} Cancellation Flows save over one quarter of customers at risk of churning. {% card title="cut in ARR churn" stat="5%" statColor="green" %} Cancellation surveys and offers reduce churn without any intervention. Rescue customers by offering the right salvage option for the right customer. Rescue potentially lost revenue by offering to pause, discount, switch plans, or contact your support team. Learn why customers want to cancel, so you can make tactical and product changes in the future. Automatically present compliant cancellation workflows based on customer jurisdiction. ![First page of a cancellation flow over the top of the AeroEdit sample app dashboard page. It asks the user what they didn't like, with options for not useful right now, didn't see the value, poor support, missing features, and other.](/src/assets/images/tmp/cancellation-flows-hero-20250108.svg) ## How it works Voluntary churn happens when customers cancel their subscription in your app. Unlike [involuntary churn](/concepts/retain/payment-recovery-dunning), like a problem with a payment method, voluntary churn is active — customers are actively choosing to cancel. Cancellation Flows, part of Paddle Retain, lets you build custom workflows that are designed to save customers from churning at the point of cancellation. Instead of canceling a subscription immediately, Cancellation Flows presents customers with a simple survey that restates your value proposition and makes customers a dynamic offer to stay. As well as helping to retain customers in the moment, Cancellation Flows also captures valuable feedback into why customers want to cancel. You can use these insights to diagnose the root causes of churn and make tactical changes to prevent it. ### Dynamic salvage attempts Cancellation Flows asks customers what they liked about your app and suggests a salvage attempt based on their answer. Depending on the options that a customer chooses, they can: Let customers schedule a meeting with your team using [Calendly](https://calendly.com/), or email your support team. Let customers switch to a different plan, retaining them at a price point that's more affordable. Let customers pause their subscription, so they can come back in the future. If customers don't accept a salvage attempt, you can make a salvage offer — **a temporary discount** to incentivize them to stick around. You can choose what kind of discount you offer, or turn this off if you don't want to offer a discount. ### Automated actions If you use Paddle Billing, you can plug Cancellation Flows into your frontend to handle the entire cancellation workflow. It automatically takes actions on the related subscription in Paddle Billing for you — including scheduling subscriptions to cancel. There's no need to build logic to handle this yourself. | Salvage attempt | Action in Paddle Billing | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Switch plan** | The Price IDs you specified as part of configuration are replaced on the subscription. Changes are prorated and billed immediately—customers are downgraded and receive a credit for any unused time. | | **Pause subscription**| Subscription is scheduled to pause at the start of the next billing period. | | **Offer discount** | A discount is created in Paddle Billing and automatically applied to the subscription. | | **Cancel subscription** | If the subscription is `active`, it's scheduled to cancel at the next billing period. If it's `paused`, it is canceled immediately. | ## Customer experience Retain uses data from thousands of subscription companies to target the right salvage attempt based on the product and user. In general, a cancellation flow has five steps: We ask a customer why they're canceling. This is useful information for your team, helping you diagnose the root cause of churn through cancellations. ![Retain Cancellation Flow modal step 1. It says: We're sorry to see you go. How did we fall short? The options are: not useful right now, didn't see the value, poor support, missing features, other.](/src/assets/images/tmp/cancellation-flows-step-1-20231124.png) We ask customers what they liked about your product. By honing in on the psychological phenomenon of the nostalgia effect, we tee up each customer to be more willing to stick around and accept a salvage attempt. ![Retain Cancellation Flow modal step 2. It says: It wasn't all bad, right? Did we do anything well? The options are: many things - I'll be back, helpful support, easy to use, other.](/src/assets/images/tmp/cancellation-flows-step-2-20231124.png) Based on a customer's responses, we offer them a salvage attempt. We direct them to pause their subscription, switch to a lower cost plan, or contact your team to chat. If you use [Calendly](https://calendly.com/), Retain can show a meeting scheduler as part of the flow so customers can schedule a call in a couple of clicks. ![Retain Cancellation Flow modal step 3. It says: Since you're getting value, want to switch to our Starter plan for $30/month. The options are: yes, no.](/src/assets/images/tmp/cancellation-flows-step-3-20231124.png) If customers don't accept a salvage attempt, we offer them a temporary discount. You can choose which discount you offer, or turn this off if you don't want to offer a discount. ![Retain Cancellation Flow modal step 4. It says: One last thing — how about 20% off your subscription for 2 months? The options are: I'll take it!, No, I'd rather just cancel.](/src/assets/images/tmp/cancellation-flows-step-4-20231124.png) If customers don't accept a salvage offer, we give them a way to provide feedback and cancel their subscription. For Paddle Billing platform users, subscriptions are automatically scheduled to cancel. There's no need to handle this yourself. ![Retain Cancellation Flow modal step 5. It says: We'll miss you. There's a paragraph-size text box to enter feedback, and a button that says confirm cancellation.](/src/assets/images/tmp/cancellation-flows-step-5-20231124.png) In regions that require one-click cancellation, Cancellation Flows automatically includes a link to cancel the subscription on each page of the flow, making sure you're compliant while also gathering insights and helping to reduce churn. --- # Overlay checkout URL: https://developer.paddle.com/concepts/sell/overlay-checkout Overlay checkout is the quickest way to integrate Paddle with your website or app. Turn any element into a checkout link, letting Paddle handle the entire checkout process. Integrate Paddle with a few lines of code using overlay checkout. Customers sign up and pay for subscriptions using an overlay. Using overlay checkout, you can: - Integrate Paddle quickly with a few lines of code. - Choose whether to present a one-page or a multi-page checkout experience. - Present an optimized checkout experience, where customers can sign up and pay without leaving your site. - Create checkout links using [HTML data attributes](/paddle-js/about/html-data-attributes) — perfect for CMS-based sites. - Use [Paddle.js events](/paddle-js/events) to build advanced checkout experiences. ## How it works Overlay checkout works by showing an overlay with all the checkout details. You can turn any element on your page into a button that opens a checkout. The overlay includes all checkout functionality including the items list, totals, and options for changing what's on the checkout. Paddle automatically creates a subscription when a checkout completes, [ready for you to provision](/build/subscriptions/provision-access-webhooks). ## Customer journey You can turn any element on your page into a Paddle Checkout button, [passing items](/build/checkout/pass-update-checkout-items) or an existing transaction to [Paddle.js](/paddle-js) to say what the checkout is for. Overlay checkout asks customers for their email, [country](/concepts/sell/supported-countries-locales), and (in some regions) ZIP or postal code. On the same screen, customers are presented with the card payment form, as well as options to pay with PayPal, Apple Pay, Google Pay, or [other local payment method](/concepts/payment-methods). Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. Customers land on [a success page that you can choose](/build/checkout/handle-success-post-checkout). Paddle automatically creates a subscription for the customer, [ready for you to provision](/build/subscriptions/provision-access-webhooks). The payment method the customer used is held on file for renewals or subscription changes. You can turn any element on your page into a Paddle Checkout button, [passing items](/build/checkout/pass-update-checkout-items) or an existing transaction to [Paddle.js](/paddle-js) to say what the checkout is for. The first screen asks customers for their email, [country](/concepts/sell/supported-countries-locales), and (in some regions) ZIP or postal code. You can [prefill this information](/build/checkout/prefill-checkout-properties) to skip this step entirely. Customers land on a card payment form, or they can choose to pay with PayPal, Apple Pay, Google Pay, or [other local payment method](/concepts/payment-methods). Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. Customers land on [a success page that you can choose](/build/checkout/handle-success-post-checkout). Paddle automatically creates a subscription for the customer, [ready for you to provision](/build/subscriptions/provision-access-webhooks). The payment method the customer used is held on file for renewals or subscription changes. --- # Proration URL: https://developer.paddle.com/concepts/subscriptions/proration Choose how and when customers are charged when they upgrade or downgrade their subscription, or make other changes to items on it. When customers upgrade or downgrade their subscription, or make other changes to items on it, Paddle calculates what they should be charged for based on changes made in the current billing cycle. ## How it works Proration is how Paddle calculates what a customer should be billed for based on changes made in the current billing cycle. For example, if a customer adds a product midway through their billing cycle, you can charge them for just the time they used rather than the entire period. If they replace a product, you can calculate the difference and charge them accurately. You're in control of proration. When changing items on a subscription, you can choose: - To prorate and bill now. - To prorate and bill on the next billing date. - Not to prorate, and to charge the full amount now. - Not to prorate, and to charge the full amount on the next billing date. - Not to bill at all. Paddle's subscription billing engine calculates proration to the minute. ## Proration options You can tell Paddle how you want to prorate when editing a subscription in the Paddle dashboard. When updating items on a subscription using the API, include the `proration_billing_mode` field to tell Paddle how to handle proration. The options are: | Value | Description | |--------------------------------|---------------------------------------------------------------------------------------------------------| | `prorated_immediately` | Prorated amount is calculated now. The customer is billed the prorated amount now. | | `full_immediately` | Prorated amount isn't calculated. The customer is billed the full amount now. | | `prorated_next_billing_period` | Prorated amount is calculated now. The customer is billed the prorated amount on their next renewal. | | `full_next_billing_period` | Prorated amount isn't calculated. The customer is billed for the full amount on their next renewal. | | `do_not_bill` | Prorated amount isn't calculated. The customer isn't billed for the prorated amount or the full amount. | Check `details.line_items.proration` against [a transaction](/api-reference/transactions) to see the rate of proration that Paddle used to calculate a total. --- # Inline checkout URL: https://developer.paddle.com/concepts/sell/branded-integrated-inline-checkout Inline checkout lets you create integrated checkout experiences. You display information about items and totals, letting Paddle take care of capturing customer and payment details. Build integrated checkout experiences with inline checkout. Customers sign up and pay for subscriptions as part of your app or website, making for a seamless experience. Using inline checkout, you can: - Create checkout experiences that are fully integrated with your app or website. - Choose whether to present a one-page or a multi-page checkout experience. - Let Paddle securely capture customer and payment information in an optimized checkout frame. - Display items, totals, and other information from Paddle on your page. - Use [Paddle.js methods](/paddle-js/methods/paddle-initialize) and [events](/paddle-js/events) to build advanced checkout experiences. ![Illustration showing an inline checkout implementation. The Paddle Checkout frame is on the left and is on the payment page. It shows buttons for card, Apple Pay, and PayPal, followed by a card payment form that includes email address, country, and ZIP code. On the right is an items list and totals. The grand total is $3,600 billed monthly and is at the top.](/src/assets/images/tmp/one-page-checkout-20241013.svg) ## How it works Inline checkout works by embedding a frame with Paddle Checkout into your website or app. The checkout frame handles collecting customer information and capturing payment details. Your page displays the items list, totals, and options for changing what's on the checkout. [Paddle.js](/paddle-js) lets your page and the checkout frame interact with each other. Paddle automatically creates a subscription when a checkout completes, [ready for you to provision](/build/subscriptions/provision-access-webhooks). It's important that customers know who they're buying from, what they're buying, and how much they're paying. To build an inline checkout that's compliant and optimized for conversion, your implementation must include: 1. If recurring, how often it recurs and the total to pay on renewal. If a trial, how long the trial lasts. 2. A description of what's being purchased. 3. Transaction totals, including subtotal, total tax, and grand total. Be sure to include the currency too. 4. The full inline checkout frame, including the checkout footer that has information about Paddle, our terms of sale, and our privacy policy. 5. A link to your refund policy, if it differs from the Paddle.com standard refund policy. ## Customer journey You can open inline checkout by [passing items](/build/checkout/pass-update-checkout-items) or an existing transaction. Use [Paddle.js](/paddle-js/events) to show and update on-page information, and [Paddle.js methods](/paddle-js/methods/paddle-checkout-updateitems) to update items based on customer interaction. ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left, and the items list is on the right.](/src/assets/images/tmp/one-page-workflow-step-1-20240726.svg) Inline checkout asks customers for their email, [country](/concepts/sell/supported-countries-locales), and (in some regions) ZIP or postal code. On the same screen, customers are presented with the card payment form, as well as options to pay with PayPal, Apple Pay, Google Pay, or [other local payment method](/concepts/payment-methods). You can [prefill customer details](/build/checkout/prefill-checkout-properties) and present saved payment methods to speed up checkout. ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left and shows fields for email address, country, ZIP code, and card details. There's a blue 'Continue' button at the bottom.](/src/assets/images/tmp/one-page-workflow-step-2-20240726.svg) Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. Customers enter [a success workflow that you can build](/build/checkout/handle-success-post-checkout). ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left and shows a large green checkmark.](/src/assets/images/tmp/one-page-workflow-step-3-20240726.svg) Paddle automatically creates a subscription for the customer, [ready for you to provision](/build/subscriptions/provision-access-webhooks). The payment method the customer used is held on file for renewals or subscription changes. ![Illustration showing a created subscription in Paddle. This is an abstract illustration, rather than a representation of an interface. In the background is a sheet with a user icon, recurring amount, MasterCard symbol, and an active tag. It's meant to represent a subscription page in the Paddle web app. In the foreground is a card that says 'webhook received: new subscription for premium plan.' It's meant to represent a subscription.created webhook event.](/src/assets/images/tmp/inline-workflow-step-5-20240618.svg) You can open inline checkout by [passing items](/build/checkout/pass-update-checkout-items) or an existing transaction. Use [Paddle.js](/paddle-js/events) to show and update on-page information, and [Paddle.js methods](/paddle-js/methods/paddle-checkout-updateitems) to update items based on customer interaction. ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left, and the items list is on the right.](/src/assets/images/tmp/illustration-1pc-eap-before-step-1.svg) Inline checkout asks customers for their email, [country](/concepts/sell/supported-countries-locales), and (in some regions) ZIP or postal code. You can [prefill this information](/build/checkout/prefill-checkout-properties) to skip this step entirely. ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left and shows fields for email address, country, and ZIP code. There is a blue 'Continue' button at the bottom.](/src/assets/images/tmp/inline-workflow-step-2-20230831.svg) Customers land on a card payment form, or they can choose to pay with PayPal, Apple Pay, Google Pay, or [other local payment method](/concepts/payment-methods). ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left and includes two buttons for Apple Pay and PayPal, with the card payment form underneath. There is a button that says 'Pay by card' at the bottom.](/src/assets/images/tmp/two-page-workflow-step-3-20240618.svg) Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. Customers enter [a success workflow that you can build](/build/checkout/handle-success-post-checkout). ![Illustration showing a sample inline checkout implementation. The Paddle Checkout frame is on the left and shows a large green checkmark.](/src/assets/images/tmp/inline-workflow-step-4-20230831.svg) Paddle automatically creates a subscription for the customer, [ready for you to provision](/build/subscriptions/provision-access-webhooks). The payment method the customer used is held on file for renewals or subscription changes. ![Illustration showing a created subscription in Paddle. This is an abstract illustration, rather than a representation of an interface. In the background is a sheet with a user icon, recurring amount, MasterCard symbol, and an active tag. It's meant to represent a subscription page in the Paddle web app. In the foreground is a card that says 'webhook received: new subscription for premium plan.' It's meant to represent a subscription.created webhook event.](/src/assets/images/tmp/inline-workflow-step-5-20240618.svg) --- # Payment methods URL: https://developer.paddle.com/concepts/payment-methods Turn on new payment methods in a couple of clicks. No need to sign up for multiple merchant accounts, partner with different platforms, or write custom code. As a merchant of record, payment methods like Apple Pay and Google Pay are already integrated with Paddle Checkout, ready to enable in a couple of clicks. Plus, we handle payments at scale across thousands of businesses, meaning you benefit from conversion rates you couldn't negotiate alone. Offering digital wallets like PayPal and local payment methods can increase sales. Turn on new payment methods in seconds. No custom code, one centralized platform. We route every payment to the best acquirer for that sale to get the best possible success rate. ## How payment methods work You can turn payment methods on or off in **Paddle > Checkout > Checkout settings**, and they're available to use at checkout automatically. Paddle determines which payment methods to show to customers based on the transaction currency, location of the customer, and device used to purchase. For example: - iDEAL is only presented to customers in the Netherlands for transactions in euro. - Apple Pay is only presented when customers are using a compatible Apple device or browser. - PayPal is only presented when making a purchase in a currency that PayPal supports. Bank transfers are always turned on for invoices, though you can also include a Paddle Checkout link on invoices to let customers pay using another payment method. Customers can check a box to save their payment method when making a purchase using Paddle Checkout. When they come to purchase from you in the future, you can securely present them with their saved payment methods to speed up the checkout process. Some payment method issuers may offer credit installments on eligible one-time purchases, letting customers pay for items over a period of time. This is sometimes called "buy now, pay later" or "pay in 3." For example, PayPal lets customers pay over three or four months. Credit agreements for installment payment plans are between the customer making a purchase and the payment method issuer. They're not provided by Paddle, and Paddle doesn't determine which customers or purchases are eligible for installments. When customers pay using installments, Paddle receives the full amount up front from the payment method issuer, and you receive this as part of your payouts as normal. Because the credit agreement is between the customer and payment method issuer, you're not responsible if a customer fails to make payments as part of their installment plan. ## Supported payment methods ## Compare payment methods --- # Term Optimization URL: https://developer.paddle.com/concepts/retain/term-optimization Increase lifetime value and reduce churn by intelligently identifying customers on a monthly plan who are likely to upgrade to longer term. Retain automatically reaches out by email, in-app, and during payment recovery. Customers on an annual plan have a lifetime value (LTV) that's two to four times higher than customers paying monthly, and they're less likely to churn. Term Optimization, part of [Paddle Retain](/concepts/retain), analyzes customer engagement and other subscription metrics to proactively upgrade customers to annual plans. Term Optimization automatically suggests plan upgrades to the right customers at the right time — no intervention required on your behalf. You can [integrate with Paddle Billing](/build/retain) in a couple of clicks, with no additional scripts required. Customers upgraded to annual plans are two to four times more valuable than monthly. You control the upgrade strategy based on your appetite for churn. Use machine learning to identify customers ready to upgrade using 14 datapoints Offer 1-click upgrade experiences to customers who are likely to accept. Strategically identify customers based on what you want to achieve. Send whitelabeled upgrade offers by email and to users logged into your app. ![Term Optimization dialog on top of the AeroEdit sample app dashboard page. It says 'Get two months free by upgrading to annual plan for $1000/year'.](/src/assets/images/tmp/term-optimization-hero-20250108.svg) ## How it works Customers on annual plans have a higher lifetime value and are much less likely to churn, but most customers aren't ready for a long term commitment when they first sign up. Term Optimization, part of Paddle Retain, uses machine learning to analyze customer engagement and thirteen other data points to identify which customers are primed to upgrade to a longer-term plan. Once customers are identified, Retain automatically prompts customers to upgrade. ### Term Optimization strategy You're in control of how Retain identifies customers who are likely to upgrade. You can choose from three strategies: Target less engaged customers who are more likely to churn—lock them in on a longer plan. Turning potential churn risks into long term paying customers. Your churn rate might increase if dormant customers subsequently cancel their subscription. Identify a balance—reach both likely churners and highly engaged customers. Identifying a good balance of customers who are at risk of churn and also engaged. You may be leaving cash on the table or missing a chance to lock in customers at risk of churning. Focus on highly engaged power users who are most willing to upgrade and pay in advance. Getting an upfront boost in revenue from annual payments. Your monthly recurring revenue (MRR) may dip as customers move to longer-term plans that cost less. ### Automated actions If you use Paddle Billing, Term Optimization automatically takes actions on the related subscription in Paddle Billing for you. There's no need to build logic to handle this yourself. | **User action** | **Action in Paddle Billing** | |----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Upgrade subscription | Monthly plan price replaced with corresponding annual plan price on customer subscription. Change applied using `prorated_immediately`, meaning that proration is calculated and the customer is billed right away. | ## Customer experience You can choose how Retain reaches out to customers who are identified as likely to upgrade to a plan with a longer term length: Term Optimization uses machine learning to identify customers who are primed to upgrade, then sends them a short, personal plaintext email to suggest switching to a plan with a longer term length. It includes a prominent link to a one-click upgrade form. If the customer doesn't take any action, Term Optimization sends a follow-up email a few days later. ![An email from Retain, with the subject 'Your AeroEdit Pro subscription.' Includes a link to upgrade in bold.](/src/assets/images/tmp/journey-term-op-email-20250110.svg) Term Optimization notifies customers that they're eligible to upgrade when they're using your web app. In-app notifications reach customers when they're using your product, maximizing the likelihood of uptake. ![Payment Recovery notification prompting the user to upgrade their plan.](/src/assets/images/tmp/journey-term-op-notification-20250110.svg) Retain can prompt customers to upgrade in [payment recovery emails and on the payment recovery form](/build/retain/configure-payment-recovery-dunning). As customers are already engaged with billing during payment recovery, this approach feels natural and non-intrusive. Customers are more receptive to cost-saving opportunities at this point. ![Payment Recovery form that a user sees when they follow any links in Retain notifications. Above the digital wallet buttons is a checkbox that says 'Save $200 by switching to an annual plan for $1000/year.'](/src/assets/images/tmp/journey-term-op-payment-recovery-20250110.svg) --- # Hosted checkout for mobile apps URL: https://developer.paddle.com/concepts/sell/hosted-checkout-mobile-apps Hosted checkout is the quickest way to let mobile app users make purchases outside your app. Present users with a secure, optimized checkout experience that redirects back to your app. Quickly add a mobile purchase flow using hosted checkout. Customers tap a button in your app to open a checkout, then they're redirected to your app when they complete their purchase. Hosted checkout is fully hosted by Paddle, meaning you can add it in minutes — no need to build or host a checkout on your own infrastructure. Go directly to your users and save on App Store fees, while boosting customer LTV. Collect email and demographic data, and communicate directly with customers. Users are automatically redirected back to your app after completing their purchase. Offer Apple Pay, Google Pay, PayPal, and other payment methods without any additional setup. Paddle handles chargebacks, fights fraud, and prevents card attacks for you. Customers can self-serve using the customer portal, and Paddle handles order inquiries. ![Illustration showing a mobile checkout flow. On the left, a payment screen with product details, price, and payment options. On the right, a confirmation screen with a success message and return to app button.](/src/assets/images/tmp/hosted-checkout-hero-20250508.svg) ## How it works With recent developments in legislation around the App Store, you can link users in the to an external checkout for purchases in iOS apps. Use hosted checkout to add a secure, optimized payment experience to your app. Instead of processing payments through the App Store, you can direct users to a Paddle-hosted checkout page, [personalized with your company or app name in the URL](/build/checkout/custom-subdomains). When users complete a purchase, they're seamlessly redirected back to your app where you can handle fulfillment. As a [merchant of record](https://mor.paddle.com/), Paddle comes with all the benefits of the App Store — including global payments, tax compliance, fraud prevention, and buyer support — with lower fees and more control over the purchase flow. Typical payment service providers (PSPs) aren't set up to handle these tasks, leaving app developers to take on responsibility for compliance, global tax remittance, and handling refunds and chargebacks. | | Paddle | App Store | PSPs | |----------------------------------------------|:--------------------:|:------------------------:|:------------------------------------------------------------:| | Payouts | Every 30 days | Up to 60 days | From 7 days | | Fees | As low as 3-6% | 30% | Average ~3% | | Relationship with customers | Direct | Mediated by Apple | Direct | | Payment handling and optimization | | | | | Global tax compliance and remittance | | | | | Fraud prevention and chargeback protection | | | Sometimes, for a fee | | Buyer support for billing issues | | | | | Subscription billing | | | Sometimes, for a fee | | Integration with RevenueCat for entitlements | | | Sometimes | | Churn prevention and dunning workflows | | | | | Control over the checkout experience | | | | | Global and local payment methods | | | Sometimes, for a fee | | Flexible discount and pricing options | | | | ## Customer experience You can create a hosted checkout link in Paddle, then present users with a button to open a new browser window to pay using Paddle Checkout. For enhanced conversion, you can personalize hosted checkout links so they include your company or app name. ![Illustration of a mobile app screen with a prominent 'Buy Now' button for purchasing premium features.](/src/assets/images/tmp/hosted-checkout-step-1-20250506.svg) Paddle Checkout presents customers with a secure, optimized payment experience. It automatically calculates taxes and displays product information in a compliant way. Customers can pay using card, Apple Pay, Google Pay, PayPal, or other local payment methods. ![Illustration of the Paddle checkout screen showing product details, price summary, and payment method options including Apple Pay.](/src/assets/images/tmp/hosted-checkout-step-2-20250506.svg) Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. When payment is complete, customers are automatically redirected to a screen in your app that you specify. ![Illustration of a payment confirmation screen with a success message and 'Return to App' button.](/src/assets/images/tmp/hosted-checkout-step-3-20250506.svg) When users return to your app, you can immediately unlock the purchased content or features. Paddle integrates with [RevenueCat for entitlements](https://www.paddle.com/revenuecat-integration-beta), or you can build a custom workflow using [webhooks](/webhooks). ![Illustration of the app showing a success screen with unlocked premium features and a 'Get Started' button.](/src/assets/images/tmp/hosted-checkout-step-4-20250506.svg) --- # Paddle Retain URL: https://developer.paddle.com/concepts/retain Integrate Paddle Retain in a couple of clicks to reduce churn automatically with interventions that use billions of datapoints to win back customers before they're gone for good. Paddle Retain combines world-class subscription expertise with algorithms that use billions of datapoints to recover failed payments, reduce churn, and automatically increase customer lifetime value (LTV). Paddle Billing is fully integrated with Retain, automatically working to minimize churn and maximize revenue — no intervention required on your behalf. {% card title="recovery rate for failed payments" stat="50%+" statColor="blue" %} Paddle Retain has the highest payment recovery rate on the market. {% card title="reduction in plan cancellations" stat="30%" statColor="green" %} Cancellation surveys and offers reduce churn without any intervention. Customers upgraded to annual plans are two to four times more valuable than monthly. Beat churn and boost revenue with a suite of best-in-class retention tools. Use Paddle Billing? You're ready to use Retain. Configure and see results right away. Reach customers by email, in-app, and SMS in 15+ local languages. ## How it works ![Payment Recovery email with highlighted recovery link](/src/assets/images/tmp/feature-payment-recovery-20250110.svg) Fully automated dunning, powered by algorithms that use billions of datapoints to reduce churn. Works on customers with past due payments, helping recover failed payments automatically. Highest payment recovery rates in the market—backed by expert algorithms. Works out of the box with Paddle Billing or your existing billing platform. [**Learn more about Payment Recovery →**](/concepts/retain/payment-recovery-dunning) ![Cancellation flow offering retention offer](/src/assets/images/tmp/feature-cancellation-flows-20250110.svg) Personalized offboarding workflows designed to deflect cancellations and gather valuable product feedback. Engages cancelling customers with offers and surveys to retain more revenue. The only globally compliant cancellation workflow—no code required. Hands-off cancellation and retention logic, fully integrated with Paddle Billing or your platform. [**Learn more about Cancellation Flows →**](/concepts/retain/cancellation-flows-surveys) ![Term Optimization upgrade prompt](/src/assets/images/tmp/feature-term-op-20250110.svg) Seamless one-click upgrades for engaged customers identified as ready to move to a longer-term plan. Turns monthly customers into long-term, higher-value subscribers. Reaches the right customers by email, in-app, and when updating card details—maximizing uptake. Upgrades, proration, and subscription change logic handled for you; works with Paddle Billing or your platform. [**Learn more about Term Optimization →**](/concepts/retain/term-optimization) ## Integration process Retain plugs into your existing billing system and works automatically in the background to recover failed payments, reduce churn, and increase customer LTV. 1. **Configure your account** Sign up for Paddle Retain, choose which interventions you want to turn on, and set them up to suit your business. Our team of experts are on hand to help throughout the process. 2. **Add JavaScript snippet** As part of configuration, Retain guides you through adding a small JavaScript snippet to your app and website. If you use Paddle Billing, Retain integrates with Paddle.js, so you don't need to include any additional scripts. 3. **Retain gets to work for you** That's it. Paddle Retain runs around the clock to automatically minimize churn and maximize revenue. --- # ProfitWell Metrics URL: https://developer.paddle.com/concepts/retain/metrics Access comprehensive subscription analytics to track growth metrics, identify revenue opportunities, and benchmark against 30,000+ companies out-of-the-box. ProfitWell Metrics helps you make smart decisions that grow your business. Understand your results individually and against peers with powerful insights, presented in easy-to-use dashboards that track your MRR, churn, customer cohorts, and segment effectiveness. ProfitWell Metrics is available to use anytime through **Subscription Metrics** on the [live Paddle Billing dashboard](https://vendors.paddle.com) — no additional setup required. Track your key metrics with instant dashboards, reports, insights, and unit economics calculations. Assess engagement and customer health data to reactivate customers and increase retention. Build custom segments and compare trends over time for sharper insights. Compare your business against industry peers with exclusive benchmark data. Share via Slack, HubSpot, Intercom, and Salesforce to drive alignment and action. See subscriptions, one-time charges, refunds, and fees together to strengthen revenue and reduce leakage. ![A detailed view of a ProfitWell Metrics dashboard showing key business performance indicators. The interface displays various metrics including Current MRR ($43.94M with 9% growth), Current Customers (387,487), and Active Customers (72%). The main graph shows monthly growth trends reaching $2,712,958, with comparison metrics between March and February 2025. The dashboard includes additional sections for New Revenue ($3,803,692) and Churn (-$1,692,175), along with a customer activity feed showing recent subscriptions, churns, and downgrades.](/src/assets/images/tmp/subscription-metrics-hero-20250324.avif) ## How it works Building a thriving subscription business requires more than just a great product and marketing strategy. Long-term growth requires continuous optimization based on customer and revenue metrics that most businesses either struggle to track effectively or don't have the tools, knowledge, or time to understand. ProfitWell Metrics provides out-of-the-box analytics, translating your complex subscription data into actionable insights so you can make informed decisions with confidence and ease. No need to spend hours calculating which customers need attention, where growth opportunities exist, and which important metrics need improving. ## Metrics Key indicators of your business health are automatically calculated and presented as datapoints throughout dashboards. Use these to understand customer costs, track lifecycle performance, optimize pricing strategies, and forecast revenue accurately. - The monthly revenue from all active subscriptions. - It's crucial for tracking growth and forecasting revenue. - A higher MRR is usually better, except for annual plans where MRR is lower but the retention rate is higher. - Paddle converts all subscription periods (monthly, quarterly, annual) into monthly values for easier comparison. - When customers increase or decrease the value of their subscription. - These metrics are vital for monitoring revenue retention. - A higher upgrade rate, and lower downgrade rate, is better. - Paddle still counts a customer switching to an annual plan as an upgrade, even if there may be less monthly revenue but more total value. - When a customer stops using your subscription product. - The higher the churn, the more money you must spend to acquire new customers and recoup the lost business. - A lower churn rate is better. - Paddle counts a customer as "churned" when their paid period ends instead of the moment they click cancel. - When previously churned customers sign up again. - Understanding reactivation helps identify what brings customers back and can reduce acquisition costs. - A higher reactivation rate is better. - Paddle counts returning customers separately from brand new customers. - Your total MRR divided by total customer count. - This metric helps inform pricing strategy and indicates the value customers place on your product. - A higher ARPU is better. - Paddle calculates this automatically to help benchmark against similar businesses. - How much revenue a customer provides over the time they're subscribed to your product. - It lets you know your growth potential and helps you plan your spending on acquisition. - A higher LTV is better. - Paddle calculates this based on how long customers typically stay and how much they spend on average. ## Dashboards and reports ProfitWell Metrics generates world-class dashboards and reports of your data every 3-6 hours so you always have the most up-to-date insights to take action on. Use these to see what's happening with your business, understand why trends are occurring, and know exactly which levers to pull to improve performance. ### MRR overview A high-level snapshot of your subscription data to understand your business health at a glance. - Provides a holistic view of your company's standing on one page. - Displays essential metrics, including MRR, customer numbers, ARPU, and LTV. - Helps identify revenue trends and key subscription movements. ![A dashboard displaying Monthly Recurring Revenue (MRR) trends. There is a bar chart that tracks MRR growth with segments for new revenue, reactivations, upgrades, existing MRR, downgrades, and churn. A line graph overlays the bar chart, showing MRR increasing over time. At the top, key metrics include $653,050 in reactivations, $348,567 in upgrades, $41.23M in existing MRR, $310,392 in downgrades, and $2,115,025 in churn. A tooltip for February 2025 breaks down revenue changes, showing a total MRR of $41,226,850. ](/src/assets/images/tmp/subscription-metrics-mrr-20250324.avif) **See a demo of the MRR overview dashboard** ### Customers and engagement A detailed view of customer activity and revenue to understand performance on a granular level. - Track customer counts by segment and identify the behavior and signals of those individuals. - Combine with the Engagement view to understand where revenue is at risk from inactivity. - Helps prevent churn and gain insight into which features drive long-term customer loyalty. ![An overview of customer and engagement metrics. One box shows a customer's email, total amount paid, MRR contribution, plan type, payment cadence, and a recent downgrade of $25. Another section displays MRR from active customers at 81% in the past 30 days, with $28,591 at risk due to inactivity. A bar chart shows customer activity by plan, breaking down active and inactive customers for Basic, Pro, and Enterprise plans, with inactivity rates of 33%, 41%, and 16%, respectively.](/src/assets/images/tmp/subscription-metrics-customer-engagement-20250324.avif) **See a demo of the customers dashboard** ### Cohorts report Track MRR retention to understand how your business is evolving over time. - Displays how much MRR has carried over into following months on a progressive basis. - Track cohorts by signup month to measure long term retention and plan stickiness. - Helps identify patterns in customer behavior and churn trends. ![A cohort analysis report tracking monthly recurring revenue (MRR) retention over time. The table shows MRR values from December 2023 to February 2025, with each row representing a cohort and its percentage of revenue retained month-over-month. The heatmap uses colors to highlight retention trends, with higher retention in green and lower in red. A text box prompts the system to show new and retained amounts for June 2024 which are highlighted on the heatmap.](/src/assets/images/tmp/subscription-metrics-cohort-20250324.avif) **See a demo of the cohorts report** ### Segment comparison Break down data into meaningful groups to drive insight and action. - Compares metrics between preset segmented groups from auto-ingested data. - Create custom segments using custom traits like account owner or campaign. - Helps discover which customers deliver most value and uncover growth opportunities. ![A dashboard for comparing customer segments. A section labeled 'Share of Revenue' indicates that 38.3% of revenue over the past year came from the Pro plan. Another section tracks retention rate, showing a 2.7% higher rate than the overall average. A right-hand panel allows filtering customers by activity level, industry, MRR quartile, plan type, and location. Location displays estimated MRR contributions from the United States (~$300K MRR), United Kingdom (~$200K MRR), and Argentina (~$200K MRR) as options to select. A 'View Segment' button allows people to select and see the segment data.](/src/assets/images/tmp/subscription-metrics-segment-20250324.avif) **See a demo of the segment comparison insights** ### By plan breakdown Compare up to five plans based on multiple metrics to optimize your pricing. - Compare up to five plans to see which are delivering better results. - Select different metrics such as MRR, total churn rate, and LTV to visualize plan effectiveness. - Helps optimize pricing and packaging strategies based on actual usage data. ![A dashboard displaying subscription revenue metrics by plan. A section on the right shows "Percentage of total MRR by Plan," with a breakdown of MRR contributions: 38% from the Pro plan, 30% from Pro - Annual, 20% from Basic, and 11% from Basic - Annual. Below, a "Delinquent Churn Rate" line chart tracks monthly churn trends across different plans, with varying shades representing different subscription tiers. On the left, a list of comparison options asks how revenue, LTV, and retention rate vary by different factors.](/src/assets/images/tmp/subscription-metrics-by-plan-20250324.avif) **See a demo of the by plan breakdown report** ### Cash flow Track money flowing in and out of your business to strengthen revenue sources and reduce expenses. - Registers subscriptions, one-time charges, refunds, and platform fees in one unified view. - Filter by time to review financial data and export to share with your team. - Helps identify opportunities to improve your revenue models and reduce cash leakage. ![A financial dashboard showing cash flow metrics for March 2025. The total net cash is displayed as $22,883,522. Revenue sources include monthly subscriptions ($22.88M), yearly subscriptions ($19.73M), and one-time payments ($2.24M), while deductions include fees (-$1.34M) and refunds (-$897K). A cumulative cash flow graph trends upwards, with a tooltip for February 2025 breaking down revenue components, showing net cash at $39.16M.](/src/assets/images/tmp/subscription-metrics-cash-flow-20250324.avif) **See a demo of the cash flow reports** --- # Upsell checkout URL: https://developer.paddle.com/concepts/sell/upsell-checkout Upsell checkouts are designed to convert customers returning for immediate subsequent purchases. Present customers with a streamlined checkout experience using previous transaction details. Upsell checkouts are optimized to minimize friction and maximize conversion rates by reusing details and consent acknowledgments from the preceding transaction. Open an upsell checkout immediately after a transaction completes to encourage the purchase of additional items and expand your revenue. Cut cognitive load for customers by reducing the checkout footprint, helping them purchase quickly. Boost your conversion with post-purchase flows designed for upsells, compliant out-of-the-box. Saved card, PayPal, Apple Pay, and Google Pay details are reused for a one-click payment experience. ## How it works Upsell checkouts are typically used as part of a post-purchase flow, encouraging customers to buy additional items or add-ons after their initial transaction. They work by taking customer, consent, and [payment method details](/build/checkout/saved-payment-methods) from a previous transaction to show a streamlined checkout experience to customers. A single button is shown so customers can purchase any additional items in one-click. You must use one-page, [inline checkouts](/concepts/sell/branded-integrated-inline-checkout) to present upsell checkouts. ## Customer journey When making their initial purchase, customers enter their personal, billing, and business details. They may also check a box to consent to [saving their payment method](/build/checkout/saved-payment-methods) for future purchases when completing payment. After the initial transaction is complete, you open a new inline, one-page checkout for the upsell. Customers are presented with a one-click payment flow if the customer is authenticated, has saved payment methods, and the upsell occurs within the [same session](/build/checkout/upsell-checkout). Their last used payment method is automatically selected. Customers can optionally click the "No thanks" button to skip the upsell and continue their journey. You [decide what happens next](/build/checkout/upsell-checkout). For example, you can show a popup window to confirm the customer wants to skip the upsell, or open a new checkout with an improved upsell offer. Customers can click to purchase instead. Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. Customers enter [a success workflow that you can build](/build/checkout/handle-success-post-checkout), and you could choose to open another upsell checkout as part of your flow. --- # Express checkout URL: https://developer.paddle.com/concepts/sell/express-checkout Express checkout is optimized for mobile purchases, prioritizing Apple Pay for a frictionless, one-click payment experience using Apple Wallet. Express checkout is a checkout variant designed for mobile that surfaces Apple Pay first, and uses Apple Wallet and IP address geolocation to capture customer information automatically — eliminating the need for customers to manually enter their email, country, and postal code. Open an express checkout to give mobile customers the fastest possible path to purchase. Surface Apple Pay first on compatible devices, making it the easiest option for customers to select. Use Apple Wallet and geolocation to capture customer information automatically. If Apple Pay isn't available, customers can pay another way using other payment methods. ## How it works Express checkout is optimized for mobile conversion, prioritizing [Apple Pay](/concepts/payment-methods/apple-pay), presenting it first to customers on compatible devices. Behind the scenes, Paddle uses the customer's IP address to estimate the correct tax and pricing before the checkout loads, and uses Apple Wallet to capture the customer's email address. This means customers go straight to confirmation without filling out a form. If Apple Pay is unavailable on the customer's device, the checkout automatically falls back to alternative payment methods. You can disable the fallback to show only Apple Pay by setting `showNonExpressPaymentMethods` to `false` in checkout settings. You can integrate using [Paddle.js](/paddle-js) or [hosted checkout](/concepts/sell/hosted-checkout-mobile-apps). You must [verify your domain for Apple Pay](/concepts/payment-methods/apple-pay#verify-your-domain-for-apple-pay) to offer the one-click experience in express checkout. ## Customer journey When a customer opens an express checkout on a compatible device, Apple Pay is presented first and prominently. Customers can also choose to pay another way if they prefer a different payment method. You can disable the option to pay another way if you want, so only Apple Pay is available. Express checkout uses Apple Wallet details to prefill customer information and estimates the correct tax and pricing using the customer's IP address, meaning customers don't need to enter address details manually. Customers select a card and confirm their purchase as normal. Paddle routes every payment to the best acquirer for that sale to get the best possible chance of success. Customers enter [a success workflow that you can build](/build/checkout/handle-success-post-checkout). --- # Invoices URL: https://developer.paddle.com/concepts/sell/sales-assisted-invoice Create and send invoices for subscriptions for payment by bank transfer or using Paddle Checkout. No need to duplicate data. Win larger value deals and enterprise business with invoices in Paddle. Your sales team can create and send invoices, giving customers the option to pay by [bank transfer](/concepts/payment-methods/wire-transfer) as well as card, PayPal, Apple Pay, Google Pay, and [other local payment methods](/concepts/payment-methods). Paddle automatically creates a subscription when you issue an invoice, [ready for you to provision](/build/subscriptions/provision-access-webhooks). Create and send invoices for subscriptions from one centralized system. Paddle automatically creates invoices for subscription changes and renewals. Transition subscriptions between automatic collection or billing by invoice. Accept bank transfers and offline payments, as well as card, PayPal, and other payment methods. As your merchant of record, Paddle receives payment and handles reconciliation, marking invoices as paid. Create and issue credit notes to credit or refund all or part of an invoice after you've issued it. ![Illustration of an invoice from Paddle](/src/assets/images/tmp/invoices-hero-20240814.svg) ## How it works Invoices are typically part of a sales-led growth billing motion. They're fully integrated with the rest of Paddle, so they share a product catalog and customers. You can [create a draft invoice in Paddle](/build/invoices/create-issue-invoices), or build an integration to automatically create draft invoices when you create a quotation in your CRM or ERP system. As you scope out requirements with a customer, you can make changes to your invoice and its items. Issue an invoice when you're happy with it to mark it as finalized. At this point, it gets an invoice number and becomes a financial record. Paddle automatically creates a subscription, [ready for you to provision](/build/subscriptions/provision-access-webhooks). When a customer pays your invoice, Paddle handles reconciliation and marks it as paid. ## Go live with invoices 1. **Sign up for Paddle** Create your Paddle [sandbox](/sdks/sandbox) and live accounts, then complete initial setup. 2. **Create product catalog** Create [products and prices](/build/products/create-products-prices), including [country-specific prices](/build/products/offer-localized-pricing) and [discounts](/build/products/offer-discounts-promotions-coupons). 3. **Handle provisioning and subscription lifecycle events** Paddle automatically creates a subscription when you issue an invoice. Use webhooks or the event stream to [provision your app](/build/subscriptions/provision-access-webhooks), and handle other subscription lifecycle events like upgrades, downgrades, and cancellations. 4. **Start transacting** You're ready to start creating and issuing invoices with Paddle. ## Hybrid billing With Paddle, you can conquer upmarket and downmarket — all from the same integrated platform. - **Empower product-led growth** Offer entry-level plans and let customers sign up for your app using Paddle Checkout. - **Easy upsells** Present customers who pay by invoice with a checkout for smaller charges, like adding users or modules mid-cycle. - **Scale with customers** Meet the needs of growing customers by transitioning customers who pay using Paddle Checkout to billing by invoice. --- # Customer portal URL: https://developer.paddle.com/concepts/sell/customer-portal Let customers manage their own subscriptions, payments, and account information using the customer portal. No engineering effort required. Easily add core subscription management, billing information, and payment history to your app with the customer portal — eliminating the need to build your own billing management screens from scratch. The customer portal is included by default with Paddle. You don't need to do anything to turn it on or set it up. Give customers a central place to take key account actions. Customers can update payment details instantly. Available in over 17 languages, across 200+ markets. ## How it works Businesses that offer digital products, especially SaaS businesses, need a way to handle billing queries. It's common for customers to want to see past payments, download invoices, and update payment details for subscriptions. For compliance, you must offer a way for customers to cancel their subscriptions, too. You can use the customer portal to give customers a central place to manage purchases made from your Paddle account. It's a secure, Paddle-hosted app that lets customers: - See past payments and download invoices. - Get information about their subscriptions, and manage them. - Update their [payment details](/concepts/payment-methods). The customer portal is fully hosted by Paddle, meaning you can integrate in minutes. You can [link to the customer portal](/concepts/sell/customer-portal) to add key billing workflows to your app, rather than building workflows from scratch yourself. Emails sent by Paddle automatically include links to the customer portal for your Paddle account to let customers update their payment method and cancel their subscription. ## Customer experience Customers sign in to your portal by entering their email address, then clicking on a secure link that Paddle sends to them. You can generate authenticated links to the customer portal for a customer in your app so they don't have to sign in again. Customers can see a full itemized list of their previous transactions, called "payments" in the portal. It's one click to download a PDF invoice for their records here, too. When customers have saved payment methods for one-time purchases or subscriptions, they can see and update their payment details — including expired payment methods. Customers can see and manage their subscriptions. Canceled subscriptions are presented too, so customers have a full record of their purchase history with you. When customers cancel their subscription, portal launches a cancellation flow. [Cancellation Flows](/concepts/retain/cancellation-flows-surveys) present customers with a simple survey that restates your value proposition and makes customers a dynamic offer to stay. --- # Checkout recovery URL: https://developer.paddle.com/concepts/sell/checkout-recovery Increase your conversions and recapture lost revenue with automated follow-up emails to customers who abandon their checkouts. With a 10-30% recovery rate, Paddle's checkout recovery emails work out-of-the-box to continuously recover revenue that would otherwise be permanently lost. Transform abandoned purchases into completed sales, boosting your bottom line without lifting a finger. Checkout recovery emails are automatically turned on if you signed up for Paddle after May 2, 2025 — no additional setup or integration required. {% card title="checkouts left" stat="27%" statColor="green" %} Over $320 million in sales are lost monthly. Recover those transactions to leave no money on the table. Strategic timing ensures follow-up emails arrive when customers are most likely to return. Preoptimized emails are automatically sent with no technical setup and at no extra cost to you. ![Illustration of a standard checkout recovery email without a discount. It shows a message that says 'Looks like you left something behind' with text to incentivize customers to return, details on what they were purchasing, the price of the item, and a button to reopen the checkout.](/src/assets/images/tmp/checkout-recovery-standard-email-20250407.svg) ![Illustration of a checkout recovery email with a discount. It shows a message that says 'Looks like you left something behind' with a 10% discount offered at the top, text to incentivize customers to return, details on what they were purchasing, the price of the item with the value of the discount applied, and a button to reopen the checkout with the discount applied.](/src/assets/images/tmp/checkout-recovery-discount-email-20250407.svg) ## How it works Checkout recovery works by automatically emailing customers 60 minutes after they abandon a checkout. You can optionally offer [discounts](/build/checkout/checkout-recovery) on abandoned transactions. Customers are more likely to return and make a purchase when you offer a discount. Our testing shows that 10-20% discounts are the most effective. The content of the email is optimized to maximize conversion rates and includes a link that reopens the abandoned checkout with any discount automatically applied. No further integration is needed — Paddle does the heavy lifting for you. ## Customer journey You can set up either an [overlay checkout](/concepts/sell/self-serve-checkout) or use an embedded [inline checkout](/concepts/sell/branded-integrated-inline-checkout) to let customers checkout and purchase products with Paddle. ![Illustration showing a checkout implementation. The Paddle Checkout frame is on the left, and the items list is on the right. A transaction has been created for the value of a $3600 as the customer has opened the checkout.](/src/assets/images/tmp/checkout-recovery-journey-1-20250407.svg) When a customer begins checkout, Paddle creates a draft transaction. Early in the checkout flow, customers can enter their email address. This links the customer to the transaction and enables Paddle to send them recovery emails. ![Illustration showing a checkout implementation with the email field highlighted. The field is filled in with the customers email address.](/src/assets/images/tmp/checkout-recovery-journey-2-20250407.avif) If a customer leaves the checkout without completing their purchase, Paddle considers the checkout and transaction abandoned. Paddle automatically tracks abandonment in the background. ![Illustration of the top right corner of the checkout screen. It shows the three Mac dots in a browser window with the cursor pointed over the red dot, indicating that the customer is leaving the checkout.](/src/assets/images/tmp/checkout-recovery-journey-3-20250407.svg) After 60 minutes without purchase, Paddle sends an email prompting the customer to return and complete the transaction. The email includes details of your configured discount and a link to reopen the checkout. ![Illustration of an email client. It shows an email to the same email address left in earlier steps. The email has a message that says 'Looks like you left something behind' and details of a discount to reincitivize return. There's a note saing that 60 minutes have passed since the transaction was created.](/src/assets/images/tmp/checkout-recovery-journey-4-20250407.svg) When the customer clicks the link in the recovery email, the checkout reopens with the discount automatically applied. The customer can then complete their purchase. This recovers the transaction and converts a previously lost sale. ![Illustration of a simplified checkout implementation. It shows a tick symbol to indicate that the checkout is completed, and a note saying that the transaction was successfully recovered.](/src/assets/images/tmp/checkout-recovery-journey-5-20250407.svg) --- # Supported currencies URL: https://developer.paddle.com/concepts/sell/supported-currencies Sell products in a range of currencies — ready-to-use, with no extra engineering effort required. Paddle has built-in support for multi-currency for both payments and payouts, letting customers purchase in their local currency and letting you get paid in yours. Paddle automatically calculates taxes and handles sales tax liability for all countries and currencies. Offering local currencies can increase sales and reduce FX fees for customers. Paddle has built-in support for popular currencies for payment and payouts. All supported currencies are ready-to-use as part of the Paddle API and Paddle.js. ## How it works ### Payments - Price items in over 30 different currencies with no additional setup needed. - You can charge customers in a currency that's not their local currency. For example, you can offer pricing in US Dollar to customers where the dollar is popular but not the official currency. - You can use price overrides to offer country-specific prices, regardless of the currency. - Paddle takes care of taxes and handles sales tax liability for all currencies and countries. ### Payouts - When customers make a purchase, you can hold your balance in US Dollar (`USD`), euro (`EUR`), Pound Sterling (`GBP`), Australian Dollar (`AUD`), and Canadian Dollar (`CAD`). - You can get paid by Paddle in a currency that's different to your balance currency. - You can query the API to get a breakdown of earnings and fees for a transaction. ## List of supported currencies --- # Supported countries URL: https://developer.paddle.com/concepts/sell/supported-countries-locales Go global and sell in over 200 countries and territories, fully tax compliant, with no extra engineering effort. With Paddle, you can unlock new revenue by selling in over 200 countries and territories across the world — no additional setup needed. Paddle automatically calculates taxes and handles sales tax liability for all countries and currencies. Expand into popular and emerging markets, while minimizing your risk. As a merchant of record, Paddle calculates, collects, and remits taxes for you. All supported countries are ready-to-use as part of the Paddle API and Paddle.js. ## How it works ### Localize prices One size doesn't fit all when it comes to currencies. Paddle lets you [set country-specific prices](/build/products/offer-localized-pricing), rather than pricing for currencies that might apply across markets. You can also automatically convert prices into local currencies at checkout, making customers more likely to purchase. ### Optimized checkout To make buying as frictionless as possible, [Paddle Checkout](/concepts/sell/self-serve-checkout) doesn't require a full address, only asking customers for their country and (in some countries) their ZIP/postal code or region. Region information and ZIP/postal codes are only required in some countries for tax calculation, fraud prevention, and banking compliance purposes. Checkout only asks for this in countries where required. ### Unsupported countries are blocked Payments from some countries are blocked in compliance with international sanctions regulations, payments platforms policies, and anti-money laundering regulations. Paddle automatically blocks transactions from unsupported countries. You don't need to do anything. Supported countries work with no additional setup required. ## List of supported countries --- # Alipay URL: https://developer.paddle.com/concepts/payment-methods/alipay Let customers pay with Alipay. No configuration required. Alipay is a digital wallet for the Chinese market, used by over a billion people. It's part of an app that lets users in book taxis, order food, access healthcare, and plan vacations. Customers authenticate and confirm payments using the Alipay app, which requires proof of identification and a bank account, making it a quick and secure way to pay. You don't need an Alipay account or entity in China to add Alipay as a payment option with Paddle. Alipay is a popular payment method for customers in China. Alipay has over 1.3bn users across 80m merchants as of June 2020. {% card title="market share" stat="54.5%" statColor="purple" %} Alipay is the leading online payment provider in China as of December 2020. The maximum transaction total for subscription renewals and charges is 1600 `CNY`. If a temporary discount is applied, the prediscount total can't be more than this total. If the transaction total goes over 1600 `CNY`, payment fails and customers can update their payment details as part of dunning. ## How it works Turn on Alipay in a couple of clicks in your Paddle dashboard. Paddle automatically presents Alipay as a payment method for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open the Alipay app. Customer confirms purchase on their phone using the Alipay app. If successful, Paddle Checkout shows a success screen or enters your success workflow. ## Accept Alipay To keep the Paddle platform safe for everyone, Alipay requires additional approval from Paddle. It's only available for some kinds of product. ### Request approval for Alipay You can request approval for Alipay if: - You offer products in `CNY` (Chinese Yuan). - Subscription renewals for a customer don't typically total more than 1,600 `CNY`. - You agree that products aren't on the Alipay [list of prohibited and restricted products](https://global.alipay.com/docs/ac/Platform/le18gg#FVEQ7). To apply, go to [Request approval for Alipay](https://paddlecom.typeform.com/to/OViGqvW1). ### Turn on Alipay Once approved, turn on Alipay. 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check **Alipay**. 3. Click **Save** to apply. ## Test Alipay You can test Alipay using your Paddle [sandbox account](/sdks/sandbox). 1. Turn on Alipay for your Paddle account. 2. Open a checkout for an item that's priced in `CNY` (Chinese Yuan). 3. Pick China as the country on the first screen of checkout, then enter an email. 4. Select Alipay as the payment method and follow the prompts to complete purchase. Paddle Checkout only presents Alipay as a payment method for items priced in Chinese Yuan, where the customer address is China. --- # Apple Pay URL: https://developer.paddle.com/concepts/payment-methods/apple-pay Let customers pay with Apple Pay. No configuration required. Apple Pay is a digital wallet integrated with iPhones, iPads, Macs, and other Apple devices. Customers authenticate using facial recognition, fingerprint, or using their Apple Watch, which makes it a simple and secure experience. More than 500 million people use Apple Pay, making it one of the most popular digital wallets. You don't need to sign up for an Apple Developer account to add Apple Pay as a payment option with Paddle. ## How it works Turn on Apple Pay in a couple of clicks in your Paddle dashboard. Paddle automatically presents Apple Pay as a payment method for customers paying in a supported country on a supported platform. Customers choose a saved payment card in their Apple Wallet that they'd like to use to pay. They can check address and other details before confirming. Apple Pay prompts the customer to authenticate. They can authenticate using Touch ID or Face ID on the device they're making a purchase on. If the device doesn't support Touch ID or Face ID, they're prompted to use Face ID or Touch ID on a nearby iPhone or iPad, or confirm on Apple Watch. If successful, Paddle Checkout shows a success screen or enters your success workflow. ## Accept Apple Pay 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check **Apple Pay**. 3. Click **Save** to apply. ## Verify your domain for Apple Pay Extend your Apple Pay integration by verifying your domain for Apple Pay. This lets you launch the Apple Pay modal where customers choose a card directly from your checkout. It's supported on iOS 17, iPadOS 17, and Safari 17+ on macOS. You don't need to verify your domain to offer Apple Pay, but we recommend it. If you use Apple Pay on an unverified domain (or an older version of Safari), Paddle opens an unobtrusive popup from a Paddle.com domain that launches the Apple Pay modal. The popup automatically closes when customers choose a card and confirm. You can verify your domain for Apple Pay in the Paddle dashboard. 1. Make sure you've added your domain to **Paddle > Checkout > Website approval > Domain approval**, and that [your domain is approved by Paddle](https://www.paddle.com/help/start/account-verification/what-is-domain-verification). For [sandbox accounts](/sdks/sandbox), domains are approved automatically once added. 2. Download [the Apple Pay domain association file](/.well-known/apple-developer-merchantid-domain-association). 3. Upload the file to your web server at `/.well-known/apple-developer-merchantid-domain-association`. For example, if your website is `example.com`, make the file available at `https://example.com/.well-known/apple-developer-merchantid-domain-association`. 4. Go to **Paddle > Checkout > Website approval > Apple Pay verification**, then click **Verify** next to your domain. If your domain is approved, you'll see a green **Approved** badge right away. Apple requests your domain association file as part of verification. Make sure your domains aren't behind a proxy or redirect, and they're publicly available to the [Apple Pay servers](https://developer.apple.com/documentation/apple_pay_on_the_web/setting_up_your_server#3179116). If you launch checkouts from multiple domains or subdomains, repeat the verification process for each one. ## Test Apple Pay You'll need an iPad, iPhone, or Mac to test Apple Pay. Use Safari on Mac. You can test Apple Pay using your Paddle [sandbox account](/sdks/sandbox). 1. Turn on Apple Pay for your Paddle account. 2. [Sign up for an Apple ID](https://support.apple.com/apple-id), if you don't already have one. 3. [Add at least one card to Apple Pay](https://support.apple.com/HT204506). When testing with Apple Pay, you use real card details but your card isn't charged. 4. Open a checkout and choose Apple Pay as the payment method. 5. Choose a card and complete the test purchase. Your checkout must be served over `https` for Apple Pay to appear. --- # Bancontact URL: https://developer.paddle.com/concepts/payment-methods/bancontact Let customers pay with Bancontact. No configuration required. Bancontact is the most popular payment method in , with over 2 billion payments processed each year. All major banks in Belgium support Bancontact. Customers pay using either a Bancontact-enabled card, or using a mobile app that's linked to their bank account. You don't need to set up a bank account in Belgium to add Bancontact as a payment option with Paddle. ## How it works Turn on Bancontact in a couple of clicks in your Paddle dashboard. Paddle automatically presents Bancontact as a payment method for customers paying in a supported country and currency. Customers enter the full name against their Bancontact account. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone or enter their Bancontact card details. If they're on mobile, they're prompted to open their banking app. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll enter their card PIN or authenticate using on-device biometrics. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # Bank transfer URL: https://developer.paddle.com/concepts/payment-methods/wire-transfer Let customers pay by bank transfer. No configuration required. Bank transfers, sometimes called wire transfers, are a safe way for customers to send money from their bank account to pay for things. It's common for businesses to pay invoices by bank transfer, especially for larger value deals that might be higher than credit or debit card limits. You don't need to open local bank accounts or spend time reconciling payments. Customers pay Paddle, then Paddle handles invoice reconciliation and payout. ## How it works When you issue an invoice using the Paddle dashboard or the API, Paddle sends it to the customer and any business contacts. It has an invoice number and is a legal document at this point. Paddle automatically generates unique bank transfer details for each customer and includes them on the invoice. Customers make a payment using these details by the due date on the invoice. When Paddle receives payment, we automatically mark the invoice as paid and reconcile for you. ## Accept bank transfers Only invoices (manually-collected transactions) can be paid by bank transfer. You don't need to do anything to enable bank transfers. They're always enabled for Paddle accounts. --- # BLIK URL: https://developer.paddle.com/concepts/payment-methods/blik Let customers pay with BLIK. No configuration required. BLIK is a mobile payment system that has transformed the Polish market. Created by the largest banks in , it's integrated directly into customers' mobile banking apps, making it an incredibly popular and trusted way to pay. Customers authorize payments by generating a unique 6-digit code in their banking app and entering it at checkout. They then confirm the transaction in their app, providing a fast and highly secure payment flow without needing to share card details. You don't need a BLIK account or an entity in Poland to add BLIK as a payment option with Paddle. BLIK is the most popular payment method for customers in Poland. BLIK has over 17 million active users and growing, as of Q2 2024. {% card title="e-commerce share" stat="~70%" statColor="purple" %} BLIK is the main player in online retail in Poland, processing over a billion transactions. ## How it works Turn on BLIK in a couple of clicks in your Paddle dashboard. Paddle automatically presents BLIK as a payment method for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open their banking app. They can also copy the BLIK key and paste it into their banking app manually. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll authenticate using on-device biometrics. Payment is authorized, and Paddle Checkout shows a screen that confirms the checkout is completed, but the payment is pending capture. After authorization, payment is captured. This typically happens immediately after authorization, but can take up to ten minutes. The transaction is marked as completed in Paddle, and the customer receives a copy of their invoice by email. ## Accept BLIK 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check **BLIK**. 3. Click **Save** to apply. ## Test BLIK You can test BLIK using your Paddle [sandbox account](/sdks/sandbox). 1. Turn on BLIK for your Paddle account. 2. Open a checkout for a one-time item that's priced in `PLN` (Polish złoty). 3. Pick Poland as the country on the first screen of checkout, then enter an email. 4. Select BLIK as the payment method and follow the prompts to complete purchase. Use the following test codes: | Description | Code | | --------------------------------------------- | ------------------------------------------------------- | | Positive authorization with token registration | 200201 | | Negative authorization | 500500 | | Positive authorization without token registration | 777xxx | | Authorization code expired | 700701 | | Authorization code canceled | 700702 | | Authorization code already used | 700703 | Paddle Checkout only presents BLIK as a payment method for one-time items priced in Polish złoty, where the customer address is in Poland. --- # Google Pay URL: https://developer.paddle.com/concepts/payment-methods/google-pay Let customers pay with Google Pay. No configuration required. Google Pay is a digital wallet linked to people's Google Accounts. It works with Android-powered devices and Google Chrome on all platforms, except on iPhones and iPads. With over 150 million active users, it's popular with Android users, especially in the and Europe. You don't need to sign up for Google Pay for Business to add Google Pay as a payment option with Paddle. ## How it works Turn on Google Pay in a couple of clicks in your Paddle dashboard. Paddle automatically presents Google Pay as a payment method for customers paying in a supported country on a supported platform. Customers choose a saved payment card in their Google Wallet that they'd like to use to pay. They can check address and other details before confirming. Google Pay prompts the customer to confirm their purchase. If they're using Google Chrome or an Android-powered device, they may be asked to authenticate using on-device biometrics. If successful, Paddle Checkout shows a success screen or enters your success workflow. ## Accept Google Pay 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check **Google Pay**. 3. Click **Save** to apply. ## Test Google Pay You'll need to [get Google Chrome](https://www.google.com/chrome/) or use an Android-powered device to test Google Pay. You can test Google Pay using your Paddle [sandbox account](/sdks/sandbox). 1. Turn on Google Pay for your Paddle account. 2. [Sign up for a Google Account](https://www.google.com/account/), if you don't already have one. 3. [Add at least one card to Google Pay](https://payments.google.com/). You can use test cards when working with Google Pay, but you must have at least one valid card in your wallet. 4. Join [the Google Group for test cards](https://groups.google.com/g/googlepay-test-mode-stub-data). This automatically adds test cards to your wallet when Google Pay detects a test environment, like Paddle sandbox. 5. Open a checkout and choose Google Pay as the payment method. 6. Choose one of the Google Pay test cards and complete purchase. If you're using Google Chrome on desktop, make sure the **Allow sites to check if you have payment methods** option is turned on in [Chrome settings](https://support.google.com/chrome/answer/142893). This is turned on by default. --- # iDEAL | Wero URL: https://developer.paddle.com/concepts/payment-methods/ideal Let customers pay with iDEAL | Wero. No configuration required. iDEAL is an interbank system used in the . All major banks in the Netherlands support iDEAL, and it's the most popular online payment method in the country. iDEAL is becoming Wero, a new European payment method that's built on iDEAL. It's backed by major banks across the European Union. It's already live in , , , and . Customers in the Netherlands see iDEAL | Wero as an option to pay. They authenticate and confirm payments using their banking app, making it quick and secure. You don't need to set up a bank account in the European Union to add iDEAL | Wero as a payment option with Paddle. ## How it works Turn on iDEAL | Wero in a couple of clicks in your Paddle dashboard. Paddle automatically presents iDEAL | Wero as a payment method for customers paying in a supported country and currency. Customers enter their name and choose their bank. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open their banking app. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll enter their card PIN or authenticate using on-device biometrics. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # KakaoPay URL: https://developer.paddle.com/concepts/payment-methods/kakaopay Let customers pay with KakaoPay. No configuration required. Digital wallets are becoming the preferred way to pay in , and KakaoPay is the most popular among young people in the country. It's part of the popular KakaoTalk instant messaging app. Customers authenticate and confirm payments using the KakaoPay app, which requires proof of identification and a bank account, making it a quick and secure way to pay. You don't need to set up a bank account in South Korea to add KakaoPay as a payment option with Paddle. KakaoPay is a popular payment method for customers in South Korea and Koreans living abroad. KakaoPay had 32.3m annual active users of their services, as of Q4 2023. KakaoPay is the most popular payment method for gen Z and millennials in South Korea. ## How it works Turn on KakaoPay in a couple of clicks in your Paddle dashboard. Paddle automatically presents the option to pay using KakaoPay for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open KakaoPay. Customer confirms purchase on their phone using their KakaoPay app. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # Korean local cards URL: https://developer.paddle.com/concepts/payment-methods/korean-cards Let customers pay with Korean local cards. No configuration required. Cards are the most popular way to pay in , with many different card issuers and most cards running through nine local payment networks rather than Visa, Mastercard, or other international payment networks. Rather than entering card details manually, customers typically pay by selecting their card brand and authenticating using their banking app. You don't need to set up a bank account in South Korea to add Korean local cards as a payment option with Paddle. Local cards are a popular payment method for customers in South Korea. The transaction value for local debit cards totals ₩212tn a year, as of 2021. South Korea has one of the highest card penetration rates in the world. - BC Card - Citi Card - Hana Card - Hyundai Card - Jeju Card - Jeonbuk Card - Kakao Bank Card - Kbank Card - KDB Bank Card - Kookmin Card - Kwangju Card - Lotte Card - mg Card - Naver Pay Card - NH Card - Post Card - Samsung Card - Savings bank - Shinhan Card - Shinhyup Card - Suhyup Card - Woori Card ## How it works Turn on Korean local cards in a couple of clicks in your Paddle dashboard. Paddle automatically presents the option to pay using Korean local cards for customers paying in a supported country and currency. Paddle presents a list of local cards for the customer to choose from. This is typical for card payments in South Korea, and understood by customers in the region. Payment method issuers have different ways of confirming a payment — for example, by scanning a QR code, entering a one-time passcode, or tapping on a push notification. As part of confirmation, customers are typically prompted to open their banking app to review the transaction. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll enter their card PIN or authenticate using on-device biometrics. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # MB WAY URL: https://developer.paddle.com/concepts/payment-methods/mb-way Let customers pay with MB WAY. No configuration required. MB WAY is 's leading digital wallet, deeply integrated into the national banking system. It lets customers link their bank cards to their mobile number, creating a simple and highly secure way to pay. Customers make payments by entering their phone number at checkout and confirming the transaction with a PIN or biometrics in their mobile banking app. This strong authentication and connection to trusted banks has made it the preferred payment method for millions. You don't need a Portuguese bank account or entity in Portugal to add MB WAY as a payment option with Paddle. MB WAY is a key payment method for unlocking growth in Portugal. Supported by 28 local banks, MB WAY has over 4.5 million users. {% card title="e-commerce share" stat="45%" statColor="purple" %} MB WAY is the market leader in Portugal, processing almost half of e-commerce transactions. ## How it works Turn on MB WAY in a couple of clicks in your Paddle dashboard. Paddle automatically presents MB WAY as a payment method for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open their banking app. They can also copy the MB WAY key and paste it into their banking app manually. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll authenticate using on-device biometrics. Payment is authorized, and Paddle Checkout shows a screen that confirms the checkout is completed, but the payment is pending capture. After authorization, payment is captured. This typically happens immediately after authorization, but can take up to ten minutes. The transaction is marked as completed in Paddle, and the customer receives a copy of their invoice by email. --- # Naver Pay URL: https://developer.paddle.com/concepts/payment-methods/naver-pay Let customers pay with Naver Pay. No configuration required. Naver Pay is a digital wallet initially introduced as a payment option for Naver Shopping, one of the most popular online stores in . It's now a widely accepted payment method in the region. Customers can earn Naver Pay points for spending, which encourages people to choose Naver Pay at checkout — especially those in their twenties and thirties. You don't need to set up a bank account in South Korea to add Naver Pay as a payment option with Paddle. Naver Pay is a popular payment method for customers in South Korea and Koreans living abroad. Naver Pay has a high average transaction value, processing a total of ₩60tn in 2023. {% card title="prefer Naver Pay" stat="74%" statColor="purple" %} Naver Pay is used by three-quarters of consumers in South Korea. ## How it works Turn on Naver Pay in a couple of clicks in your Paddle dashboard. Paddle automatically presents the option to pay using Naver Pay for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open Naver Pay. Customer confirms purchase on their phone using their Naver Pay app. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # Payco URL: https://developer.paddle.com/concepts/payment-methods/payco Let customers pay with Payco. No configuration required. Payco was created by Nonghyup Bank, one of the biggest banks in , as a way for customers to send money, pay bills, get insurance, and access investment products. Partnerships with platforms like YouTube and T-money, used to pay for public transportation in Seoul and other major cities, mean it's grown into a popular way to pay online. You don't need to set up a bank account in South Korea to add Payco as a payment option with Paddle. Payco is a popular payment method for customers in South Korea and Koreans living abroad. Payco processes transactions totalling ₩7.4tn on average each year. Payco has 12m registered users of their app as of Q1 2022, with 4.1m monthly active users. ## How it works Turn on Payco in a couple of clicks in your Paddle dashboard. Paddle automatically presents the option to pay using Payco for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open Payco. Customer confirms purchase on their phone using their Payco app. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # PayPal URL: https://developer.paddle.com/concepts/payment-methods/paypal Let customers pay with PayPal. No configuration required. PayPal is a digital wallet that lets customers pay for things online without having to enter card or bank details every time. More than 400 million people use PayPal. It's especially popular in the , , and the . You don't need to set up a PayPal account to add PayPal as a payment option with Paddle ## How it works Turn on PayPal in a couple of clicks in your Paddle dashboard. Paddle automatically presents PayPal as a payment method for customers paying in a supported country and currency. Paddle Checkout presents a PayPal login screen. Customers sign in to their account, or they can create a new one. Once logged in, customers choose a saved payment method that they'd like to use to pay. PayPal supports cards, bank accounts, and other payment methods. Customers can choose to pay with their [PayPal balance](https://www.paypal.com/us/cshelp/article/how-can-i-use-a-balance-with-paypal-help335), but must also select a backup payment method. If successful, Paddle Checkout shows a success screen or enters your success workflow. ## Accept PayPal 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check **PayPal**. 3. Click **Save** to apply. ## Save PayPal at checkout You can let customers securely save their PayPal for future purchases. 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, Check **Allow buyers to opt in to save their payment methods for future purchases**. 3. Click **Save** to apply. ## Test PayPal You can test PayPal using your Paddle [sandbox account](/sdks/sandbox). 1. Turn on PayPal for your Paddle account. 2. Sign up for [a PayPal sandbox account](https://developer.paypal.com/tools/sandbox/accounts/#link-createandmanagesandboxaccounts). This is separate from your Paddle sandbox account. 3. Open a checkout and choose PayPal as the payment method. 4. Log in to your PayPal sandbox account and complete purchase. --- # Pix URL: https://developer.paddle.com/concepts/payment-methods/pix Let customers pay with Pix. No configuration required. Pix is a digital payment method created by the Central Bank of Brazil (BCB) to offer a fast, low cost, and widely accessible way for people in to make payments. Since its introduction in 2020, it has become Brazil's preferred way to pay and an expectation for Brazilian consumers when paying online. You don't need to set up a bank account in Brazil or sign up for a Pix merchant account to add Pix as a payment option with Paddle. Transactions are capped at R$250,000. Pix is the preferred instant payment method for customers across Brazil. Pix processed nearly 42 billion transactions in 2023, totaling over R$17 trillion. Over 90% of the adult population in Brazil actively use Pix. ## How it works Turn on Pix in a couple of clicks in your Paddle dashboard. Paddle automatically presents Pix as a payment method for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open their banking app. They can also copy the Pix key and paste it into their banking app manually. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll authenticate using on-device biometrics. Payment is authorized, and Paddle Checkout shows a screen that confirms the checkout is completed, but the payment is pending capture. After authorization, payment is captured. This typically happens immediately after authorization, but can take up to ten minutes. The transaction is marked as completed in Paddle, and the customer receives a copy of their invoice by email. --- # Samsung Pay URL: https://developer.paddle.com/concepts/payment-methods/samsung-pay Let customers pay with Samsung Pay. No configuration required. Samsung Pay comes preinstalled on Samsung smartphones, the most popular brand of phone in . It's a popular way to pay in-store and increasingly used for online payments, too. Customers authenticate and confirm payments using the Samsung Pay app, making it a quick and secure way to pay. You don't need to set up a bank account in South Korea to add Samsung Pay as a payment option with Paddle. Samsung Pay is a popular payment method for customers in South Korea. Samsung Pay was used by three in 10 people for online payments in 2023. {% card title="phone market share" stat="84%" statColor="purple" %} Samsung Pay comes preinstalled on over 80% of phones sold in South Korea. ## How it works Turn on Samsung Pay in a couple of clicks in your Paddle dashboard. Paddle automatically presents the option to pay using Samsung Pay for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open Samsung Pay. Customer confirms purchase on their phone using their Samsung Pay app. If successful, Paddle Checkout shows a success screen or enters your success workflow. --- # UPI International URL: https://developer.paddle.com/concepts/payment-methods/upi Let customers pay with UPI International. No configuration required. UPI International (Unified Payments Interface) is a realtime system that allows users to link multiple bank accounts to a single app for easy digital payments. It's the main way to pay online in , processing the majority of transactions in the country. Beyond India, UPI International is becoming an emerging player in the future of cross-border payments, [integrating with banks](https://www.npci.org.in/what-we-do/upi-global/upi-global-acceptance/live-members) in the , , , and . You don't need to set up a bank account in India or sign up for a UPI merchant account to add UPI International as a payment option with Paddle. More than 80% of digital payments in India go through UPI. Supported by over 600 banks, UPI has over 350 million active users. In FY 2025, UPI processed over 185 billion transactions worth ₹261 trillion. - AU small Finance Bank - Axis Bank Ltd - Bank of Baroda - Bank of India - Bank of Maharashtra - Bharat Co-operative Bank - Canara Bank - Central Bank of India - City Union Bank - CSB Bank Ltd - DBS Bank Ltd - DCB Bank - Dhanlaxmi Bank Ltd - Equitas Small Finance Bank - HDFC Bank Ltd - ICICI Bank - Indian Bank - IndusInd Bank - Janata Sahakari Bank Ltd. Pune - Karur Vysya Bank - Kotak Mahindra Bank - NKGSB Bank - Punjab and Sind Bank - Punjab National Bank - RBL Bank - Saraswat Bank - South Indian Bank - State Bank of India - Suryoday Small Finance Bank Ltd. - Tamilnadu Mercantile Bank - TJSB Sahakari Bank Ltd - UCO Bank - Union Bank of India - Yes Bank Ltd For a full list, see [UPI global acceptance](https://www.npci.org.in/what-we-do/upi-global/upi-global-acceptance/live-members). Customers must turn on UPI International in their UPI app. If not enabled, most apps should prompt customers to turn on as part of the checkout flow. - BHIM - Aditya Birla - Canara Bank (ai1) - DBS Bank Ltd (digi bank by DBS India) - Dhanlaxmi Bank (BHIM DLB UPI) - Federal Bank (FedMobile) - Freecharge - GPay - Groww - ICICI Bank (iMobile) - Indian Bank (IndOASIS) - Indian Overseas Bank (BHIM IOB UPI) - IndusInd Bank (BHIM Indus Pay) - Jio (Jio Finance App) - Karnataka Bank (BHIM KBL UPI) - Karur Vysya Bank (BHIM KVB Upay) - Paytm - Phonepe - Punjab National Bank (BHIM PNB) - South Indian Bank (SIB Mirror+) - State Bank of India (BHIM SBIPay) - TATA Neu - UCO Bank (BHIM UCO) - WhatsApp Pay For a full list, see [UPI global acceptance](https://www.npci.org.in/what-we-do/upi-global/upi-global-acceptance/live-members). ## How it works Turn on UPI in a couple of clicks in your Paddle dashboard. Paddle automatically presents UPI as a payment method for customers paying on desktop in a supported country and currency. Customers are prompted to scan a QR code with their phone. They can also copy the UPI key and paste it into their banking app manually. If the customer doesn't have UPI International turned on, most banking apps prompt them to enable it as part of the payment flow. If not prompted, they'll need to turn on UPI International in their banking app then try to pay again. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll authenticate using on-device biometrics. Payment is authorized, and Paddle Checkout shows a screen that confirms the checkout is completed, but the payment is pending capture. After authorization, payment is captured. This typically happens immediately after authorization, but can take up to ten minutes. The transaction is marked as completed in Paddle, and the customer receives a copy of their invoice by email. ## Accept UPI International ### Set up prices in USD If you've turned on automatic currency conversion, Paddle automatically localizes prices in India to `INR`. As UPI International is only available for prices in `USD`, you need to [create a country-price override for India](/build/products/offer-localized-pricing) to present customers in India with a price in `USD`. 1. Go to **Paddle > Catalog > Products**, then find the product you want to present UPI for. 2. Find the price you want to create a price override for in the list, then click the **…** action menu and choose **Edit** from the menu. 3. Under the country-specific prices section, add an override for India where the currency is `USD`. 4. Click **Save** when you're done. When you do this, your prices in India will be presented in `USD` — including for other payment methods like card or PayPal. To learn more, see [Localize prices](/build/products/offer-localized-pricing). ### Request approval for UPI International To keep the Paddle platform safe for everyone, the early access program for UPI International requires additional approval from Paddle. It's not available for any products related to online gaming. You can request to join the early access program for UPI International if you agree that you don't offer products related to online gaming using UPI International. To apply, contact [sellers@paddle.com](mailto:sellers@paddle.com). ### Turn on UPI International Once approved, turn on UPI International. 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check **UPI International**. 3. Click **Save** to apply. ## Test UPI International You can test UPI International using your Paddle sandbox account. 1. Turn on UPI International for your Paddle account. 2. Open a checkout for a one-time item that's priced in `USD` (US Dollar). 3. Pick India as the country, then enter an email and PIN code. 4. Select UPI as the payment method. 5. Click **Pay with UPI** and follow the prompts to complete purchase. Paddle Checkout only presents UPI International as a payment method for one-time items priced in `USD`, where the customer address is in India. --- # WeChat Pay URL: https://developer.paddle.com/concepts/payment-methods/wechat Let customers pay with WeChat Pay. No configuration required. WeChat Pay is a leading digital wallet for the Chinese market, built into the WeChat super-app. WeChat is an essential part of daily life in , used for messaging, social media, and accessing millions of services and online stores via Mini Programs. Customers authenticate and confirm payments securely within the WeChat app using a password or biometrics, often by scanning a QR code. This makes it a fast, familiar, and trusted way to pay. You don't need a WeChat Pay account or an entity in China to add WeChat Pay as a payment option with Paddle. WeChat Pay is a vital payment method for businesses selling to customers in China. WeChat Pay has 935 million users as of 2023, making it one of the world's largest payment platforms. {% card title="market share" stat="~37%" statColor="purple" %} WeChat Pay holds a significant share of China's massive mobile payments market. ## How it works Turn on WeChat Pay in a couple of clicks in your Paddle dashboard. Paddle automatically presents WeChat Pay as a payment method for customers paying in a supported country and currency. If the customer is checking out on a desktop, they're prompted to scan a QR code with their phone. If they're on mobile, they're prompted to open their banking app. They can also copy the WeChat Pay key and paste it into their banking app manually. Customer confirms purchase on their phone using their banking app. The exact process varies depending on the banking app the customer uses, but typically they'll authenticate using on-device biometrics. Payment is authorized, and Paddle Checkout shows a screen that confirms the checkout is completed, but the payment is pending capture. After authorization, payment is captured. This typically happens immediately after authorization, but can take up to ten minutes. The transaction is marked as completed in Paddle, and the customer receives a copy of their invoice by email. --- # Manage client-side tokens URL: https://developer.paddle.com/paddle-js/about/client-side-tokens Create and revoke client-side tokens used to initialize Paddle.js in your frontend. Client-side tokens let you interact with the Paddle platform in frontend code, like webpages or mobile apps. - They're intended only for client-side use. - They're limited to opening checkouts, previewing prices, and previewing transactions. - They're safe to publish and expose in your code. They're required for working with Paddle.js. Looking to integrate Paddle in your backend? Use the [Paddle API](/api-reference) or SDKs with [API keys](/api-reference/about/authentication) instead. ## Use an AI agent ## How it works When you [initialize Paddle.js](/paddle-js/about/include-paddlejs), you must include a client-side token. Paddle uses your client-side token to identify your account and verify that you have permission to perform the requested action. ### Sandbox vs live workspaces Paddle has separate [sandbox](/sdks/sandbox) and live workspaces, each with their own set of client-side tokens. This separation helps you safely test your integration without affecting real customer data or transactions. #### Sandbox client-side tokens - Use these tokens as you build and test your integration. - They only work in the sandbox environment where no real money is involved. - Sandbox client-side tokens contain `test_`. - Create a sandbox client-side token in the [sandbox dashboard](https://sandbox-vendors.paddle.com/authentication-v2). #### Live client-side tokens - Use these tokens only when you're ready to process real transactions in your production app. - They only work in the live environment where real money is involved. - Live client-side tokens contain `live_`. - Create a live client-side token in the [live dashboard](https://vendors.paddle.com/authentication-v2). ### Format Client-side tokens always follow a specific format: - Always start with `test_` or `live_` to show the [environment](#sandbox-vs-live-workspaces) they're used for. - Contains a random string of 27 characters in length after the environment prefix. ```bash ^(test|live)_[a-zA-Z0-9]{27}$ ``` ## Create a client-side token 1. Go to **Paddle > Developer Tools > Authentication**. 2. Click the **Client-side tokens** tab. 3. Click New client-side token. 4. Enter a name and description for the client-side token. 5. Click Save when you're done. 6. Click the button next to the client-side token you want to use, then choose Copy. ![Illustration of the new token form in Paddle. It shows the name and description fields. There's a button that says Save.](/src/assets/images/paddlejs/client-side-token-create-20250407.svg) You can use the `/client-tokens` endpoint to create a client-side token. - Build a request that includes the `name` of your token to easily identify it. - You can optionally provide a `description` with more information on the token's purpose or usage. If successful, Paddle responds with a copy of the new client-side token entity. The returned `token` field is the client-side token you can use for [authentication when intializing Paddle.js](/paddle-js/about/include-paddlejs). ```json { "name": "Pricing page integration", "description": "Used to display prices and open checkout within our pricing page on our marketing domain." } ``` ```json { "data": { "id": "ctkn_01ghbkd0frb9k95cnhwd1bxpvk", "token": "live_7d279f61a3499fed520f7cd8c08", "name": "Pricing page integration", "description": "Used to display prices and open checkout on our pricing page on our marketing domain.", "status": "active", "created_at": "2025-06-26T14:36:14.695Z", "updated_at": "2025-06-26T14:36:14.695Z", "revoked_at": null }, "meta": { "request_id": "1681f87f-9c36-4557-a1da-bbb622afa0cc" } } ``` ## Revoke a client-side token Client-side tokens are safe to expose publicly in your frontend code. However, you may still want to revoke a token so it can no longer be used to authenticate Paddle.js. Revoking a token is permanent. Check that your client-side token isn't used in production before revoking it to prevent disruption to customers. 1. Go to **Paddle > Developer Tools > Authentication**. 2. Click the **Client-side tokens** tab. 3. Click the button next to the token you want to revoke, then choose Revoke. 4. Confirm you want to revoke the client-side token by filling in the confirmation box. ![Illustration of the authentication screen in Paddle. It shows the client-side tokens tab. There's a list of client-side tokens with the three dots icon. The menu for the first token is open, showing options to revoke.](/src/assets/images/paddlejs/client-side-token-revoke-20250407.svg) You can revoke a client-side token using the `/client-tokens/{client_token_id}` endpoint. Build a request that includes a `status` field with a value of `revoked`. If successful, Paddle responds with a copy of the revoked client-side token entity. It can no longer be used to authenticate. ```json { "status": "revoked" } ``` ```json { "data": { "id": "ctkn_01ghbkd0frb9k95cnhwd1bxpvk", "token": "live_7d279f61a3499fed520f7cd8c08", "name": "Pricing page integration", "description": "Used to display prices and open checkout within our pricing page on our marketing domain.", "status": "revoked", "created_at": "2025-06-26T14:36:14.695Z", "updated_at": "2025-07-03T15:14:12.435Z", "revoked_at": "2025-07-03T15:14:12.435Z" }, "meta": { "request_id": "1681f87f-9c36-4557-a1da-bbb622afa0cc" } } ``` --- # Paddle.Initialize() URL: https://developer.paddle.com/paddle-js/methods/paddle-initialize Use to initialize Paddle.js and Retain. This is required. Use `Paddle.Initialize()` to initialize Paddle.js and set default checkout settings. This is required when working with pricing pages, checkouts, and Retain workflows. You can use `Paddle.Initialize()` to: - Authenticate with your Paddle account - Integrate with Retain - Pass settings that apply to all checkouts opened on a page - Create event callbacks You must call `Paddle.Initialize()` and pass a client-side token to use Paddle Checkout. You can [create and manage client-side tokens](/paddle-js/about/client-side-tokens) in **Paddle > Developer tools > Authentication**. You can only call `Paddle.Initialize()` once on a page. You'll get an error if you try to call it more than once. Use [`Paddle.Update()`](/paddle-js/methods/paddle-update) to update `pwCustomer` or pass an updated `eventCallback`. You can pass settings for opened checkouts using either [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) or `Paddle.Initialize()`. Settings passed to `Paddle.Initialize()` are [default settings](/build/checkout/set-up-checkout-default-settings), which means they apply to all checkouts opened on a page. Paddle.js [emits events for key actions](/paddle-js/events) as a customer moves through checkout. You can pass an `eventCallback` to `Paddle.Initialize()` to call a function for every Paddle.js checkout event. This is typically used as part of an [inline checkout integration](/build/checkout/build-branded-inline-checkout) for updating on-page elements, like items lists or breadcrumbs. ## Paddle Retain [Paddle.js integrates with Retain](/concepts/retain), so you don't have to include a separate Retain script in your app or website. Client-side tokens for live accounts authenticate with both Paddle Billing and Paddle Retain, so there's no need to pass a separate key for Retain. To use Retain, pass `pwCustomer` for logged-in customers. You can update `pwCustomer` after initialization using [`Paddle.Update()`](/paddle-js/methods/paddle-update). ## Parameters ## Examples This example passes a client-side token to Paddle.js. This is required. You can create and manage client-side tokens in **Paddle > Developer tools > Authentication**. To learn more, see [Include and initialize Paddle.js](/paddle-js/about/include-paddlejs) For logged-in users, you should pass `pwCustomer`. This example passes the Paddle ID for a customer entity in Paddle to Retain. Where you don't know the Paddle ID for a customer, you can pass an empty object to `pwCustomer`. To learn more, see [Initialize Paddle.js with Retain](/paddle-js/about/include-paddlejs) This example sets default checkout settings for all checkouts opened on a page. It includes common settings for `inline` checkouts. To learn more, see [Pass checkout settings](/build/checkout/set-up-checkout-default-settings) This example sets default checkout settings for all checkouts opened on a page. It includes the required settings to open a one-page inline checkout. This example sets default checkout settings for all checkouts opened on a page. It includes common settings for `overlay` checkouts. To learn more, see [Paddle.js events](/paddle-js/events) This example logs events emitted by Paddle.js to console. To learn more, see [Paddle.js events](/paddle-js/events) This example uses a switch statement to log some text to console based on events emitted by Paddle.js. To learn more, see [Paddle.js events](/paddle-js/events) --- # Quickstart URL: https://developer.paddle.com/paddle-js/about Open your first Paddle Checkout in five minutes. Paddle.js is a JavaScript library that you include in your frontend to open checkouts, render localized prices, and integrate with Paddle Retain. This quickstart walks you through including Paddle.js, initializing it with a client-side token, and opening an overlay checkout. It takes about five minutes. ## Before you begin You need a Paddle account. You can sign up for free: - [**Sandbox account**](https://sandbox-login.paddle.com/signup) — for testing. No real money is involved. - [**Live account**](https://login.paddle.com/signup) — for production use. Requires approval before you can process real transactions. We recommend starting in the sandbox environment. You'll also need: - At least one [product and price](/build/products/create-products-prices) in your Paddle account. Note the price ID, starting with `pri_`. - A domain approved in **Paddle > Checkout > Website approval**, so you can test from a local file. On sandbox accounts, you can use `localhost` as your domain. ## Get a client-side token [Client-side tokens](/paddle-js/about/client-side-tokens) authenticate Paddle.js with your Paddle account. They're safe to expose in frontend code. 1. Go to **Paddle > Developer tools > Authentication**. 2. Click the **Client-side tokens** tab. 3. Click New client-side token. 4. Enter a name, then click Save. 5. Click the button next to the token, then Copy. ![Illustration of the new token form in Paddle.](/src/assets/images/paddlejs/client-side-token-create-20250407.svg) Sandbox tokens start with `test_`. Live tokens start with `live_`. ## Include Paddle.js You can include Paddle.js using a script tag or an npm package. Create an HTML file and load Paddle.js from the Paddle CDN in the ``: ```html ``` Always load Paddle.js directly from `https://cdn.paddle.com/`. This makes sure that you're running with the latest security and feature updates from Paddle. Install `@paddle/paddle-js` using your package manager: ```bash pnpm add @paddle/paddle-js ``` ```bash yarn add @paddle/paddle-js ``` ```bash npm install @paddle/paddle-js ``` Then import it where you'll initialize Paddle.js: ```typescript import { initializePaddle } from "@paddle/paddle-js"; ``` The package includes TypeScript definitions for all Paddle.js methods. ## Initialize Paddle.js Initialize Paddle.js with your client-side token. For sandbox tokens, also set the environment to `sandbox`. Call [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) inside a script tag: ```html ``` Skip `Paddle.Environment.set()` if you're using a live token. Call `initializePaddle()` and keep the returned `paddle` instance. You'll use it to open checkouts: ```typescript import { initializePaddle, type Paddle } from "@paddle/paddle-js"; let paddle: Paddle | undefined; initializePaddle({ environment: "sandbox", token: "test_abc123def456ghi789jkl0mn", // replace with your client-side token }).then((p) => { paddle = p; }); ``` Omit `environment` if you're using a live token. ## Open a checkout Open a checkout by calling [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) with the price ID you noted earlier. Add a button to your HTML that calls `Paddle.Checkout.open()`: ```html ``` Wire up a button or other event handler to call `Checkout.open()` on the `paddle` instance you initialized: ```typescript function openCheckout() { paddle?.Checkout.open({ items: [ { priceId: "pri_01h1vjes1y163xfj1rh1tkfb65", quantity: 1 }, ], }); } ``` Load your page in a browser and click the button. An overlay checkout slides in. ## Take a test payment Fill in the checkout with these test card details: | Field | Value | | :--------------- | :----------------------------------- | | Email | Any email address | | Country | Any country | | Card number | `4242 4242 4242 4242` | | Name on card | Any name | | Expiration date | Any future date | | Security code | `100` | When you complete checkout, your test payment shows up in **Paddle > Transactions** in the sandbox dashboard. ## Complete example Drop your client-side token and price ID in and run it. Save this as `checkout.html`, run a local dev server, and open it in a browser: ```html ``` Import and initialize Paddle.js, then wire up a button to open a checkout: ```typescript import { initializePaddle, type Paddle } from "@paddle/paddle-js"; let paddle: Paddle | undefined; initializePaddle({ environment: "sandbox", token: "test_abc123def456ghi789jkl0mn", }).then((p) => { paddle = p; }); function openCheckout() { paddle?.Checkout.open({ items: [ { priceId: "pri_01h1vjes1y163xfj1rh1tkfb65", quantity: 1 }, ], }); } document.querySelector("#buy")?.addEventListener("click", openCheckout); ``` Add a matching button to your page: ```html ``` ## Next steps You've opened your first Paddle Checkout. These pages cover what to build next: A deeper walkthrough that covers prefilling customer details, handling events, and styling. Embed the checkout directly in your page instead of opening it as an overlay. Customize the theme, locale, and behavior of your checkouts using default settings. Browse the full method reference for opening, updating, and closing checkouts. Listen for events emitted by Paddle.js to react to customer actions and payment status. Use `Paddle.PricePreview()` to render localized prices on your marketing site. --- # Include and initialize Paddle.js URL: https://developer.paddle.com/paddle-js/about/include-paddlejs Include Paddle.js on your website to start building checkout experiences with Paddle. Initialize and authenticate by passing a client-side token. Include Paddle.js using a script tag or an npm package, then initialize it with a client-side token. You should include and initialize Paddle.js on pages where you open a pricing page or a checkout. ## Before you begin You need a [client-side token](/paddle-js/about/client-side-tokens) to authenticate with Paddle.js. [Create a token](/paddle-js/about/client-side-tokens) in **Paddle > Developer tools > Authentication**, using the Paddle API, or using the Paddle MCP server. ## Use an AI agent ## Include Paddle.js You can manually load the Paddle.js script on your website using a script tag. Add the Paddle.js script to the `` section of your HTML: ```html ``` Always load Paddle.js directly from `https://cdn.paddle.com/`. This makes sure that you're running with the latest security and feature updates from Paddle. Install Paddle.js using your package manager: ```bash pnpm add @paddle/paddle-js ``` ```bash yarn add @paddle/paddle-js ``` ```bash npm install @paddle/paddle-js ``` Then import into your project: ```typescript import { initializePaddle } from '@paddle/paddle-js'; ``` The package includes TypeScript definitions for all Paddle.js methods. ## Initialize and authenticate Initialize Paddle.js by calling the [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) method with a configuration object that includes a client-side token as the `token` property: Initialize Paddle.js by calling the [`initializePaddle()`](https://github.com/PaddleHQ/paddle-js-wrapper?tab=readme-ov-file#initialize-paddlejs) function with a configuration object that includes a client-side token as the `token` property: ## Set up Retain [Paddle.js integrates with Retain](/concepts/retain), so you don't have to include a separate Retain script in your app or website. [Client-side tokens](/paddle-js/about/client-side-tokens) for live accounts authenticate with both Paddle Billing and Paddle Retain, so there's no need to pass a separate key for Retain. ### Where to initialize Paddle.js for Retain As well as pricing pages and checkouts, you should also initialize Paddle.js on: - **Public-facing pages** Retain emails link back to your site, so customers can complete or confirm actions. Initialize Paddle.js on a public-facing marketing page that Retain can redirect to, like your homepage or pricing page. - **In-app authenticated pages** Initialize Paddle.js on logged-in, authenticated pages so Retain can send in-app [payment recovery](/build/retain/configure-payment-recovery-dunning) and [term optimization](/build/retain/configure-term-optimization-automatic-upgrades) notifications, and display in-app [cancellation flows](/build/retain/configure-cancellation-flows-surveys). ### Public-facing pages For public-facing pages, use the standard initialization script from the previous step without any extra configuration. ### In-app authenticated pages For in-app authenticated pages, you need to include a `pwCustomer` object in the configuration object passed to the [`Paddle.Initialize()` method](/paddle-js/methods/paddle-initialize). The `id` property of the `pwCustomer` object must be the Paddle ID of a [customer entity](/api-reference/customers). Other IDs and internal identifiers for a customer don't work with Retain. --- # Paddle.Update() URL: https://developer.paddle.com/paddle-js/methods/paddle-update Use to update values passed to Paddle.js during initialization. Use `Paddle.Update()` to update values sent to Paddle.js during initialization. This is typically used when working with single page applications to pass an updated customer to `pwCustomer` when a customer is identified. You must call `Paddle.Initialize()` before calling `Paddle.Update()`. Use the `Paddle.Initialized` flag to determine whether you need to call `Paddle.Initialize()` or `Paddle.Update()`. ## Parameters ## Examples This example passes a new `pwCustomer` to Paddle.js using `Paddle.Update()`. This example checks if Paddle is initialized using the `Paddle.Initialized` flag, then calls `Paddle.Update()` to set `pwCustomer` if not initialized. --- # Paddle.Environment.set() URL: https://developer.paddle.com/paddle-js/methods/paddle-environment-set Use to set your checkout environment to sandbox. Use `Paddle.Environment.set()` to set the environment for your checkout. Only used to set the [sandbox environment](/sdks/sandbox). If not present, Paddle uses the production environment. You should call this method before calling any other Paddle.js methods, ideally just before your [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) call. ## Parameters ## Examples This example sets the environment to `sandbox`. We recommend removing `Paddle.Environment.set()` before going live. This example doesn't set the checkout environment. Paddle defaults to `production`. --- # Simulate Paddle Retain interventions URL: https://developer.paddle.com/paddle-js/about/test-retain Check that you've installed Paddle.js with Retain or the ProfitWell.js snippets correctly, and see for yourself what Retain looks like. Once you've installed Retain, you can simulate Paddle Retain interventions by running some commands in your browser console. This lets you check that Paddle Retain is installed correctly, and gives you a chance to see what customers see when Retain is in action. ## Before you begin If you haven't already, include [Paddle.js with Retain](/paddle-js/about/include-paddlejs) in your web app and on your commercial website. [Paddle.js integrates with Retain](/concepts/retain), so you don't have to include a separate Retain script. You can simulate Retain interventions even if you haven't set up Retain yet. We recommend that you [set up Retain](/build/retain) and [configure payment recovery](/build/retain/configure-payment-recovery-dunning) before testing. Install the ProfitWell.js snippets on your web app and on your commercial website. See [install ProfitWell.js](https://www.paddle.com/help/profitwell-metrics/setup/install-engagement) on the Paddle Retain help center for more information. We recommend that you [set up Retain](/build/retain) and [configure payment recovery](/build/retain/configure-payment-recovery-dunning) before testing. ## Simulate payment recovery You can simulate the payment recovery form that a customer sees when they click a Retain link and payment recovery notifications on a page where you've installed Paddle Retain. 1. Go to a page where you've installed Paddle.js. 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type Paddle.Retain.demo({feature: 'paymentRecoveryInApp'}) to demo a payment recovery notification. 4. Type Paddle.Retain.demo({feature: 'paymentRecovery'}) to demo a payment recovery form. The form that appears uses Paddle [sandbox](/sdks/sandbox), so you may use [test card details](/concepts/payment-methods/card) to simulate a successful or failed payment. 1. Go to a page where you've installed the ProfitWell.js snippets. 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type profitwell('cq_demo', 'in_app') to demo a payment recovery notification. 4. Type profitwell('cq_demo', 'dunning') to demo a payment recovery form. The form that appears uses Paddle [sandbox](/sdks/sandbox), so you may use [test card details](/concepts/payment-methods/card) to simulate a successful or failed payment. ## Simulate Cancellation Flows You can simulate Cancellation Flows on a page where you've installed Paddle Retain. 1. Go to a page where you've [installed Paddle.js for Retain](/paddle-js/about/include-paddlejs). 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type and enter Paddle.Retain.demo({feature: 'cancellationFlow'}). 1. Go to a page where you've [installed the ProfitWell.js snippets](/build/retain). 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type and enter profitwell('cq_demo', 'cancellation_flow'). ## Simulate Term Optimization You can simulate Term Optimization on a page where you've installed Paddle Retain. 1. Go to a page where you've installed Paddle.js. 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type Paddle.Retain.demo({feature: 'termOptimizationInApp'}) to demo a Term Optimization notification. 4. Type Paddle.Retain.demo({feature: 'termOptimization'}) to demo a Term Optimization form. 1. Go to a page where you've installed the ProfitWell.js snippets. 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type profitwell('cq_demo', 'plan_upgrade', 'notification') to demo a Term Optimization notification. 4. Type profitwell('cq_demo', 'plan_upgrade') to demo a Term Optimization form. ## Simulate other features [Retain Reactivations](https://www.paddle.com/help/profitwell-metrics/retain/get-started/retain-reactivations) and [Retain Lockout](https://www.paddle.com/help/profitwell-metrics/retain/get-started/lockout) aren't available for all billing platforms supported by Paddle Retain. If you use a platform that supports Retain Reactivations and Retain Lockout, you can simulate them on a page where you've installed Paddle Retain. ### Reactivations 1. Go to a page where you've installed the ProfitWell.js snippets. 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type profitwell('cq_demo', 'reactivation'). ### Lockout 1. Go to a page where you've installed the ProfitWell.js snippets. 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Type profitwell('cq_demo', 'lockout'). --- # Paddle.Checkout.open() URL: https://developer.paddle.com/paddle-js/methods/paddle-checkout-open Use to open a checkout with settings, items, and customer information. Use `Paddle.Checkout.open()` to open a checkout. - Set the initial items list or transaction that this checkout is for - Set checkout settings, like the theme - Prefill checkout properties, like customer email and country - Send [custom data](/build/transactions/custom-data) to Paddle To add items to a checkout, you can pass either: - An `items` array of objects, where each object contains a `priceId` and `quantity` property. `priceId` should be a Paddle ID of [a price entity](/api-reference/prices). - The Paddle ID of [a transaction entity](/api-reference/transactions) that you prepared earlier. Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually. To speed up checkout, or build workflows for logged-in customers, you can [prefill customer, address, and business information](/build/checkout/prefill-checkout-properties). You can do this by passing customer, address, and business data, or by passing Paddle IDs for an existing customer, address, or business. You can [open a checkout for an upsell](/build/checkout/upsell-checkout) by passing an `upsell` object, containing the Paddle ID of the previously completed transaction that this upsell follows. You can use [the `Paddle.Initialize()` method](/paddle-js/methods/paddle-initialize) to [set default checkout settings](/build/checkout/set-up-checkout-default-settings). These settings apply to all checkouts opened on a page. ## Parameters ## Example You can pass checkout settings using `Paddle.Initialize()`, or set them for each checkout in `Paddle.Checkout.open()`. This example includes the `settings` object as part of the checkout open method. Checkout is opened with these settings. To learn more, see [Pass checkout settings](/build/checkout/set-up-checkout-default-settings) You can pass checkout settings using `Paddle.Initialize()`, or set them for each checkout in `Paddle.Checkout.open()`. This example includes the `settings` object as part of the checkout open method, passing the required settings to open a one-page overlay checkout. You can prefill checkout properties to speed up checkout. This example includes `customer`, `address`, and `business` objects. Checkout is opened with this information prefilled, so customers land on the payment screen. There's no `settings` object, so checkout settings must be included in `Paddle.Initialize()`. To learn more, see [Prefill checkout properties](/build/checkout/prefill-checkout-properties) You can pass Paddle IDs for customers, addresses, and businesses to build upgrade workflows for logged-in customers. This example includes a customer ID, address ID, and business ID. Checkout is opened with this information prefilled, so customers land on the payment screen. `allowLogout` is `false` in the `settings` object, hiding the option to change the customer on the opened checkout. To learn more, see [Prefill checkout properties](/build/checkout/prefill-checkout-properties) You can create a transaction using the API or the Paddle dashboard, then pass it to a checkout to collect for it. This example passes `transactionID` to open a checkout for that transaction. There's no `settings` object, so checkout settings must be included in `Paddle.Initialize()`. To learn more, see [Pass a transaction to a checkout](/build/transactions/pass-transaction-checkout) Customers can save payment methods when buying items using Paddle Checkout. You can then present customers with their saved payment methods when making purchases in the future. You must pass `customerAuthToken` when opening a checkout to authenticate a customer and present them with their saved payment methods. You can [generate an authentication token for a customer using the API](/api-reference/customers/generate-customer-authentication-token). This example passes `customerAuthToken` to `Paddle.Checkout.open()`, so customers are presented with their saved payment methods. To learn more, see [Present saved payment methods at checkout](/build/checkout/saved-payment-methods) Customers can save payment methods when buying items using Paddle Checkout. You can then present customers with their saved payment methods when making purchases in the future. You must pass `customerAuthToken` when opening a checkout to authenticate a customer and present them with their saved payment methods. You can [generate an authentication token for a customer using the API](/api-reference/customers/generate-customer-authentication-token). You can open a checkout for a specific a payment method that a customer has saved by using `savedPaymentMethodId`, passing the Paddle ID for a saved payment method. You can [list payment methods for a customer using the API](/api-reference/payment-methods/list-customer-payment-methods). This example passes `customerAuthToken` and `savedPaymentMethodId` to `Paddle.Checkout.open()`, so customers are presented with the passed saved payment method. To learn more, see [Present saved payment methods at checkout](/build/checkout/saved-payment-methods) You can present customers with a streamlined purchase flow for upsells at checkout to encourage them to upgrade or purchase additional items if they've previously completed a transaction. This example passes the previously completed transaction ID as `upsell.transactionId`, and passes `upsell.settings.showSkipButton: false` to hide the "No thanks" skip button at checkout. To learn more, see [Open a checkout for an upsell](/build/checkout/upsell-checkout) You can open an [express checkout](/concepts/sell/express-checkout) for mobile customers to surface Apple Pay first and capture customer information automatically from Apple Wallet and IP address geolocation. This example passes `variant: 'express'` in the `settings` object alongside the required `displayMode: 'inline'` and `frameTarget` to open an express checkout. To learn more, see [Express checkout](/concepts/sell/express-checkout). --- # Hosted checkout URL query parameters URL: https://developer.paddle.com/paddle-js/about/hosted-checkout Append query parameters to hosted checkout launch URLs to pass information about a customer and items to a checkout. You can use [hosted checkouts](/concepts/sell/hosted-checkout-mobile-apps) to let users securely make purchases outside your mobile app. Each hosted checkout has a unique link that you can add to your app to let customers open a checkout that's fully hosted by Paddle. All hosted checkout URL query parameters are optional. You can set defaults when creating a hosted checkout in **Paddle > Checkout > Hosted Checkouts**, like the prices to open the checkout with if no `priceId` or `transactionId` is passed. However, one is required if no default price is set. Hosted checkout parameters also work with the [Paddle mobile web payments starter kit](https://github.com/PaddleHQ/paddle-mobile-web-payments-starter), which you can use to deploy your own mobile purchase workflow. For maximum compatibility across browsers, make sure to [percent-encode query strings](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding). For example, pass `max%2Bpaddle%40example.com` for `max+paddle@example.com`. ## Parameters ## Examples This example passes a price ID to Paddle Checkout to specify what the customer is purchasing. ```sh https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt ``` This example passes two price IDs to Paddle Checkout to specify that a customer is purchasing two items. ```sh https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt,pri_01hv0vax6rv18t4tamj848ne4d ``` This example passes a price ID to tell Paddle Checkout what the customer is purchasing, along with a user email address. The email address is [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding). ```sh https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt&user_email=sam%40example.com ``` This example passes a price ID to tell Paddle Checkout what the customer is purchasing, along with a discount code. ```sh https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt&discount_code=BF20OFF ``` This example passes a price ID along with `variant=express` to open the link as an [express checkout](/concepts/sell/express-checkout) optimized for mobile, prioritizing Apple Pay. ```sh https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a?price_id=pri_01h1vjg3sqjj1y9tvazkdqe5vt&variant=express ``` --- # Paddle.Checkout.updateCheckout() URL: https://developer.paddle.com/paddle-js/methods/paddle-checkout-updatecheckout Use to update an open checkout. Use `Paddle.Checkout.updateCheckout()` to dynamically update the items list, discount, and customer information for an open checkout. This method is similar to [`Paddle.Checkout.updateItems()`](/paddle-js/methods/paddle-checkout-updateitems), but also lets you pass discount and customer information. Typically used with [inline checkout](/build/checkout/build-branded-inline-checkout) to change the items list while adding, removing, or changing a discount. To use this method, a checkout should already be opened. Use [the `Paddle.Checkout.open()` method](/paddle-js/methods/paddle-checkout-open) to open a checkout. To update items, pass an array of objects, where each object contains a `priceId` and `quantity` property. `priceId` should be a Paddle ID of [a price entity](/api-reference/prices). Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually. Paddle expects the complete list of items that you want to be on the checkout — including existing items. If you don't include an existing item, it's removed from the checkout. To learn more, see [Work with lists](/api-reference/about/lists) ## Parameters ## Examples This example passes an array of items and a discount code to `Paddle.Checkout.updateCheckout()`. If successful, the items and the discount on the opened checkout are updated. To learn more, see [Pass or update checkout items](/build/checkout/pass-update-checkout-items) This example passes `customData` to `Paddle.Checkout.updateCheckout()`. If successful, custom data on the open checkout is updated. To learn more, see [Work with custom data](/build/transactions/custom-data) --- # HTML data attributes URL: https://developer.paddle.com/paddle-js/about/html-data-attributes Use HTML data attributes to pass parameters to a checkout, rather than using JavaScript properties. You can use HTML data attributes to open a checkout with settings, items, and customer information. They're typically used with [overlay checkouts](/concepts/sell/overlay-checkout) in websites or apps where you don't have full control over the page layout. Data attributes support all the same properties as the [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) method, letting you: - Set the initial items list or transaction that a checkout is for - Set checkout settings, like the theme - Prefill checkout properties, like customer email and country - Send [custom data](/build/transactions/custom-data) to Paddle Set data attributes on the element that you've turned into a Paddle Checkout button to open a checkout with the properties. ## Before you begin You must [include and initialize Paddle.js](/paddle-js/about/include-paddlejs) to use HTML data attributes, but you don't need to use any other JavaScript if you're working with an overlay checkout. ## List of attributes ## Examples This example includes checkout settings and items. ```html Buy now ``` You can prefill checkout properties to speed up checkout. This example includes `customer`, `address`, and `business` information. Checkout is opened with this information prefilled, so customers land on the payment screen. ```html Buy now ``` You can pass Paddle IDs for customers, addresses, and businesses to build upgrade workflows for logged-in customers. This example includes a customer ID, address ID, and business ID. Checkout is opened with this information prefilled, so customers land on the payment screen. `data-allow-logout` is `false`, hiding the option to change the customer on the opened checkout. ```html Buy now ``` --- # Paddle.Checkout.updateItems() URL: https://developer.paddle.com/paddle-js/methods/paddle-checkout-updateitems Use to dynamically update the items list of an open checkout. Use `Paddle.Checkout.updateItems()` to dynamically update the items list for an open checkout. Typically used with [inline checkout](/build/checkout/build-branded-inline-checkout) to update quantities or add addons to the checkout. To use this method, a checkout should already be opened. Use [the `Paddle.Checkout.open()` method](/paddle-js/methods/paddle-checkout-open) to open a checkout. Pass an array of objects, where each object contains a `priceId` and `quantity` property. `priceId` should be a Paddle ID of [a price entity](/api-reference/prices). Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually. Paddle expects the complete list of items that you want to be on the checkout — including existing items. If you don't include an existing item, it's removed from the checkout. To learn more, see [Work with lists](/api-reference/about/lists) Use the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method to update discount and customer information, as well as items. You might do this when you need to swap or remove a discount when updating items. ## Parameters ## Example This example passes an array called `itemsList` to `Paddle.Checkout.updateItems()`. If successful, the items on the opened checkout are updated. To learn more, see [Pass or update checkout items](/build/checkout/pass-update-checkout-items) --- # Paddle.Checkout.close() URL: https://developer.paddle.com/paddle-js/methods/paddle-checkout-close Use to close an opened checkout. Use `Paddle.Checkout.close()` to close [an opened checkout](/paddle-js/methods/paddle-checkout-open). Typically used when working with [overlay checkout](/concepts/sell/overlay-checkout) to close the Paddle Checkout overlay. If used with [inline checkout](/concepts/sell/branded-integrated-inline-checkout), removes the Paddle Checkout iframe from the DOM. For success workflows, you can redirect to a success URL or pass an `eventCallback` to `Paddle.Initialize()` for `checkout.completed`. See [Handle checkout success](/build/checkout/handle-success-post-checkout). ## Example This example shows how you can use `Paddle.Checkout.close()` to close an inline checkout. --- # Paddle.js URL: https://developer.paddle.com/paddle-js Integrate Paddle and Retain in your app or website frontend with Paddle.js. --- # Paddle.PricePreview() URL: https://developer.paddle.com/paddle-js/methods/paddle-pricepreview Use to preview localized prices given location information supplied. Use `Paddle.PricePreview()` to return a pricing preview object for the given items and location parameters. [Pricing preview objects](/api-reference/pricing-preview) hold calculated totals for prices, including discounts, taxes, and currency conversion. Typically used for [building pricing pages](/build/checkout/build-pricing-page). For more advanced pricing pages, consider the [`Paddle.TransactionPreview()`](/paddle-js/methods/paddle-transactionpreview) method instead. Accepts the same request body as [the preview prices operation](/api-reference/pricing-preview/preview-prices) in the Paddle API, except fields must be formatted as `camelCase` rather than `snake_case`. Returns a promise that contains an object that matches the response from the preview prices operation. Field names are `camelCase` rather than `snake_case`. When location information is omitted, Paddle.js automatically detects visitor location using their IP address and returns localized prices. ## Parameters Check [the preview prices operation](/api-reference/pricing-preview/preview-prices) documentation to learn about the fields you can send in a request. Convert `snake_case` field names to `camelCase`, as is convention for JavaScript. ## Examples This example includes a request with two items where the country code is the . The request is passed to `Paddle.PricePreview()`, which returns a promise. It prints the response to the console. To learn more, see [Build a pricing page](/build/checkout/build-pricing-page) --- # Paddle.TransactionPreview() URL: https://developer.paddle.com/paddle-js/methods/paddle-transactionpreview Use to generate a transaction preview for prices and location information supplied. Use `Paddle.TransactionPreview()` to return a transaction preview object for items and location information supplied. [Transaction previews](/api-reference/transactions/preview-transaction-create) are previews of [transaction entities](/api-reference/transactions), holding calculated totals for prices — including discounts, taxes, and currency conversion. They're typically used for building advanced, cart-style pricing pages where users can build their own plans. For simpler pricing pages, consider the [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview) method instead. Unlike [pricing previews](/paddle-js/methods/paddle-pricepreview) returned when using [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview), transaction previews return both line item and grand totals for items passed. This means that they have the same validation logic as transactions, too. For example, all items must have the same billing period. Accepts the same request body as [the preview a transaction operation](/api-reference/transactions/preview-transaction-create) in the Paddle API, except fields must be formatted as `camelCase` rather than `snake_case`. Returns a promise that contains an object that matches the response from the preview a transaction operation. Field names are `camelCase` rather than `snake_case`. When location information is omitted, Paddle.js automatically detects visitor location using their IP address and returns localized prices. ## Parameters Check [the preview a transaction operation](/api-reference/transactions/preview-transaction-create) documentation to learn about the fields you can send in a request. Convert `snake_case` field names to `camelCase`, as is convention for JavaScript. ## Examples This example includes a request with two items where the country code is the and the currency code is `USD`. One of the items is excluded from the totals using the `includeInTotals` field. It also passes a discount. The request is passed to `Paddle.TransactionPreview()`, which returns a promise. It prints the response to the console. --- # Methods URL: https://developer.paddle.com/paddle-js/methods Explore the methods available in Paddle.js to interact with checkouts and pricing pages. --- # Paddle.Retain.demo() URL: https://developer.paddle.com/paddle-js/methods/paddle-retain-demo Use to demo Retain functionality. Use `Paddle.Retain.demo()` to demo Paddle Retain functionality. Typically used by typing `Paddle.Retain.demo({feature: 'featureName'})` directly into the browser console. You can simulate payment recovery and dunning, Cancellation Flows, and Term Optimization. This method is for Paddle Billing only. If you use Paddle Retain with another billing platform, use the `profitwell` method in the ProfitWell.js snippet instead. To learn more, see [Test Paddle Retain](/paddle-js/about/test-retain) ## Parameters ## Examples This example simulates a cancellation flow. Type this directly into your browser console to demo Retain Cancellation Flows: ```javascript Paddle.Retain.demo({feature: 'cancellationFlow'}) ``` This example simulates the payment form that a customer sees when they click on a link in a payment recovery email. Type this directly into your browser console to demo Retain Payment Recovery: ```javascript Paddle.Retain.demo({feature: 'paymentRecovery'}) ``` This example simulates an in-app notification that prompts a customer to update their payment method. Type this directly into your browser console to demo an in-app notification for Retain Payment Recovery: ```javascript Paddle.Retain.demo({feature: 'paymentRecoveryInApp'}) ``` This example simulates the modal that a customer sees when they click on a link in a Term Optimization email. Type this directly into your browser console to demo Retain Term Optimization: ```javascript Paddle.Retain.demo({feature: 'termOptimization'}) ``` This example simulates an in-app notification that prompts a customer to upgrade their plan. Type this directly into your browser console to demo an in-app notification for Retain Term Optimization: ```javascript Paddle.Retain.demo({feature: 'termOptimizationInApp'}) ``` To learn more, see [Test Paddle Retain](/paddle-js/about/test-retain) --- # Paddle.Retain.initCancellationFlow() URL: https://developer.paddle.com/paddle-js/methods/paddle-retain-initcancellationflow Use to start a Paddle Retain cancellation flow for a subscription. Use `Paddle.Retain.initCancellationFlow()` to start a Paddle Retain cancellation flow for a subscription. [Cancellation Flows](/build/retain/configure-cancellation-flows-surveys) help you save customers from canceling by presenting them with dynamic salvage attempts while gathering cancellation insights. Retain automatically schedules a cancellation for the subscription in Paddle Billing if a customer proceeds to cancel. Typically used as part of [a cancel subscription workflow](/build/retain/configure-cancellation-flows-surveys). This method is for Paddle Billing only. If you use Cancellation Flows with another billing platform, use the `profitwell` method in the ProfitWell.js snippet instead. To learn more, see [Configure Cancellation Flows and salvage offers](/build/retain/configure-cancellation-flows-surveys) To specify a subscription to cancel, pass a `subscriptionId` parameter. This is recommended, but not required where customers only have one subscription and you passed `pwCustomer` to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) or [`Paddle.Update()`](/paddle-js/methods/paddle-update). ## Parameters ## Returns ## Examples This example shows how you can use `Paddle.Retain.initCancellationFlow()` to start a cancellation flow. `subscriptionId` is passed to `Paddle.Retain.initCancellationFlow()` to specify the subscription to cancel. `pwCustomer` is passed to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) to identify the customer to Paddle Retain, but this isn't required. Paddle Retain infers the customer from the `subscriptionId` passed and presents a cancellation flow. Retain automatically schedules a cancellation for the subscription in Paddle Billing if a customer proceeds to cancel, so you don't need to build logic to handle this yourself. To learn more, see [Build cancellation surveys and offers](/build/retain/configure-cancellation-flows-surveys) This example shows how you can attach a callback to a cancellation flow. It uses the `.then()` method to attach a callback that logs a message to the console: - **Customer retained** The customer accepted a salvage attempt or a salvage offer, or chose not to cancel. - **There was a problem starting the cancellation flow.** Something went wrong while starting the cancellation flow. The customer wasn't given the chance to cancel. - **Customer proceeded with cancellation.** The customer rejected salvage attempts and salvage offers and proceeded to cancel. To learn more, see [Build cancellation surveys and offers](/build/retain/configure-cancellation-flows-surveys) --- # Paddle.Status.libraryVersion URL: https://developer.paddle.com/paddle-js/methods/paddle-status-libraryversion Returns the Paddle.js version. Useful for troubleshooting. Use `Paddle.Status.libraryVersion()` to return the Paddle.js version. When including Paddle.js, Paddle automatically serves the latest compatible version of the Paddle.js library to your device. You may be asked to get the version of Paddle.js by Paddle Support while troubleshooting. ## Example This example prints the version of Paddle.js to the console. ```javascript console.log(Paddle.Status.libraryVersion) ``` You can type `Paddle.Status.libraryVersion` into the browser console to return the latest version. --- # Events URL: https://developer.paddle.com/paddle-js/events Paddle.js emits events as the customer moves through the checkout. Use an event callback to show and update on-page information. As a customer moves through the checkout, Paddle.js emits events for key actions like: - When the checkout is opened, closed, or completed - When customer information is added, updated, or removed - When items are added, removed, or updated - When payments are attempted and outcomes of payment attempts - When discounts are added or removed - When an upsell is skipped You can pass an `eventCallback` when initializing Paddle.js to run actions based on these events. It's typically used to show and update on-page information when building [an inline checkout](/build/checkout/build-branded-inline-checkout). ## Event schema Most transaction-related events include the same basic schema, with a payload that closely mirrors transactions in the Paddle API. `checkout.warning` and `checkout.error` events don't follow this schema. Payloads closely mirror error responses in the Paddle API instead. New events added to the platform may not follow these schemas, so you should build your event handler to handle unexpected fields or values. Use `event.name` to listen only to the events you need. ## Common attributes When [building an inline checkout](/build/checkout/build-branded-inline-checkout), you can use events to present totals and other information about a customer's purchase. It's important that customers know who they're buying from, what they're buying, and how much they're paying. To build an inline checkout that's compliant and optimized for conversion, your implementation must include: 1. If recurring, how often it recurs and the total to pay on renewal. If a trial, how long the trial lasts. 2. A description of what's being purchased. 3. Transaction totals, including subtotal, total tax, and grand total. Be sure to include the currency too. 4. The full inline checkout frame, including the checkout footer that has information about Paddle, our terms of sale, and our privacy policy. 5. A link to your refund policy, if it differs from the Paddle.com standard refund policy. Here are some commonly used fields in Paddle.js events that you might like to use in your implementation: | Event | Used for | |-------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `data.items[].product.name` | Product name for itemized breakdowns. | | `data.items[].price_name` | Price name. Typically describes how often this item bills. | | `data.items[].trial_period` | Details about the trial period for an item, if any. | | `data.items[].billing_cycle` | How often an item is billed, if recurring. | | `data.items[].totals.subtotal` | Total for an item, excluding tax and discount. | | `data.totals.total` | Grand total for a transaction, including tax and discount. | | `data.recurring_totals.total` | Recurring total for a transaction, including tax and discount. | | (`data.totals.total` - `data.recurring_totals.total`) | Where transactions contain a mix of one-time charges and recurring items, subtract a value in `data.recurring_totals` from the corresponding value in `data.totals` to calculate one-time charge totals. | ## List of events --- # checkout.closed URL: https://developer.paddle.com/paddle-js/events/checkout-closed Emitted when a checkout is closed. Use `checkout.closed` to run actions when a checkout is closed. This typically means the customer clicked the close icon on an overlay checkout, or you called `Paddle.Checkout.close()` to close the checkout. `checkout.completed` is emitted when payment is successful. --- # checkout.completed URL: https://developer.paddle.com/paddle-js/events/checkout-completed Emitted when a checkout is completed successfully. Use `checkout.completed` to run actions when a checkout is completed successfully. This typically means: - For instant capture payment methods, payment was taken successfully for the items on a checkout. - For deferred capture payment methods, payment was authorized and is pending capture. For compatibility reasons, `data.payment.method_details.type` values in `checkout.completed` events are `snake_case` instead of `kebab-case`. For example, Apple Pay is `apple_pay` in `checkout.completed` events, but `apple-pay` in all other events and the Paddle API. --- # checkout.customer.created URL: https://developer.paddle.com/paddle-js/events/checkout-customer-created Emitted when a customer is created as part of checkout. Use `checkout.customer.created` to run actions when a customer is created as part of checkout. This typically means the customer has entered their email address and Paddle created a customer entity for them. [`checkout.customer.updated`](/paddle-js/events/checkout-customer-updated) is emitted when the customer edits their details, and [`checkout.customer.removed`](/paddle-js/events/checkout-customer-removed) is emitted if they sign out of the checkout. --- # checkout.customer.removed URL: https://developer.paddle.com/paddle-js/events/checkout-customer-removed Emitted when a customer is removed from a checkout. Use `checkout.customer.removed` to run actions when a customer is removed from a checkout. This typically means a customer has clicked "Not you? Change" to log out on an overlay checkout, or they updated their customer details. --- # checkout.customer.updated URL: https://developer.paddle.com/paddle-js/events/checkout-customer-updated Emitted when customer information is updated. Use `checkout.customer.updated` to run actions when customer information is updated. This includes business and address information. This typically means a customer has updated their details, including entering address information or business details. --- # checkout.discount.applied URL: https://developer.paddle.com/paddle-js/events/checkout-discount-applied Emitted when a discount is applied to a checkout. Use `checkout.discount.applied` to run actions when a discount is applied to a checkout. [`checkout.discount.removed`](/paddle-js/events/checkout-discount-removed) is emitted if the customer or your code later removes the discount. --- # checkout.discount.removed URL: https://developer.paddle.com/paddle-js/events/checkout-discount-removed Emitted when a discount is removed from a checkout. Use `checkout.discount.removed` to run actions when a discount is removed from a checkout. It's the counterpart to [`checkout.discount.applied`](/paddle-js/events/checkout-discount-applied). --- # checkout.error URL: https://developer.paddle.com/paddle-js/events/checkout-error Emitted when an error occurs on a checkout. Use `checkout.error` to run actions when an error occurs on a checkout. Errors occur when data passed to Paddle.js prevents Paddle from opening a checkout entirely. For example, if you pass properties to Paddle.js that are fundamentally incompatible, you can use `checkout.error` to log the error to your analytics tool. You may be able to parse the error to relaunch the checkout with the correct data. If data passed to Paddle.js is invalid but doesn't prevent Paddle from opening a checkout, a `checkout.warning` event is emitted instead. --- # checkout.items.removed URL: https://developer.paddle.com/paddle-js/events/checkout-items-removed Emitted when an item is removed from a checkout. Use `checkout.items.removed` to run actions when an item is removed from a checkout. For quantity or price changes to existing items, [`checkout.items.updated`](/paddle-js/events/checkout-items-updated) is emitted instead. --- # checkout.items.updated URL: https://developer.paddle.com/paddle-js/events/checkout-items-updated Emitted when an item is updated on a checkout. Use `checkout.items.updated` to run actions when an item is updated on a checkout. For example, a customer might change the quantity of an item using overlay checkout, or you might update the quantity of an item using `Paddle.Checkout.updateItems()` or `Paddle.Checkout.updateCheckout()`. --- # checkout.loaded URL: https://developer.paddle.com/paddle-js/events/checkout-loaded Emitted when a checkout is created and loaded on the page. Use `checkout.loaded` to run actions when a checkout is initially created and loaded on the page. It's emitted once per checkout. For subsequent changes, [`checkout.updated`](/paddle-js/events/checkout-updated) is emitted instead. --- # checkout.payment.error URL: https://developer.paddle.com/paddle-js/events/checkout-payment-error Emitted when a payment errors for a checkout. Use `checkout.payment.error` to run actions when a payment errors for a checkout. This typically occurs when no payment methods are available. For example, if you open a checkout with `allowedPaymentMethods` set to `["apple-pay"]`, but the customer's device or browser doesn't support Apple Pay, this is emitted. --- # checkout.payment.failed URL: https://developer.paddle.com/paddle-js/events/checkout-payment-failed Emitted when a payment fails for a checkout. Use `checkout.payment.failed` to run actions when a payment fails for a checkout. It follows [`checkout.payment.initiated`](/paddle-js/events/checkout-payment-initiated) when the payment attempt doesn't succeed. --- # checkout.payment.initiated URL: https://developer.paddle.com/paddle-js/events/checkout-payment-initiated Emitted when an attempt to take payment is initiated. Use `checkout.payment.initiated` to run actions when an attempt to take payment is initiated. It's followed by either: - `checkout.payment.failed` if payment fails - `checkout.completed` if payment is successful This is typically used to track payment acceptance rates. --- # checkout.payment.selected URL: https://developer.paddle.com/paddle-js/events/checkout-payment-selected Emitted when a payment method is selected. Use `checkout.payment.selected` to run actions when a payment method is selected. This typically means a customer has selected one of the payment method buttons, like PayPal or Apple Pay, or started to enter details into the card details form. [`checkout.payment.initiated`](/paddle-js/events/checkout-payment-initiated) is emitted when the customer submits payment. --- # checkout.updated URL: https://developer.paddle.com/paddle-js/events/checkout-updated Emitted when a checkout is updated using one of the checkout update methods. Use `checkout.updated` to run actions when a checkout is updated using the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method. --- # checkout.upsell.canceled URL: https://developer.paddle.com/paddle-js/events/checkout-upsell-canceled Emitted when a checkout for an upsell is canceled. Use `checkout.upsell.canceled` to run actions when a checkout for an upsell is canceled. This typically means the customer has clicked the "No thanks" skip button. You should handle this by redirecting, closing the checkout, or updating your page to reflect the skipped upsell. --- # checkout.warning URL: https://developer.paddle.com/paddle-js/events/checkout-warning Emitted when a warning occurs on a checkout. Use `checkout.warning` to run actions when a warning occurs on a checkout. Warnings occur when something is wrong with data passed to Paddle.js, but it doesn't prevent Paddle from opening a checkout. For example, if you prefill customer details but the details you passed aren't valid, you can use `checkout.warning` to display a custom error message to the customer or log the warning to your analytics tool. If data passed to Paddle.js prevents Paddle from opening a checkout, a `checkout.error` event is emitted instead. --- # How webhooks work URL: https://developer.paddle.com/webhooks/about/how-webhooks-work Understand Paddle's event model, webhook payload structure, and delivery guarantees before you build your integration. Paddle uses webhooks to push real-time notifications to your server when things happen in your account. Rather than polling the API for changes, your server receives an HTTP `POST` request the moment an event occurs. Use webhooks to: - Manage access to features in your app depending on a customer's subscription status. - Sync information with other systems that your business uses, like a CRM or ERP solution. - Set up notifications or automations. ## The event model When something notable happens in Paddle, it creates an **event** entity. An event records: - What happened (`event_type`) - When it happened (`occurred_at`) - A snapshot of the entity at that moment (`data`) You can poll the `/events` endpoint in the Paddle API to get a list of events. However, subscribing to events via webhooks is recommended. Paddle pushes events to you as they happen, rather than you having to pull them. When you create a [notification destination](/webhooks/about/notification-destinations), you tell Paddle which events you want to receive and where to send them. From that point on, every time a matching event occurs, Paddle sends a **notification** to your endpoint with the event payload as JSON. ```mermaid sequenceDiagram participant Paddle participant YourServer as Your server Paddle->>Paddle: Something happens (e.g. subscription created) Paddle->>Paddle: Creates an event entity Paddle->>YourServer: POST /your-webhook-endpoint note over YourServer: Receives JSON payload YourServer-->>Paddle: HTTP 200 OK note over Paddle: Marks notification as delivered ``` ## Webhook payloads Every webhook Paddle sends has the same top-level structure, regardless of the event type: | Field | Description | |---|---| | `event_id` | Unique ID for this event, prefixed with `evt_`. Use this to deduplicate events you may receive more than once. | | `event_type` | The type of event, in the format `entity.action` (e.g. `customer.created`). Use this to route events in your handler. | | `occurred_at` | RFC 3339 timestamp of when the event occurred. Use this to handle events that arrive out of order. | | `notification_id` | Unique ID for this delivery attempt, prefixed with `ntf_`. Different from `event_id` because a single event can produce multiple notifications. | | `data` | The new or changed entity at the time of the event. This is a snapshot that reflects the entity's state when the event occurred. | ## Events vs. notifications An **event** is a record of something that happened. A **notification** is a delivery attempt for that event to a specific destination. Notifications can be delivered via webhook or email. The distinction matters when you have multiple notification destinations. If you have two webhook endpoints both subscribed to `subscription.created`, Paddle creates one event but two notifications — one for each destination. Each notification has its own `notification_id` but they share the same `event_id`. ## Delivery guarantees Paddle guarantees **at-least-once delivery**. If your server doesn't respond with `200` within five seconds, Paddle retries the notification automatically. This means your handler may occasionally receive the same event more than once. To handle this safely, make your webhook processing **idempotent**. This means processing the same event twice should produce the same result as processing it once. Using `event_id` as a deduplication key is the simplest approach: store it when you first process an event, and skip any event with an ID you've already seen. Because events can arrive out of order, use `occurred_at` rather than arrival time when you need to reason about the sequence of events. For example, if you receive both `subscription.updated` and `subscription.canceled` for the same subscription, compare their `occurred_at` values to determine which represents the latest state. ## Notification status Each notification moves through a lifecycle as Paddle attempts to deliver it: ```mermaid flowchart LR A["not_attempted"] --> B["needs_retry"] A --> C["delivered"] B --> C B --> D["failed"] ``` | Status | Description | |---|---| | `not_attempted` | Paddle hasn't yet tried to deliver this notification. | | `needs_retry` | Delivery failed. The notification is scheduled for another attempt using an exponential backoff schedule. | | `delivered` | Your server responded with `200`. Paddle considers this notification successfully delivered. | | `failed` | All delivery attempts were exhausted. The notification won't be retried automatically, but you can [replay it](/api-reference/notifications/replay-notification) using the API. | You can inspect notification status and delivery logs in the Paddle dashboard under **Developer Tools > Notifications**, or by using the [list notification logs](/api-reference/notification-logs/list-notification-logs) operation. ## What happens at checkout When a customer completes Paddle Checkout, a sequence of events fires automatically as Paddle creates and updates entities in your account. Understanding this sequence helps you know which events to handle for provisioning. Paddle Checkout supports prefilling data and a variety of configuration options, and customer journeys can vary, but a typical sequence of events might look like this: ```mermaid sequenceDiagram participant Customer participant Paddle participant YourServer as Your server Customer->>Paddle: Opens checkout Paddle->>YourServer: transaction.created Customer->>Paddle: Enters email Paddle->>YourServer: customer.created Customer->>Paddle: Enters country / ZIP Paddle->>YourServer: address.created Customer->>Paddle: Enters tax/VAT number (optional) Paddle->>YourServer: business.created Customer->>Paddle: Completes payment Paddle->>YourServer: transaction.paid Paddle->>YourServer: subscription.created (if recurring) Paddle->>YourServer: transaction.completed ``` For a subscription, `transaction.paid` is the earliest point at which you can be sure payment has been captured. Use `subscription.created` to record the subscription in your system, and `transaction.completed` to confirm that Paddle has finished all internal processing. You can use the [subscription created scenario](/webhooks/subscriptions/subscription-created) in the webhook simulator to test your checkout integration end-to-end. You can inspect each event in order, replay individual notifications, and validate your handler logic without making a real purchase. ## Next steps Tell Paddle where to send webhooks and which events you want to receive. Go to **Developer Tools > Notifications** in your Paddle dashboard, or use the [create notification destination](/api-reference/notification-settings/create-notification-setting) operation. [Create a notification destination](/webhooks/about/notification-destinations) Configure your server to accept `POST` requests over HTTPS, respond with `200` within five seconds, and handle retries. Learn about IP allowlisting and processing recommendations. [Handle webhook delivery](/webhooks/about/respond-to-webhooks) Every Paddle webhook includes a `Paddle-Signature` header. Verify it to confirm that the request genuinely came from Paddle and hasn't been tampered with. [Verify webhook signatures](/webhooks/about/signature-verification) Use the webhook simulator to send test events to your endpoint without triggering real transactions. [Simulate webhooks](/webhooks/simulator/test-webhooks) --- # Create or update notification destinations URL: https://developer.paddle.com/webhooks/about/notification-destinations Create webhook destinations to tell Paddle which events you want to receive and where to deliver them to. Once added, you can update, deactivate, and delete destinations. A notification destination is a webhook endpoint or email address that Paddle sends notifications about events to. It's called a notification setting in the API. ## Use an AI agent ## Create a notification destination Create a notification destination to start receiving notifications for events. You can choose the kind of events that you want to receive notifications for. You can create as many notification destinations as you want, but only 10 can be active at once. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click New destination 3. Enter the details for your new notification destination. 4. Choose the events that you want to receive notifications for. 5. Click Save destination when you're done. ![Illustration of the create notification destination drawer in Paddle.](/src/assets/images/tmp/procedure-new-destination-20240923.svg) Short description for this notification destination. Whether you want to send events to a webhook endpoint or an email address. Webhook endpoint URL or email address to send events to. API version that entities for events should conform to. Whether this destination receives real platform events, simulation events, or both. Create a notification destination using the API in two steps: 1. **List event types and extract names** Query the API to learn about the events that you can subscribe to, then extract names. 2. **Create your destination** Build a request that includes details of your destination and an array of strings for the events you want to receive, then send your request. ### List event types and extract names Event types are actions that Paddle creates events for. To create a notification destination, you'll need to tell Paddle which event types you want to get notifications for. Send a `GET` request to the `/event-types` endpoint to get a list of all event types. Review the response, then extract `data[].name` for the events that you want to subscribe to and save these for later — we'll use this in the next step. ### Create your destination Build a request that includes information about your destination. - The value for `destination` changes depending on the `type`. For `email`, pass a valid email address. For `url`, pass a valid URL. - Pass an array of strings as `events` for the events you want to receive notifications for. - Pass `traffic_source` to determine whether this destination receives real platform events, simulation events, or both. Send a `POST` request to the `/notification-settings` endpoint with the request you built. If successful, Paddle responds with a copy of the new notification destination. The response includes an object for each subscribed event with details about that event, and an `endpoint_secret_key` that you can use to [verify events come from Paddle](/webhooks/about/signature-verification). ## Update a notification destination Once you've created a notification destination, you can change its description, destination URL or email, what kind of traffic it receives, and the events it's subscribed to. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click the button next to a notification destination in the list, then choose Edit destination from the menu. 3. Edit notification destination details and subscribed events. 4. Click Update destination when you're done. ![Illustration of the update notification destination drawer in Paddle.](/src/assets/images/tmp/procedure-update-destination-20240923.svg) Send a `PATCH` request to the `/notification-settings/{notification_setting_id}` endpoint, passing the ID of the notification destination as a path parameter. Include only the fields you want to update. Omitted fields remain unchanged. If successful, Paddle responds with a copy of the updated notification destination. ## Deactivate a notification destination Deactivate a notification destination to stop Paddle from sending notifications for events to it. Deactivation is useful if you need to make changes to a webhook endpoint server or integration. You can reactivate later, if needed. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click the button next to a notification destination in the list, then choose Deactivate from the menu. 3. Click Deactivate destination on the confirmation dialog. You can reactivate later by choosing Activate from the menu. ![Illustration of the notifications list screen in Paddle. The action menu is open showing the Deactivate option.](/src/assets/images/tmp/procedure-deactivate-destination-20240916.svg) Send a `PATCH` request to the `/notification-settings/{notification_setting_id}` endpoint, setting `active` to `false`. If successful, Paddle responds with a copy of the updated notification destination. Paddle no longer tries to deliver notifications for subscribed events. ## Delete a notification destination There's no way to recover a deleted notification destination. [Deactivate a notification destination](#deactivate-a-notification-destination) if you'll need access to the logs or want to reactivate later. Delete a notification destination to permanently remove it from your Paddle account. Paddle stops sending notifications for events to your destination and you lose access to all delivery logs for it. You can only delete notification destinations using the API. Send a `DELETE` request to the `/notification-settings/{notification_setting_id}` endpoint, passing the ID of the notification destination as a path parameter. If successful, Paddle returns `204 No Content` with no response body. Unique Paddle ID for this notification, prefixed with `ntfset_`. ## Troubleshooting If you're having trouble creating, updating, or activating a notification destination, check that: - The webhook endpoint URL or email address you're using is correctly formatted. - You've not exceeded the limit for active notifications. You can have up to 10 active notification destinations. [Deactivate a notification destination](#deactivate-a-notification-destination) before creating a new one. - You set the correct notification type. Simulated events can only be delivered to webhook destinations and can't be delivered to email destinations. Change the **Notification type** to **URL** or the **Usage type** to **Platform only** in the dashboard, or change `type` to `url` or `traffic_source` to `platform` in the API request. If you're not receiving **either type** of notification: - Check that you subscribed to the events you're expecting to receive. You'll only receive notifications for events that you checked when configuring the notification destination. This includes [simulated events](/webhooks/simulator/test-webhooks), too. Simulated events can't be sent to email destinations. - Check that you entered the correct email address or webhook endpoint URL. If you're not receiving **webhook** notifications: - Review your webhook logs to see if there are any delivery errors or timeout issues. Go to **Paddle > Developer tools > Notifications**, click the button next to a destination in the list, then choose View logs to review. - Make sure the endpoint is accessible and responding with `2xx` status codes. --- # Handle webhook delivery URL: https://developer.paddle.com/webhooks/about/respond-to-webhooks Receive notifications by making sure your webhook event server is configured correctly, and responding within five seconds. Once you've created a notification destination, you should properly handle webhook delivery. This includes making sure your webhook server is configured correctly, responding to notifications promptly, and handling retries. ## Before you begin [Create a notification destination](/webhooks/about/notification-destinations) where the type is `url` (webhook), if you haven't already. ## Use an AI agent ## Allow Paddle IP addresses You should make sure that webhooks originate from a Paddle webhook IP address. We recommend adding Paddle webhook IP addresses to your allowlist, and rejecting webhooks that come from other sources. Allow different IP addresses for `sandbox` and `live` accounts: ```text 34.194.127.46 54.234.237.108 3.208.120.145 44.226.236.210 44.241.183.62 100.20.172.113 ``` ```text 34.232.58.13 34.195.105.136 34.237.3.244 35.155.119.135 52.11.166.252 34.212.5.7 ``` ## Update firewall rules If you're using a Web Application Firewall (WAF) to protect your web server from bot traffic, requests from Paddle may be blocked incorrectly. We recommend configuring your firewall to bypass bot checks on webhook endpoint paths. Additionally, use Paddle IP addresses and match `Paddle` as the user agent string to further restrict your rule. ## Configure your webhook handler To receive webhooks, make sure your webhook event server: - Uses HTTPS - Can accept `POST` requests with a JSON payload - Returns `200` within **five seconds** of receiving a request We recommend configuring your handler to process webhooks asynchronously by queueing received events and processing them in order. This helps prevent a large spike in webhooks from overwhelming your server. ## Respond to events The server that you set to receive events from Paddle should respond with an HTTP `200` status code **within five seconds**. This lets Paddle know that you successfully received the message. You should respond before doing any internal processing. For example, if you use a webhook to update a record in a third-party system, respond with a `200` before running any logic to communicate with the third-party solution. If you're running your webhook handler on Vercel Serverless Functions, AWS Lambda, Google Cloud Functions, or similar serverless platforms, consider pre-warming your function to prevent cold starts that might cause intermittent timeouts. We can't guarantee the order of delivery for webhooks. They may be delivered in a different order to the order they're generated. Store and check the `occurred_at` date against a webhook before making changes. ## Handle retries If your server sends another kind of status code or doesn't respond within five seconds, Paddle automatically retries using an exponential backoff schedule: - For sandbox accounts, we retry 3 times within 15 minutes. - For live accounts, we retry 60 times within 3 days. The first 20 attempts happen in the first hour, with 47 in the first day and 60 in total. Use [an exponential backoff calculator](https://exponentialbackoffcalculator.com/) to visualize retries from the date now. Use these values: `60` `60` `1.1` You can check the status of a webhook and see delivery attempts using the Paddle dashboard, or by using the [list logs for a notification operation](/api-reference/notification-logs/list-notification-logs) in the Paddle API. When all attempts to deliver a webhook are exhausted, its status is set to `failed`. You can attempt to redeliver a notification using the [replay a notification operation](/api-reference/notifications/replay-notification) in the Paddle API. ## Verify webhook signatures Use the `Paddle-Signature` header included with each webhook to [verify that received events](/webhooks/about/signature-verification) are genuinely sent by Paddle. ## Test your handler ### Send simulated webhooks to your handler Test your webhook handler by sending simulated events to your endpoint using the [webhook simulator](/webhooks/simulator/test-webhooks). You can customize payloads, inspect event details, and replay simulations as part of your testing process. ### Forward events to a local endpoint Notification destinations require public-facing URLs. If you're developing locally, you can expose your local development server to the internet using a service like [Hookdeck CLI](https://hookdeck.com/docs/cli): 1. Install [Hookdeck CLI](https://hookdeck.com/docs/cli#installation). 2. Run your local server. Note the port your local server is running on. 3. Run `hookdeck listen {PORT} paddle --path {WEBHOOK_ENDPOINT_PATH}`, where `{PORT}` is the port where your local server is running and `{WEBHOOK_ENDPOINT_PATH}` is the path to your webhook handler. For example: ```bash hookdeck listen 3000 paddle --path /api/webhook ``` 4. Use the unique URL generated by Hookdeck CLI as your webhook endpoint URL when [creating or updating a notification destination](/webhooks/about/notification-destinations). [Learn more about using the Hookdeck CLI to test and replay Paddle webhooks events on the Hookdeck docs.](https://hookdeck.com/webhooks/platforms/how-to-test-and-replay-paddle-webhooks-events-on-localhost-with-hookdeck) --- # Verify webhook signatures URL: https://developer.paddle.com/webhooks/about/signature-verification Check that received events are genuinely sent from Paddle by verifying webhook signatures. This helps you be sure they haven't been tampered with in-transit. All webhooks sent by Paddle include a `Paddle-Signature` header. Paddle generates this header using a secret key that only you and Paddle know. To verify, you can use the secret key to generate your own signature for each webhook. Since only you and Paddle know the secret key, if both signatures match then you can be sure that a received event came from Paddle. ## Before you begin [Create a notification destination](/webhooks/about/notification-destinations) where the type is `url` (webhook), if you haven't already. ## Use an AI agent ## Get your endpoint secret key To verify webhooks, you'll need to get the secret key for your notification destination. Paddle generates a secret key for each notification destination that you create. If you've created more than one notification destination, get keys for each notification destination that you want to verify signatures for. 1. Go to **Paddle > Developer tools > Notifications.** 2. Click the button next to a notification destination in the list, then choose Edit destination from the menu. 3. Click the copy icon in the secret key field to copy it. ![Illustration showing the edit destination screen. The secret key field is spotlighted.](/src/assets/images/tmp/copy-webhook-secret-key-20240923.svg) Send a GET request to the `/notification-settings/{notification_setting_id}` endpoint. If successful, Paddle returns the notification destination settings, including the `endpoint_secret_key`. #### Response For example: ## Ways that you can verify Verify webhook signatures in one of two ways: - [**Verify using Paddle SDKs (recommended)**](#verify-using-paddle-sdks) Use helper functions or classes in our official SDKs to verify webhook signatures. - [**Verify manually**](#verify-manually) Build your own logic to verify webhook signatures. ## Verify using Paddle SDKs Use our official SDKs to verify webhook signatures. You'll need to provide the event payload, `Paddle-Signature` header, and the endpoint secret key. You can use a middleware to verify the signature of an incoming request before processing it. You can also verify the signature of an incoming request manually. Learn more and clone `@PaddleHQ/paddle-go-sdk` on GitHub Learn more and clone `@PaddleHQ/paddle-node-sdk` on GitHub Learn more and clone `@PaddleHQ/paddle-php-sdk` on GitHub Learn more and clone `@PaddleHQ/paddle-python-sdk` on GitHub To prevent replay attacks, our SDK helper methods check the timestamp (`ts`) against the current time and reject events that are too old. The default tolerance between the timestamp and the current time is five seconds. ## Verify manually ### Get Paddle-Signature header First, get the `Paddle-Signature` header from an incoming webhook sent by Paddle. All webhook events sent by Paddle include a `Paddle-Signature` header. For example: ```text ts=1671552777;h1=eb4d0dc8853be92b7f063b9f3ba5233eb920a09459b6e6b2c26705b4364db151 ``` Signatures include two parts, separated by a semicolon: Timestamp as a Unix timestamp. Webhook event signature. Signatures contain at least one `h1`. We may add support for secret rotation in the future. During secret rotation, more than one `h1` is returned while secrets are rotated out. ### Extract timestamp and signature from header Now you have the `Paddle-Signature` header, parse it to extract the timestamp (`ts`) and signature values (`h1`). You can do this by splitting using a semicolon character (`;`) to get elements, then splitting again using an equals sign character (`=`) to get key-value pairs. To prevent replay attacks, you may like to check the timestamp (`ts`) against the current time and reject events that are too old. Our SDKs have a default tolerance of five seconds between the timestamp and the current time. ### Build signed payload Paddle creates a signature by first concatenating the timestamp (`ts`) with the body of the request, joined with a colon (`:`). Build your own signed payload by concatenating: - The extracted timestamp (`ts`) + - A colon (`:`) + - The raw body of the request ### Hash signed payload Next, hash your signed payload to generate a signature. Paddle generates signatures using a keyed-hash message authentication code (HMAC) with SHA256 and a secret key. Compute the HMAC of your signed payload using the SHA256 algorithm, using the secret key for this notification destination as the key. This should give you the expected signature of the webhook event. ### Compare signatures Finally, compare the signature within the `Paddle-Signature` header (the value of `h1`) to the signature you just computed in the previous step. If they don't match, you should reject the webhook event. Someone may be sending malicious requests to your webhook endpoint. ### Complete examples These code snippets demonstrate how to verify webhooks manually. ## Test signature verification You can [send a simulated webhook](/webhooks/simulator/test-webhooks) to test that your webhook signature verification is working. Webhook simulator sends an exact replica of a webhook request, including the `Paddle-Signature` header, to the URL you provide. --- # Simulate webhooks URL: https://developer.paddle.com/webhooks/simulator/test-webhooks Use webhook simulator to send test webhooks for single events or predefined scenarios as part of testing and integration. Use webhook simulator to send test webhooks for single events or predefined scenarios without triggering real transactions. ## Before you begin [Create a notification destination](/webhooks/about/notification-destinations) where the type is `url` (webhook), if you haven't already. Only destinations with the **Usage type** (`traffic_source`) of **Platform and simulation** (`all`) or **Simulation** (`simulation`) work with webhook simulator. Platform destinations can only receive traffic for real events to avoid sending simulated data to production endpoints. ## Use an AI agent ## Create a simulation 1. Go to **Paddle > Developer tools > Simulations**. 2. Click New simulation. 3. Choose where to send simulated events using the **Destination** dropdown box. You can [create a new notification destination](/webhooks/about/notification-destinations) if you don't already have one. 4. Enter a descriptive name for your simulation. You can customize payloads to send specific data, so a descriptive name helps you to identify this simulation. 5. Click the **Single event** or **Scenario** tab, then choose the simulated data you want to receive. 6. Click Create when you're done. ![Illustration showing the new simulation drawer in the dashboard. It shows fields for destination and name, followed by a toggle for single event or scenario.](/src/assets/images/tmp/procedure-new-simulation-20240919.svg) Create a simulation using the API in two steps: 1. **List simulation types** Query the API to learn about the events and scenarios you can simulate. 2. **Create your simulation** Build and send a request with the event or scenario you want to simulate. ### List simulation types [Simulation types](/api-reference/simulation-types) are events and scenarios that Paddle can send simulated events for. Send a `GET` request to the `/simulation-types` endpoint to get a list of all simulation types. Review the response and extract `data[].name` for the event or scenario you want to simulate. ### Create your simulation Build a request that includes: - `notification_setting_id`: the Paddle ID of a notification destination with `traffic_source` of `simulation` or `all`. - `type`: the name of the simulation type you want to simulate. - `name` (optional): a descriptive name. If omitted, Paddle uses the event or scenario name. For single event simulations, you can also include a `payload` object to simulate a specific payload. To learn more, see [Customize a simulation payload](#customize-a-simulation-payload). ## Configure a simulation You can configure scenarios to populate simulated webhook payloads with real data and test different flows. This only customizes scenario event simulations. For single event simulations, you can [customize payloads](#customize-a-simulation-payload) after you've run a simulation. 1. Go to **Paddle > Developer tools > Simulations**. 2. Find your simulation in the list, then click the button and choose View. If you haven't already created a simulation, [create one instead](#create-a-simulation). 3. Click Configure simulation. 4. Select from **Simulate as if** options to simulate which events are sent as if that use-case happened. 5. Add [Paddle IDs](/api-reference/about/paddle-ids) in **Populate payloads with** fields to populate sent event payloads with the details of those entities. 6. Click Done when you're done. 7. [Run the simulation](#run-a-simulation) to send events. ![Illustration showing the configure scenario drawer for webhook simulations. It shows options to configure a subscription creation simulation with customer details.](/src/assets/images/tmp/scenario-config-how-to-20250306.avif) Send a PATCH request to the `/simulations/{simulation_id}` endpoint that includes a `config` object with `entities` and `options` for your scenario. Each scenario type has its own configuration object — provide the one that matches your simulation's `type`. - **Entities** let you populate simulated webhook payloads with your real data. - **Options** let you control what flow happens in the specific scenario. This example updates a [subscription renewal scenario simulation](/webhooks/simulator/subscription-renewed) where events are simulated as if: - Payment fails when renewing - The subscription pauses when all payment recovery attempts are exhausted - Payloads for all simulated events use details from the provided subscription If successful, Paddle updates the simulation entity with your configuration. ## Run a simulation 1. Go to **Paddle > Developer tools > Simulations**. 2. Find your simulation in the list, then click the button and choose View. If you haven't already created a simulation, [create one instead](#create-a-simulation). 3. Click Run simulation to run your simulation. If you've already run your simulation before, click Replay to run it again. 4. Use the **Payload**, **Request**, and **Response** tabs to [work with simulated events](#work-with-simulated-events). ![Illustration showing the event simulation page in the dashboard. It shows a simulation with no runs. It shows a button that says run simulation.](/src/assets/images/tmp/procedure-run-simulation-20240920.svg) You can [verify signatures](/webhooks/about/signature-verification) for your simulation to check that events are genuinely sent by Paddle. Click **View more** to reveal your webhook secret key. Send a `POST` request to the `/simulations/{simulation_id}/runs` endpoint. If successful, Paddle creates a simulation run entity and sends the simulated events. You can [verify signatures](/webhooks/about/signature-verification) for simulated events to check that they're genuinely sent by Paddle and to test your signature verification logic. ## Customize a simulation payload You can customize the payload for a single event simulation to send specific data as part of your request. Paddle saves your simulated data, so you can use it in the future. This only customizes payloads for single event simulations. For scenario simulations, you can populate payloads with existing Paddle entities by [configuring those simulations](#configure-a-simulation). 1. Go to **Paddle > Developer tools > Simulations**. 2. Find your simulation in the list, then click the button and choose View. If you haven't already created a simulation, [create one instead](#create-a-simulation). 3. If you haven't run your simulation before, [run it](#run-a-simulation). 4. Click the Payload tab in the preview pane, then click the payload to start editing. 5. Click Replay to run your simulation with the customized payload. ![Illustration showing the event simulation page in the dashboard. It shows an address payload, with the line status:archived highlighted to show that it's being edited.](/src/assets/images/tmp/procedure-customize-simulation-data-20240920.svg) Paddle checks the data you enter is valid JSON and that values for fields are the correct type. However, you can still enter incorrect data, like passing transaction totals that don't add up correctly. Send a PATCH request to the `/simulations/{simulation_id}` endpoint that includes a `payload` object. Your custom payload should match the schema for the event type — for example, if simulating `address.created`, your payload should match the [address entity](/api-reference/addresses). Set `payload` to `null` to clear. Paddle sends a sample payload in future runs. If successful, Paddle updates the simulation entity with your custom payload. ## Work with simulated events Paddle creates a [simulation run event](/api-reference/simulation-run-events) for the event or events that are part of a simulation. 1. Go to **Paddle > Developer tools > Simulations**. 2. Find your simulation in the list, then click the button and choose View. If you haven't already created a simulation, [create one instead](#create-a-simulation). 3. If you haven't run your simulation before, [run it](#run-a-simulation). 4. Inspect the **Payload**, **Request**, and **Response** using the tabs in the preview pane. If you're working with a scenario, click the events on the left to see information about a single event sent as part of this scenario. ![Illustration showing the simulation page in the dashboard. It shows a scenario, with tabs on the left meant to represent simulated events and an address payload on the right.](/src/assets/images/tmp/procedure-list-events-20240920.svg) The simulated data sent by Paddle. Returned by the `data` object in simulated events. The complete simulated event request sent by Paddle. Includes simulated fields for the event, like `occurred_at` and `event_id`. The status code and body of the response received from the destination server. May be blank where a body isn't returned. Send a `GET` request to the `/simulations/{simulation_id}/runs/{simulation_run_id}/events` endpoint. If successful, the response includes the simulated data sent by Paddle and information about responses from your destination server. ## Troubleshooting If you provide an item or transaction with a trialing price, a `subscription.trialing` webhook notification is sent in place of a `subscription.activated` webhook notification. Providing a customer doesn't mean any associated business or address is automatically added. You must add a `business_id` or `address_id` manually. These must be related to the given customer. Discounts may have no effect on the cost of a transaction if the provided discount isn't valid against any of the items in the transaction. Check the discount to see its restrictions and compare to the items in the transaction. Only one `transaction.payment_failed` webhook notification is sent when you specify that the payment has failed (`payment_outcome: failed`). In live implementations, you can receive a new notification for each subsequent payment retry that fails up until dunning is exhausted. If you provide a subscription, the most recent transaction associated with that subscription is used. When you provide a subscription, Paddle sets the subscription status to best match the scenario and its configuration. This means the status may be different to the status of the subscription provided. For example, when simulating resuming a subscription, the subscription status is `paused` initially to mirror behavior in live implementations. Other data for the subscription, like the latest transaction, is used as normal. We'd love to hear from you. Let us know which flows you want to simulate using the feedback button at the top-right. --- # Webhooks URL: https://developer.paddle.com/webhooks Webhooks let you get notified for when events happen in Paddle. They're also called notifications. --- # Quickstart URL: https://developer.paddle.com/webhooks/about Everything you need to receive your first webhook from Paddle. You can use webhooks to react to events that happen in your Paddle account, like a new subscription or a renewal. Paddle sends events to a URL you provide, so you can build your own logic to handle them. This quickstart walks you through forwarding webhooks to a local server, creating a destination, and verifying signatures. It takes about five minutes. ## Before you begin You need a Paddle account. You can sign up for free: - [**Sandbox account**](https://sandbox-login.paddle.com/signup) — for testing. No real money is involved. - [**Live account**](https://login.paddle.com/signup) — for production use. Requires approval before you can process real transactions. We recommend starting in the sandbox environment. You'll also need [Node.js](https://nodejs.org) installed locally to run the example handler. ## Use an AI agent ## Install the Hookdeck CLI The [Hookdeck CLI](https://hookdeck.com/docs/cli) gives your computer a public URL and forwards incoming webhooks to a port on `localhost`. It's free for development and the fastest way to receive real webhooks without deploying anything. Install using your package manager: ```bash pnpm add -g hookdeck-cli ``` ```bash yarn global add hookdeck-cli ``` ```bash npm install hookdeck-cli -g ``` Or use Homebrew or Scoop: ```bash brew install hookdeck/hookdeck/hookdeck ``` ```bash scoop bucket add hookdeck https://github.com/hookdeck/scoop-hookdeck-cli.git scoop install hookdeck ``` Visit hookdeck.com/docs to learn more about the Hookdeck CLI ## Spin up a local listener Create a minimal webhook handler that accepts POST requests, logs the raw body, and responds with `200 OK`. Paddle retries on non-2xx responses, so returning a 2xx quickly is important. ```js import express from "express"; const app = express(); app.post( "/webhooks", express.raw({ type: "application/json" }), (req, res) => { console.log("Received webhook:", req.body.toString()); res.status(200).send("ok"); } ); app.listen(3000, () => console.log("Listening on :3000")); ``` Then, run it: ```bash node server.js ``` In a second terminal, start Hookdeck and point it at port 3000: ```bash hookdeck listen 3000 paddle-quickstart --path /webhooks ``` Hookdeck prints a public URL like `https://hkdk.events/abc123xyz`. Copy it for the next step. Keep both terminals open for the rest of the quickstart. If you stop the Hookdeck CLI, the public URL stops forwarding and webhooks will queue up rather than reach your server. ## Create a notification destination A [notification destination](/webhooks/about/notification-destinations) tells Paddle which events you want and where to send them. You can create a notification destination using the API, dashboard, or MCP server. We'll use the dashboard for this quickstart. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click New destination. 3. Set **Notification type** to **URL** and paste the Hookdeck URL from the previous step. 4. Choose the events you want to receive — for testing, `customer.created` is a good start. 5. Click Save destination. 6. Open the destination you just created and copy its **Secret key**. You'll need it for signature verification. ![Illustration of the new destination drawer in Paddle.](/src/assets/images/tmp-build/create-webhook-destination-20240912.svg) ![Illustration of the edit destination drawer with the secret key field highlighted.](/src/assets/images/tmp-build/copy-webhook-secret-key-20240912.svg) ## Send a test webhook Use [webhook simulator](/webhooks/simulator) to send a test webhook to your destination without having to take any real action, like completing a checkout. 1. Go to **Paddle > Developer tools > Notifications** and click the **Simulations** tab. 2. Click New simulation. 3. Choose your notification destination, select the `customer.created` event, and click Save. 4. Click Run on your simulation. ![Illustration showing the event simulation page in the dashboard. It shows a simulation with no runs. It shows a button that says run simulation.](/src/assets/images/tmp/procedure-run-simulation-20240920.svg) Within a second or two, you should see the event in the Hookdeck terminal and the parsed payload logged by your Node server. If nothing arrives, check that Hookdeck is still running and that the destination URL matches the one it printed. ## Verify the signature Every webhook Paddle sends includes a `Paddle-Signature` header. Verifying it on your side proves the request really came from Paddle — not from someone who found your public URL. The quickest way to verify is with one of our SDKs. Install the Node.js SDK: ```bash pnpm add @paddle/paddle-node-sdk ``` ```bash yarn add @paddle/paddle-node-sdk ``` ```bash npm install @paddle/paddle-node-sdk ``` Replace your handler with a version that uses the SDK to verify before acting on the event: Set `PADDLE_API_KEY` and `WEBHOOK_SECRET_KEY` in your environment variables. Restart your server and run the simulation again. You should now see the verified event type logged. For all supported languages and manual verification, see [Verify webhook signatures](/webhooks/about/signature-verification). ## Next steps You're set up to receive verified webhooks. These pages cover what to build next: Learn how Paddle delivers events, the webhook lifecycle, and delivery guarantees. Best practices for responding to webhooks, including retries and idempotency. See verification examples for Go, PHP, Python, Ruby, Java, and more. Manage destinations, rotate secret keys, and subscribe to more events. Use the simulator to test lifecycle scenarios like subscription renewals and failed payments. Browse the full event catalog grouped by entity to see every event Paddle sends. --- # Webhook simulator URL: https://developer.paddle.com/webhooks/simulator Understand how webhook simulator works and how simulations are created, configured, and run. Webhook simulator lets you send test webhooks to your endpoint without triggering real transactions. Use it to build and test your [webhook integrations](/webhooks/about/how-webhooks-work) without going through the steps to trigger events in a real flow. Send webhooks without affecting real data. Works in sandbox and production. Trigger a sequence of related events, like all webhooks for subscription creation. Create reusable simulations, then run them anytime with real payloads. ## Billing scenarios involve multiple webhooks Key subscription lifecycle events typically involve multiple webhooks. For example, when a customer signs up using checkout, Paddle: - Creates and updates a [customer](/api-reference/customers), [address](/api-reference/addresses), and [business](/api-reference/businesses) to hold customer information. - Creates and updates a [transaction](/api-reference/transactions) to collect payment. - Creates a [subscription](/api-reference/subscriptions) when checkout completes. Each step of the process fires its own webhook. Webhook simulator lets you send single events or predefined groups of events, called scenarios, without going through the steps to make them occur yourself. ## Single events vs. scenarios A **simulation** is a reusable configuration of events that you want to test. There are two kinds of simulations: - **Single events** simulate one webhook event. You can [customize the payload](/webhooks/simulator/test-webhooks) after running to send specific data. - **Scenarios** simulate a predefined sequence of events for a lifecycle flow, like a subscription creation or renewal. You can [configure scenarios](/webhooks/simulator/test-webhooks) to populate payloads with real entities and test different flows. ## Configuration You can configure scenarios to match your real flows and test different scenarios. When configuring, you can include: - **Entities** Populate simulated webhook payloads with your real data. They map to your existing Paddle entities, like [customers](/api-reference/customers) and [subscriptions](/api-reference/subscriptions). - **Options** Control what flow occurs in the scenario, including which webhooks are sent and the details they contain. Options relate to user actions or entity field values at the time the scenario takes place. For example, you can: - Provide a customer ID to populate simulated events with that customer's details. - Set a payment outcome of `failed` to simulate a subscription renewal where payment doesn't go through. Providing entities only populates payload data. For example, providing a customer ID doesn't change which webhooks are sent. To change which webhooks are sent during a simulated flow, use options. ## Simulation lifecycle Once you've created and configured a simulation, you can run it as many times as you want as part of your testing workflows. ```mermaid flowchart LR A[Create simulation] --> B["Configure\n(scenarios only, optional)"] B --> C[Run simulation] C --> D[Work with events] D -->|Run again| C ``` 1. **Create a simulation** Tell Paddle which event or predefined sequence to simulate, and which notification destination to send events to. 2. **Configure a simulation** For scenario simulations, optionally populate payloads with your real Paddle entities and choose which flow to simulate. 3. **Run your simulation** Trigger the event or events. Paddle creates a [simulation run entity](/api-reference/simulation-runs) for each run. 4. **Work with simulated events** Inspect each event's payload, request, and your server's response. Paddle creates a [simulation run event entity](/api-reference/simulation-run-events) for each event sent. --- # address.created URL: https://developer.paddle.com/webhooks/addresses/address-created Occurs when an address is created. Occurs when an address is created. Paddle creates addresses automatically when a customer provides their details at checkout. You can also create addresses using the API. Payload includes the complete address entity. --- # address.imported URL: https://developer.paddle.com/webhooks/addresses/address-imported Occurs when an address is imported. Occurs when an address is imported from another platform, typically as part of migrating from Paddle Classic. `import_meta` is populated with information about the source of the import. --- # address.updated URL: https://developer.paddle.com/webhooks/addresses/address-updated Occurs when an address is updated. Occurs when an address is updated, for example when any of its address lines, city, region, postal code, or country change. Payload includes the complete address entity. --- # adjustment.created URL: https://developer.paddle.com/webhooks/adjustments/adjustment-created Occurs when an adjustment is created. Occurs when an adjustment is created. Adjustments are used to refund or credit all or part of a completed or billed transaction. The `action` field describes the type of adjustment: | Action | Description | |-------------------------|-------------| | `credit` | Credits some or all of the related transaction. Doesn't require Paddle approval. | | `refund` | Refunds some or all of the related transaction. Most refunds for live accounts require Paddle approval and are created as `pending_approval`. [`adjustment.updated`](/webhooks/adjustments/adjustment-updated) occurs when the status changes to `approved` or `rejected`. | | `chargeback`, `chargeback_warning`, and related | Created automatically by Paddle when a customer disputes a charge with their payment provider. | --- # adjustment.updated URL: https://developer.paddle.com/webhooks/adjustments/adjustment-updated Occurs when an adjustment is updated. Occurs when an adjustment is updated. This only occurs for `refund` adjustments, which must be approved by Paddle and are created as `pending_approval` initially. On review, they move to `approved` or `rejected` and `adjustment.updated` occurs. --- # api_key_exposure.created URL: https://developer.paddle.com/webhooks/api-keys/api-key-exposure-created Occurs when an API key has been exposed. Occurs when Paddle detects that an API key has been publicly exposed, for example in a public code repository. Paddle automatically revokes the exposed key. [`api_key.revoked`](/webhooks/api-keys/api-key-revoked) occurs immediately after. --- # api_key.created URL: https://developer.paddle.com/webhooks/api-keys/api-key-created Occurs when an API key is created. Occurs when an API key is created. Subscribe to this event to audit API key creation across your account. As the key moves through its lifecycle, [`api_key.updated`](/webhooks/api-keys/api-key-updated), [`api_key.expiring`](/webhooks/api-keys/api-key-expiring), [`api_key.expired`](/webhooks/api-keys/api-key-expired), or [`api_key.revoked`](/webhooks/api-keys/api-key-revoked) may occur. --- # api_key.expired URL: https://developer.paddle.com/webhooks/api-keys/api-key-expired Occurs when an API key has expired. Occurs when an API key has reached its expiration date. The key's `status` changes to `expired` and it can no longer be used to authenticate API requests. --- # api_key.expiring URL: https://developer.paddle.com/webhooks/api-keys/api-key-expiring Occurs when an API key expires in 7 days. Occurs when an API key is within 7 days of its expiration date. Use this event to rotate or renew the key before it expires to avoid disrupting API requests. [`api_key.expired`](/webhooks/api-keys/api-key-expired) occurs when it reaches its expiration date. --- # api_key.revoked URL: https://developer.paddle.com/webhooks/api-keys/api-key-revoked Occurs when an API key has been revoked. Occurs when an API key is revoked. Revoked keys can no longer be used to authenticate API requests. A key may be revoked manually, or automatically following an [`api_key_exposure.created`](/webhooks/api-keys/api-key-exposure-created) event. --- # api_key.updated URL: https://developer.paddle.com/webhooks/api-keys/api-key-updated Occurs when an API key is updated. Occurs when an API key is updated, for example when its name, description, or permissions change. --- # business.created URL: https://developer.paddle.com/webhooks/businesses/business-created Occurs when a business is created. Occurs when a business is created. Paddle creates a business automatically when a customer provides their business details at Paddle Checkout. You can also create businesses using the API. --- # business.imported URL: https://developer.paddle.com/webhooks/businesses/business-imported Occurs when a business is imported. Occurs when a business is imported from another platform, typically as part of migrating from Paddle Classic. `import_meta` is populated with information about the source of the import. --- # business.updated URL: https://developer.paddle.com/webhooks/businesses/business-updated Occurs when a business is updated. Occurs when a business is updated, for example when its name, tax identifier, or contacts change. --- # client_token.created URL: https://developer.paddle.com/webhooks/client-tokens/client-token-created Occurs when a client-side token is created. Occurs when a client-side token is created. Client-side tokens are used to authenticate Paddle.js. Pass the `token` field as the `token` parameter when initializing Paddle.js. [`client_token.updated`](/webhooks/client-tokens/client-token-updated) or [`client_token.revoked`](/webhooks/client-tokens/client-token-revoked) may occur later in the token's lifecycle. --- # client_token.revoked URL: https://developer.paddle.com/webhooks/client-tokens/client-token-revoked Occurs when a client-side token is revoked. Occurs when a client-side token is revoked. Its `status` changes to `revoked` and it can no longer be used to authenticate Paddle.js. --- # client_token.updated URL: https://developer.paddle.com/webhooks/client-tokens/client-token-updated Occurs when a client-side token is updated. Occurs when a client-side token is updated, for example when its name, description, or status changes. --- # customer.created URL: https://developer.paddle.com/webhooks/customers/customer-created Occurs when a customer is created. Occurs when a customer is created. Paddle creates customers automatically when someone provides their details at Paddle Checkout. You can also create customers using the API. --- # customer.imported URL: https://developer.paddle.com/webhooks/customers/customer-imported Occurs when a customer is imported. Occurs when a customer is imported from another platform, typically as part of migrating from Paddle Classic. `import_meta` is populated with information about the source of the import. --- # customer.updated URL: https://developer.paddle.com/webhooks/customers/customer-updated Occurs when a customer is updated. Occurs when a customer is updated, for example when their name, email, locale, or marketing preferences change. --- # discount_group.created URL: https://developer.paddle.com/webhooks/discount-groups/discount-group-created Occurs when a discount group is created. Occurs when a discount group is created. Discount groups are used to categorize and organize discounts together. Group names are not shown to customers. [`discount_group.updated`](/webhooks/discount-groups/discount-group-updated) occurs if the group is edited later. Payload includes the complete discount_group entity. --- # discount_group.updated URL: https://developer.paddle.com/webhooks/discount-groups/discount-group-updated Occurs when a discount group is updated. Occurs when a discount group is updated, for example when its name or status changes. --- # discount.created URL: https://developer.paddle.com/webhooks/discounts/discount-created Occurs when a discount is created. Occurs when a discount is created. Payload includes the complete discount entity, except `times_used`. This field changes frequently, so isn't included in payloads. [Get a discount using the API](https://developer.paddle.com/api-reference/discounts/get-discount) to see the latest value. --- # discount.imported URL: https://developer.paddle.com/webhooks/discounts/discount-imported Occurs when a discount is imported. Occurs when a discount is imported. Payload includes the complete discount entity, except `times_used`. This field changes frequently, so isn't included in payloads. [Get a discount using the API](https://developer.paddle.com/api-reference/discounts/get-discount) to see the latest value. --- # discount.updated URL: https://developer.paddle.com/webhooks/discounts/discount-updated Occurs when a discount is updated. Payload includes the complete discount entity, except `times_used`. This field changes frequently, so isn't included in payloads. [Get a discount using the API](https://developer.paddle.com/api-reference/discounts/get-discount) to see the latest value. Occurs when a discount is updated. Payload includes the complete discount entity, except `times_used`. This field changes frequently, so isn't included in payloads. [Get a discount using the API](https://developer.paddle.com/api-reference/discounts/get-discount) to see the latest value. --- # payment_method.deleted URL: https://developer.paddle.com/webhooks/payment-methods/payment-method-deleted Occurs when a payment method is deleted. Occurs when a saved payment method is deleted. The `deletion_reason` field describes why it was deleted: - `replaced_by_newer_version` — the customer provided updated payment details that matched this payment method, so Paddle created a replacement and deleted this one. - `api` — the payment method was deleted using the API. --- # payment_method.saved URL: https://developer.paddle.com/webhooks/payment-methods/payment-method-saved Occurs when a payment method is saved. Occurs when a payment method is saved for a customer. The `origin` field describes how it was saved: - `saved_during_purchase` — the customer chose to save their payment method while purchasing a one-time item. - `subscription` — the customer purchased a subscription, so their payment method was saved automatically for future renewals. - `subscription_saved_during_purchase` — the customer chose to save their payment method when purchasing a subscription. --- # payout.created URL: https://developer.paddle.com/webhooks/payouts/payout-created Occurs when a payout is initiated by Paddle. Occurs when a payout is initiated by Paddle. The payout `status` starts as `unpaid`. [`payout.paid`](/webhooks/payouts/payout-paid) occurs when the payout is processed and funds are on their way to your bank account. --- # payout.paid URL: https://developer.paddle.com/webhooks/payouts/payout-paid Occurs when a payout is paid. Occurs when a payout is paid. This is usually the working day before the funds credit your account. --- # price.created URL: https://developer.paddle.com/webhooks/prices/price-created Occurs when a price is created. Occurs when a price is created. `created_at` and `updated_at` may be `null` in events that occurred before this field was added to price entities. --- # price.imported URL: https://developer.paddle.com/webhooks/prices/price-imported Occurs when a price is imported. Occurs when a price is imported. `created_at` and `updated_at` may be `null` in events that occurred before this field was added to price entities. --- # price.updated URL: https://developer.paddle.com/webhooks/prices/price-updated Occurs when a price is updated. Occurs when a price is updated. `created_at` and `updated_at` may be `null` in events that occurred before this field was added to price entities. --- # product.created URL: https://developer.paddle.com/webhooks/products/product-created Occurs when a product is created. Occurs when a product is created. `updated_at` may be `null` in events that occurred before this field was added to product entities. --- # product.imported URL: https://developer.paddle.com/webhooks/products/product-imported Occurs when a product is imported. Occurs when a product is imported. `updated_at` may be `null` in events that occurred before this field was added to product entities. --- # product.updated URL: https://developer.paddle.com/webhooks/products/product-updated Occurs when a product is updated. Occurs when a product is updated. `updated_at` may be `null` in events that occurred before this field was added to product entities. --- # report.created URL: https://developer.paddle.com/webhooks/reports/report-created Occurs when a report is created. Occurs when a report is created. Reports are created with a `status` of `pending` while Paddle generates them. [`report.updated`](/webhooks/reports/report-updated) occurs when the status changes to `ready` and the report is available to download. --- # report.updated URL: https://developer.paddle.com/webhooks/reports/report-updated Occurs when a report is updated. Occurs when a report is updated. A report moves from `pending` to `ready` when Paddle has finished generating it and it's available to download. --- # Subscription canceled scenario URL: https://developer.paddle.com/webhooks/simulator/subscription-canceled Simulates all events that occur when a subscription is canceled by a customer. This page describes a webhook scenario with configurable inputs. Each event below lists the conditions under which it fires; combine the per-step and per-event conditions to determine what fires for any given configuration. ## Configuration - **Subscription cancels effective from** (`effective_from`) — `immediately` *(default)* or `next_billing_period` (End of the billing period). Whether the simulated subscription should be canceled immediately or at the end of the billing period. - **Subscription has past due transactions** (`has_past_due_transaction`) — `false` *(default)* or `true`. Whether the simulated subscription has past due transactions which need to be canceled. ## Events ### Subscription is scheduled to cancel *Only when Subscription cancels effective from is "End of the billing period".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — Paddle updates the `scheduled_change` object against the subscription to say the subscription is set to cancel. ### Subscription cancels - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — *When Subscription cancels effective from is "Immediately":* Paddle updates billing dates for the subscription and any items. Its status changes to `canceled`. - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — *When Subscription cancels effective from is "End of the billing period":* When `scheduled_change.effective_from` elapses, Paddle updates billing dates for the subscription and any items. Its status changes to `canceled`. - [`subscription.canceled`](/webhooks/subscriptions/subscription-canceled) — Occurs because the subscription status changes to `canceled`. ### Past due transactions are canceled *Only when Subscription has past due transactions is enabled.* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — When a subscription has transactions with the `origin` of `subscription_recurring` and the status of `past_due`, Paddle automatically cancels them as the related subscription has been canceled. The `status` of the transaction is changed to `canceled`. - [`transaction.canceled`](/webhooks/transactions/transaction-canceled) — Occurs because the past due transaction status changes to `canceled`. --- # Subscription created scenario URL: https://developer.paddle.com/webhooks/simulator/subscription-created Simulates all events that occur when a subscription is created via checkout. This page describes a webhook scenario with configurable inputs. Each event below lists the conditions under which it fires; combine the per-step and per-event conditions to determine what fires for any given configuration. ## Configuration - **Customer** (`customer_simulated_as`) — `new` *(default)*, `existing_email_matched` (Existing - entered at checkout), or `existing_details_prefilled`. Whether the simulated subscription is for a new customer, an existing customer, or an existing customer with details prefilled. - **Business** (`business_simulated_as`) — `not_provided` *(default)*, `new`, or `existing_details_prefilled`. Whether to simulate if a new, existing, or no business is provided at checkout. - **Discount** (`discount_simulated_as`) — `not_provided` *(default)*, `entered_by_customer`, or `prefilled`. Whether to simulate if a discount is entered, prefilled, or not provided at checkout. - **Subscription trials** (`trial_period_on_price`) — `false` *(default)* or `true`. Whether to simulate if prices in the transaction items have a trial period. ## Events ### Customer opens checkout - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Customer is not "Existing - details prefilled" and Business is not "Existing - details prefilled" and Discount is not "Prefilled":* Paddle creates a transaction for the items on the checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Customer is "Existing - details prefilled" and Business is not "Existing - details prefilled" and Discount is not "Prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the customer passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Business is "Existing - details prefilled" and Customer is not "Existing - details prefilled" and Discount is not "Prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the business passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Discount is "Prefilled" and Customer is not "Existing - details prefilled" and Business is not "Existing - details prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the discount (including total calculations) passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Customer is "Existing - details prefilled" and Business is "Existing - details prefilled" and Discount is not "Prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the customer and business passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Customer is "Existing - details prefilled" and Discount is "Prefilled" and Business is not "Existing - details prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the customer and discount (including total calculations) passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Business is "Existing - details prefilled" and Discount is "Prefilled" and Customer is not "Existing - details prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the business and discount (including total calculations) passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. - [`transaction.created`](/webhooks/transactions/transaction-created) — *When Customer is "Existing - details prefilled" and Business is "Existing - details prefilled" and Discount is "Prefilled":* Paddle creates a transaction for the items on the checkout. It contains details of the customer, business, and discount (including total calculations) passed to Paddle.js and prefilled at checkout. Its status is initially `draft`. Its origin is `web`. ### Transaction ready for payment *Only when Customer is "Existing - details prefilled".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — Paddle updates the transaction with the customer and address. The transaction status is `ready` because the transaction has customer and address information. - [`transaction.ready`](/webhooks/transactions/transaction-ready) — Occurs because the transaction status changes to `ready`. ### Customer adds their details and address *Only when Customer is not "Existing - details prefilled".* - [`customer.created`](/webhooks/customers/customer-created) — *When Customer is "New":* Paddle creates a new customer with the information provided by the customer. The customer's status is `active`. - [`address.created`](/webhooks/addresses/address-created) — When a customer enters their country and ZIP/postal code, Paddle always creates a new address related to this customer. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — Paddle updates the transaction with the customer and address. The transaction status is `ready` because the transaction has customer and address information. - [`transaction.ready`](/webhooks/transactions/transaction-ready) — Occurs because the transaction status changes to `ready`. ### Customer adds their business details *Only when Business is "New".* - [`business.created`](/webhooks/businesses/business-created) — When a customer enters their business details, Paddle creates a business. - [`address.updated`](/webhooks/addresses/address-updated) — Customers must provide an address when entering details of their business. Paddle updates the existing address associated to the customer. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — Paddle updates the transaction with the new business that was just created. Totals on the transaction may be updated to reflect changes in tax. ### Customer adds a discount *Only when Discount is "Entered by customer".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — Paddle updates the transaction with details of the discount (including total calculations). Also includes any changes made by the customer at checkout to items and quantities. ### Customer completes checkout successfully - [`transaction.updated`](/webhooks/transactions/transaction-updated) — The transaction status changes to `paid` now that the customer has paid successfully. The transaction is updated with information about the successful payment. - [`transaction.paid`](/webhooks/transactions/transaction-paid) — Occurs because the transaction status changes to `paid`. - [`subscription.created`](/webhooks/subscriptions/subscription-created) — *When Subscription trials is disabled:* Paddle creates a subscription for the customer, address, and business against the transaction. Its status is `active` as the prices in the transaction items have no `trial_period`. Includes a `transaction_id` field so you can match with the completed transaction. - [`subscription.created`](/webhooks/subscriptions/subscription-created) — *When Subscription trials is enabled:* Paddle creates a subscription for the customer, address, and business against the transaction. Its status is `trialing` as the prices in the transaction items have a `trial_period`. Includes a `transaction_id` field so you can match with the completed transaction. - [`subscription.activated`](/webhooks/subscriptions/subscription-activated) — *When Subscription trials is disabled:* Occurs because the subscription has no trial period and is now active. - [`subscription.trialing`](/webhooks/subscriptions/subscription-trialing) — *When Subscription trials is enabled:* Occurs because the subscription status changes to `trialing`. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — The transaction is updated with the ID of the new subscription, the billing period, and information about fees, payouts, and earnings. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — An invoice number is assigned to the transaction. Its status changes to `completed` as Paddle has finished processing it. - [`transaction.completed`](/webhooks/transactions/transaction-completed) — Occurs because the transaction status changes to `completed`. ### Payment method is saved - [`payment_method.saved`](/webhooks/payment-methods/payment-method-saved) — Occurs if the customer opted to save their payment method at checkout. --- # Subscription paused scenario URL: https://developer.paddle.com/webhooks/simulator/subscription-paused Simulates all events that occur when a subscription is paused. This page describes a webhook scenario with configurable inputs. Each event below lists the conditions under which it fires; combine the per-step and per-event conditions to determine what fires for any given configuration. ## Configuration - **Subscription pauses effective from** (`effective_from`) — `immediately` *(default)* or `next_billing_period` (End of the billing period). Whether the simulated subscription should be paused immediately or at the end of the billing period. - **Subscription has a past due transaction** (`has_past_due_transaction`) — `false` *(default)* or `true`. Whether the simulated subscription has past due transactions which need to be canceled. ## Events ### Subscription is scheduled to pause *Only when Subscription pauses effective from is "End of the billing period".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — Paddle updates the `scheduled_change` object against the subscription to say the subscription is set to pause. ### Subscription pauses - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — *When Subscription pauses effective from is "Immediately":* Paddle updates billing dates for the subscription and any items. Its status changes to `paused`. - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — *When Subscription pauses effective from is "End of the billing period":* When `scheduled_change.effective_from` elapses, Paddle updates billing dates for the subscription and any items. Its status changes to `paused`. - [`subscription.paused`](/webhooks/subscriptions/subscription-paused) — Occurs because the subscription status changes to `paused`. ### Past due transactions are canceled *Only when Subscription has a past due transaction is enabled.* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — When a subscription has transactions with the `origin` of `subscription_recurring` and the status of `past_due`, Paddle automatically cancels them to avoid double-charging a customer if they resume the subscription and a new billing period is created. The `status` of the transaction is changed to `canceled`. - [`transaction.canceled`](/webhooks/transactions/transaction-canceled) — Occurs because the past due transaction status changes to `canceled`. --- # Subscription renewed scenario URL: https://developer.paddle.com/webhooks/simulator/subscription-renewed Simulates all events that occur when a subscription renews. This page describes a webhook scenario with configurable inputs. Each event below lists the conditions under which it fires; combine the per-step and per-event conditions to determine what fires for any given configuration. ## Configuration - **Payment outcome** (`payment_outcome`) — `success` *(default)*, `recovered_existing_payment_method` (Recovered after retry with existing payment method), `recovered_updated_payment_method` (Recovered after customer updates payment method), or `failed`. What the outcome of the payment should be when the simulated subscription is renewed. - **Action after payment recovery fails** (`dunning_exhausted_action`) — `subscription_canceled` (Cancel the subscription) *(default)* or `subscription_paused` (Pause the subscription). *Only shown when Payment outcome is "Failed".* Whether the subscription cancels or pauses as a result of all payment recovery attempts being exhausted. ## Events ### Subscription renews - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — The subscription and its items are updated with new `previously_billed_at` and `next_billed_at` dates, and the `current_billing_period` is updated. - [`transaction.created`](/webhooks/transactions/transaction-created) — Paddle creates a transaction for recurring items on the subscription, as well as any prorated or one-time charges that were set to be billed on the next billing period. Its status is `billed`, meaning no changes can be made to the transaction. Its origin is `subscription_recurring`. - [`transaction.billed`](/webhooks/transactions/transaction-billed) — Occurs because the transaction status changes to `billed`. ### Initial payment collection fails *Only when Payment outcome is not "Success".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — Payment fails using the payment method on file. The transaction is updated with information about the unsuccessful payment attempt. Its status changes to `past_due`. - [`transaction.payment_failed`](/webhooks/transactions/transaction-payment-failed) — Occurs because payment fails using the payment method on file. - [`transaction.past_due`](/webhooks/transactions/transaction-past-due) — Occurs because the transaction status changes to `past_due`. - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — The subscription status changes to `past_due`. - [`subscription.past_due`](/webhooks/subscriptions/subscription-past-due) — Occurs because the payment failed for the transaction and the subscription therefore has an overdue payment. ### Customer updates their payment method *Only when Payment outcome is "Recovered after customer updates payment method".* - [`payment_method.saved`](/webhooks/payment-methods/payment-method-saved) — Occurs because the customer's payment method has been updated and saved. This results in a successful payment. ### Payment is successfully collected *Only when Payment outcome is "Success" or "Recovered after customer updates payment method".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — The transaction status changes to `paid` now that the customer has paid successfully. The transaction is updated with information about the successful payment. - [`transaction.paid`](/webhooks/transactions/transaction-paid) — Occurs because the transaction status changes to `paid`. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — An invoice number is assigned to the transaction. Its status changes to `completed` as Paddle has finished processing it. - [`transaction.completed`](/webhooks/transactions/transaction-completed) — Occurs because the transaction status changes to `completed`. ### Payment is successfully collected *Only when Payment outcome is "Recovered after retry with existing payment method".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — The existing payment method is retried and payment succeeds. The transaction status changes to `paid` now that the customer has paid successfully. The transaction is updated with information about the successful payment. - [`transaction.paid`](/webhooks/transactions/transaction-paid) — Occurs because the transaction status changes to `paid`. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — An invoice number is assigned to the transaction. Its status changes to `completed` as Paddle has finished processing it. - [`transaction.completed`](/webhooks/transactions/transaction-completed) — Occurs because the transaction status changes to `completed`. ### Subscription is activated *Only when Payment outcome is "Recovered after retry with existing payment method" or "Recovered after customer updates payment method".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — The subscription status changes to `active` now that the transaction is completed and no longer past due from payment failures. The subscription can now be considered active so you can handle access provisioning. - [`subscription.activated`](/webhooks/subscriptions/subscription-activated) — Occurs because the subscription status changes to `active`. ### Payment retries fail and the subscription is canceled *Only when Payment outcome is "Failed" and Action after payment recovery fails is "Cancel the subscription".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — All payment attempts fail and dunning options are exhausted. The subscription cancels as a result. The subscription status changes to `canceled`. - [`subscription.canceled`](/webhooks/subscriptions/subscription-canceled) — Occurs because the subscription status changes to `canceled`. ### Payment retries fail and the subscription is paused *Only when Payment outcome is "Failed" and Action after payment recovery fails is "Pause the subscription".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — All payment attempts fail and dunning options are exhausted. The subscription pauses as a result. The subscription status changes to `paused`. - [`subscription.paused`](/webhooks/subscriptions/subscription-paused) — Occurs because the subscription status changes to `paused`. --- # Subscription resumed scenario URL: https://developer.paddle.com/webhooks/simulator/subscription-resumed Simulates all events that occur when a paused subscription is resumed. This page describes a webhook scenario with configurable inputs. Each event below lists the conditions under which it fires; combine the per-step and per-event conditions to determine what fires for any given configuration. ## Configuration - **Payment outcome** (`payment_outcome`) — `success` *(default)*, `recovered_existing_payment_method` (Recovered after retry with existing payment method), `recovered_updated_payment_method` (Recovered after customer updates payment method), or `failed`. What the outcome of the payment should be when the simulated subscription is resumed. - **Action after payment recovery fails** (`dunning_exhausted_action`) — `subscription_canceled` (Cancel the subscription) *(default)* or `subscription_paused` (Pause the subscription). *Only shown when Payment outcome is "Failed".* Whether the subscription cancels or pauses as a result of all payment recovery attempts being exhausted. ## Events ### Subscription resumed - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — The subscription and its items are updated with new `previously_billed_at` and `next_billed_at` dates, and the `current_billing_period` is updated. If there was a scheduled change to resume, this is set to `null`. - [`subscription.resumed`](/webhooks/subscriptions/subscription-resumed) — Occurs because a subscription is resumed after being paused. - [`transaction.created`](/webhooks/transactions/transaction-created) — Paddle creates a transaction for recurring items on the subscription. Its status is `billed`, meaning no changes can be made to the transaction. Its origin is `subscription_recurring`. - [`transaction.billed`](/webhooks/transactions/transaction-billed) — Occurs because the transaction status changes to `billed`. ### Initial payment collection fails *Only when Payment outcome is not "Success".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — Payment fails using the payment method on file. The transaction is updated with information about the unsuccessful payment attempt. Its status changes to `past_due`. - [`transaction.payment_failed`](/webhooks/transactions/transaction-payment-failed) — Occurs because payment fails using the payment method on file. - [`transaction.past_due`](/webhooks/transactions/transaction-past-due) — Occurs because the transaction status changes to `past_due`. - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — The subscription status changes to `past_due`. - [`subscription.past_due`](/webhooks/subscriptions/subscription-past-due) — Occurs because the subscription status changed to past due. ### Customer updates their payment method *Only when Payment outcome is "Recovered after customer updates payment method".* - [`payment_method.saved`](/webhooks/payment-methods/payment-method-saved) — Occurs because the customer's payment method has been updated and saved. This results in a successful payment. ### Payment is successfully collected *Only when Payment outcome is "Success" or "Recovered after customer updates payment method".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — The transaction status changes to `paid` now that the customer has paid successfully. The transaction is updated with information about the successful payment. - [`transaction.paid`](/webhooks/transactions/transaction-paid) — Occurs because the transaction status changes to `paid`. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — An invoice number is assigned to the transaction. Its status changes to `completed` as Paddle has finished processing it. - [`transaction.completed`](/webhooks/transactions/transaction-completed) — Occurs because the transaction status changes to `completed`. ### Payment is successfully collected *Only when Payment outcome is "Recovered after retry with existing payment method".* - [`transaction.updated`](/webhooks/transactions/transaction-updated) — The existing payment method is retried and payment succeeds. The transaction status changes to `paid` now that the customer has paid successfully. The transaction is updated with information about the successful payment. - [`transaction.paid`](/webhooks/transactions/transaction-paid) — Occurs because the transaction status changes to `paid`. - [`transaction.updated`](/webhooks/transactions/transaction-updated) — An invoice number is assigned to the transaction. Its status changes to `completed` as Paddle has finished processing it. - [`transaction.completed`](/webhooks/transactions/transaction-completed) — Occurs because the transaction status changes to `completed`. ### Subscription is activated *Only when Payment outcome is not "Failed".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — The subscription status changes to `active` now that the transaction is completed. The subscription can now be considered active so you can handle access provisioning. - [`subscription.activated`](/webhooks/subscriptions/subscription-activated) — Occurs because the subscription status changes to `active`. ### Payment retries fail and the subscription is canceled *Only when Payment outcome is "Failed" and Action after payment recovery fails is "Cancel the subscription".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — All payment attempts fail and dunning options are exhausted. The subscription cancels as a result. The subscription status changes to `canceled`. - [`subscription.canceled`](/webhooks/subscriptions/subscription-canceled) — Occurs because the subscription status changes to `canceled`. ### Payment retries fail and the subscription is paused *Only when Payment outcome is "Failed" and Action after payment recovery fails is "Pause the subscription".* - [`subscription.updated`](/webhooks/subscriptions/subscription-updated) — All payment attempts fail and dunning options are exhausted. The subscription pauses as a result. The subscription status changes to `paused`. - [`subscription.paused`](/webhooks/subscriptions/subscription-paused) — Occurs because the subscription status changes to `paused`. --- # subscription.activated URL: https://developer.paddle.com/webhooks/subscriptions/subscription-activated Occurs when a subscription becomes active. Occurs when a subscription becomes active. Its `status` field changes to `active`. This means any trial period has elapsed and Paddle has successfully billed the customer. [`subscription.updated`](/webhooks/subscriptions/subscription-updated) typically follows as Paddle completes processing. [`subscription.past_due`](/webhooks/subscriptions/subscription-past-due), [`subscription.paused`](/webhooks/subscriptions/subscription-paused), or [`subscription.canceled`](/webhooks/subscriptions/subscription-canceled) may occur later in the subscription lifecycle. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.canceled URL: https://developer.paddle.com/webhooks/subscriptions/subscription-canceled Occurs when a subscription is canceled. Occurs when a subscription is canceled. Its `status` field changes to `canceled`. If you choose to cancel a subscription immediately, the subscription status changes to `canceled` and `subscription.canceled` occurs. If you choose to cancel a subscription on the next billing period, Paddle creates a scheduled change to say the subscription should be canceled on the next billing date. [`subscription.updated`](https://developer.paddle.com/webhooks/subscriptions/subscription-updated) occurs at this point. On the next billing date, the subscription status changes to `canceled` and `subscription.canceled` occurs. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.created URL: https://developer.paddle.com/webhooks/subscriptions/subscription-created Occurs when a subscription is created. Occurs when a subscription is created. Paddle automatically creates subscriptions for recurring items when automatically-collected transactions are `completed`, or when manually-collected transactions are `billed`. [`subscription.trialing`](/webhooks/subscriptions/subscription-trialing) or [`subscription.activated`](/webhooks/subscriptions/subscription-activated) typically follow. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](/api-reference/subscriptions/get-subscription) to get management links for a subscription. It also includes a `transaction_id` field, which contains the Paddle ID of the completed or billed transaction that caused Paddle to create this subscription. You can use this match the created subscription with related transaction events [as part of provisioning](/build/subscriptions/provision-access-webhooks). Other subscription events don't include this field, and it is not returned by the API when working with subscription entities. --- # subscription.imported URL: https://developer.paddle.com/webhooks/subscriptions/subscription-imported Occurs when a subscription is imported. Occurs when a subscription is imported. [`subscription.trialing`](https://developer.paddle.com/webhooks/subscriptions/subscription-trialing) or [`subscription.activated`](https://developer.paddle.com/webhooks/subscriptions/subscription-activated) typically follow. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.past_due URL: https://developer.paddle.com/webhooks/subscriptions/subscription-past-due Occurs when a subscription has an unpaid transaction. Its `status` changes to `past_due`. Occurs when a subscription has an unpaid transaction. Its `status` changes to `past_due`. Paddle retries payment according to your [payment recovery](/build/retain/configure-payment-recovery-dunning) settings. If payment succeeds, the subscription returns to `active` and [`subscription.updated`](/webhooks/subscriptions/subscription-updated) occurs. If recovery attempts are exhausted, the subscription is canceled and [`subscription.canceled`](/webhooks/subscriptions/subscription-canceled) occurs. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.paused URL: https://developer.paddle.com/webhooks/subscriptions/subscription-paused Occurs when a subscription is paused. Occurs when a subscription is paused. Its `status` field changes to `paused`. If you choose to pause a subscription immediately, the subscription status changes to `paused` and `subscription.paused` occurs. If you choose to pause a subscription on the next billing period, Paddle creates a scheduled change to say the subscription should be paused on the next billing date. [`subscription.updated`](https://developer.paddle.com/webhooks/subscriptions/subscription-updated) occurs at this point. On the next billing date, the subscription status changes to `paused` and `subscription.paused` occurs. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.resumed URL: https://developer.paddle.com/webhooks/subscriptions/subscription-resumed Occurs when a subscription is resumed after being paused. Occurs when a subscription is resumed after being paused. Its status field changes to `active`. When resumed, Paddle bills for the subscription immediately. [`transaction.created`](https://developer.paddle.com/webhooks/transactions/transaction-created) and other transaction events occur, depending on the collection mode. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.trialing URL: https://developer.paddle.com/webhooks/subscriptions/subscription-trialing Occurs when a subscription enters trial period. Occurs when a subscription enters trial period. Its `status` field is `trialing`. [`subscription.activated`](/webhooks/subscriptions/subscription-activated) typically follows when the trial ends and Paddle has successfully billed the customer. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # subscription.updated URL: https://developer.paddle.com/webhooks/subscriptions/subscription-updated Occurs when a subscription is updated. Occurs when a subscription is updated. This covers a wide range of changes, including billing detail updates, item or price changes, scheduled changes being created or removed, and status transitions that don't have a dedicated event. `subscription.updated` may also occur immediately after a dedicated lifecycle event, like [`subscription.activated`](/webhooks/subscriptions/subscription-activated) or [`subscription.canceled`](/webhooks/subscriptions/subscription-canceled), when Paddle updates additional fields as part of processing. Payload includes the complete subscription entity, except `management_urls`. Subscription management links are temporary, so they're not included. [Get a subscription using the API](https://developer.paddle.com/api-reference/subscriptions/get-subscription) to get management links for a subscription. --- # transaction.billed URL: https://developer.paddle.com/webhooks/transactions/transaction-billed Occurs when a transaction is billed. Its `status` field changes to `billed` and `billed_at` is populated. Occurs when a transaction is billed. Its `status` field changes to `billed` and `billed_at` is populated. Marking a transaction as billed is typically used when working with manually-collected transactions to issue an invoice. It's not typically part of checkout workflows, where collection mode is automatic. Transactions are marked as billed when: - You update its status to `billed` using the API. - You send an invoice (a manually-collected transaction) using the Paddle dashboard. Billed transactions get an invoice number. They're considered legal records, so they can't be deleted or changed. [`transaction.updated`](https://developer.paddle.com/webhooks/transactions/transaction-updated) events occur immediately after to add: - `invoice_number`, and `invoice_id` if not already present. - If manually-collected, the newly created `subscription_id` for any recurring items. --- # transaction.canceled URL: https://developer.paddle.com/webhooks/transactions/transaction-canceled Occurs when a transaction is canceled. Its `status` field changes to `canceled`. Occurs when a transaction is canceled. Its `status` field changes to `canceled`. Marking a transaction as canceled is typically used when working with manually-collected transactions to say that an invoice was created in error. It's not typically part of checkout workflows, where collection mode is automatic. Transactions are marked as canceled when: - You update its status to `canceled` using the API. - You cancel an invoice (a manually-collected transaction) using the Paddle dashboard. --- # transaction.completed URL: https://developer.paddle.com/webhooks/transactions/transaction-completed Occurs when a transaction is completed. Its status field changes to `completed`. Occurs when a transaction is completed. Its status field changes to `completed`. Transactions move to completed after they're `paid`. After a transaction is paid, Paddle starts completed transaction processing. This involves: - Logging details of the successful payment against `transaction.payments[]`. - Adding information about fees, earnings, and totals for payouts to the transaction entity. - For automatically-collected transactions, creating a subscription for any recurring items and adding the related `subscription_id` to the transaction entity. - For automatically-collected transactions, adding an `invoice_number` and `invoice_id` to the transaction entity. [`transaction.updated`](https://developer.paddle.com/webhooks/transactions/transaction-updated) occurs as Paddle updates a paid transaction. When all processing is completed, a transaction is marked as completed and `transaction.completed` occurs. --- # transaction.created URL: https://developer.paddle.com/webhooks/transactions/transaction-created Occurs when a transaction is created. Occurs when a transaction is created. Paddle creates a transaction when: - Customers open checkout. - You create an invoice in the Paddle dashboard. You may also create a transaction using the API: - Create a manually-collected transaction to create and send an invoice. - Create an automatically-collected transaction and pass to a checkout to collect for payment. Where a transaction has `items`, `customer_id`, and `address_id`, [`transaction.ready`](https://developer.paddle.com/webhooks/transactions/transaction-ready) occurs immediately after. --- # transaction.paid URL: https://developer.paddle.com/webhooks/transactions/transaction-paid Occurs when a transaction is paid. Its status field changes to `paid`. Occurs when a transaction is paid. Its status field changes to `paid`. Transactions are paid when payment has been captured successfully, but Paddle hasn't yet fully processed the transaction internally. For example: - Payout totals may not be present. - Automatically-collected transactions for recurring items might not yet have a `subscription_id`. - Automatically-collected transactions might not yet have an `invoice_number`. Transactions move to `completed` and [`transaction.completed`](https://developer.paddle.com/webhooks/transactions/transaction-completed) occurs when they're fully processed. --- # transaction.past_due URL: https://developer.paddle.com/webhooks/transactions/transaction-past-due Occurs when a transaction becomes past due. Its `status` field changes to `past_due`. Occurs when a transaction becomes past due. Its `status` field changes to `past_due`. - Automatically-collected transactions for subscription renewals become past due when a payment attempt fails. - Manually-collected transactions become past due when no payment has been received and the payment terms have elapsed. The status against the related subscription for a transaction also changes to `past_due`, and [`subscription.past_due`](https://developer.paddle.com/webhooks/subscriptions/subscription-past-due) occurs. --- # transaction.payment_failed URL: https://developer.paddle.com/webhooks/transactions/transaction-payment-failed Occurs when a payment fails for a transaction. Occurs when a payment fails for a transaction. The `payments` array is updated with details of the payment attempt. Typically happens for automatically-collected transactions, but may occur for manually-collected transactions (invoices) where a customer pays using Paddle Checkout and their payment is declined. If related to a subscription renewal: - The transaction status changes to `past_due` and [`transaction.past_due`](https://developer.paddle.com/webhooks/transactions/transaction-past-due) occurs. - The related subscription status changes to `past_due` and [`subscription.past_due`](https://developer.paddle.com/webhooks/subscriptions/subscription-past-due) occurs. Manually-collected transactions are marked as `past_due` when the payment terms have elapsed. --- # transaction.ready URL: https://developer.paddle.com/webhooks/transactions/transaction-ready Occurs when a transaction is ready to be billed. Its `status` field changes to `ready`. Occurs when a transaction is ready to be billed. Its `status` field changes to `ready`. Transactions are ready when they have all the required fields against them to be transitioned to `billed` or `completed`. This includes `items`, `customer_id`, and `address_id`. Paddle automatically marks transactions as `ready` when these fields are present. When working with manually-collected transactions (invoices), [`transaction.updated`](https://developer.paddle.com/webhooks/transactions/transaction-updated) may occur immediately after to add `invoice_id` and `adjusted_totals`. --- # transaction.revised URL: https://developer.paddle.com/webhooks/transactions/transaction-revised Occurs when a transaction is revised. Occurs when customer, address, or business information is revised for a billed or completed transaction. `revised_at` is set to the date and time of the revision. Billed and completed transactions are financial records, so they can't be changed directly. Revisions let you update the related customer, address, and business details without altering the underlying transaction record. [`transaction.updated`](/webhooks/transactions/transaction-updated) also occurs when a transaction is revised. --- # transaction.updated URL: https://developer.paddle.com/webhooks/transactions/transaction-updated Occurs when a transaction is updated. Occurs when a transaction is updated. Specific events occur for status changes. `transaction.updated` may also occur after a status change events to add additional fields to the transaction after Paddle has completed internal processing for a transaction. For example, [`transaction.billed`](https://developer.paddle.com/webhooks/transactions/transaction-billed) occurs when a transaction status changes to `billed`. `transaction.updated` occurs immediately after to add an `invoice_number`. --- # Paddle Billing explained for Paddle Classic users URL: https://developer.paddle.com/migrate/learn Understand what Paddle Billing is and learn more about how the migration process works. If you signed up for Paddle before **August 8, 2023**, then you have Paddle Classic. We released a new version of Paddle called Paddle Billing. It's built on the Paddle platform, but includes a totally new [API](/api-reference), [SDKs](/sdks), [webhooks](/webhooks), [JavaScript library](/paddle-js), and pages in the Paddle dashboard. These guides are for Paddle Classic customers who want to move to Paddle Billing. They explain what Paddle Billing is, how it compares to Paddle Classic, and what you'll need to do if you want to migrate. ## What's Paddle Billing? Paddle Billing is a developer-first merchant of record designed for modern SaaS, Web2App, and other companies that offer digital products. It gives you new ways to increase your revenue, retain customers, and scale your operations compared to Paddle Classic. You can think of it as an entirely new version of Paddle. It shares the same foundations as Paddle Classic, meaning it comes with fraud prevention, global tax calculation and remittance, plug and play payment methods, and access to over 200 countries and territories out-of-the-box. ## How do you get Paddle Billing? All Paddle Classic customers can activate Paddle Billing to get access to the new API, webhooks, and pages in the Paddle dashboard. Data in Paddle Billing is kept separate from your Paddle Classic data, so you won't lose any data in Paddle Classic when you turn on Paddle Billing. It doesn't cost anything until you start running transactions through it. Go to **Business account > Account settings > Get Paddle Billing** to activate Paddle Billing. ## What happens to your Paddle Classic data and integration? Paddle Billing and Paddle Classic have separate sets of data. Once you've activated Paddle Billing, you get access to a toggle that lets you switch between your Paddle Classic and Paddle Billing data. Paddle Classic and Paddle Billing share core settings, but data is separate. This means: - **Your Paddle Classic data is safe.** Nothing happens to existing subscriptions or other data in Paddle Classic. - Your Paddle Classic integration isn't affected. Customers can still make purchases using your existing integration. - You get a new set of data for Paddle Billing, but no data is migrated automatically. Pages for customers, products, subscriptions, and other entities in Paddle Billing are empty initially. This data still exists in Paddle Classic. Your existing integration will continue to work as before. Existing customer and subscription data isn't ported automatically or changed in any way. For example, customers can continue to buy items using your existing integration, and subscriptions in Paddle Classic aren't created in Paddle Billing. When you migrate to Paddle Billing from Paddle Classic, historic reporting data isn't ported. You can always switch to Paddle Classic using the toggle to work with your historic data. ## Next steps If you want to migrate, we're here to help throughout the process. To start, continue reading our migration guides. Our guides walk you through: - What's new or changed in Paddle Billing, in detail. - Why you should migrate based on how you generate revenue. - How workflows and data in Paddle Classic maps to Paddle Billing. - Technical differences between Paddle Classic and Paddle Billing. - What you need to know to build an integration with Paddle Billing. - How to port your subscription data from Paddle Classic. --- # Key features in Paddle Billing for Paddle Classic users URL: https://developer.paddle.com/migrate/learn/pathways Learn more about what you can do with Paddle Billing and why you should migrate from Paddle Classic. Paddle Billing is a reimagining of how you work with the Paddle platform, offering you more ways to maximize revenue and grow your sales volume. This guide is an overview of the key reasons to migrate from Paddle Classic to Paddle Billing. It helps you understand some of the things you'll be able to do after you've migrated. ## Product catalog Paddle Billing supports multi-product subscriptions, letting you build complex offerings including plans, recurring addons, and one-time charges on the same subscription. No need to duplicate data. Create invoices for the same products that you offer at checkout, changing the price as you move through the sales cycle. Issue compliant invoices when you're ready. Price for countries, not currencies. Use price overrides to adjust for willingness-to-pay and purchasing power — even across countries that share the same currency. Coupons, modifiers, and charges have become a unified discount entity in Billing. Choose how long discounts recur for, apply them to subscriptions, and limit at checkout. Pass products and prices directly to a subscription, checkout, or invoice to bill for one-off items, or in cases where you manage your catalog outside of Paddle. ## Customers Let customers grab and revise invoices, update their account details, and manage their own subscriptions using the customer portal. No engineering effort required. Customer information is centralized in a lightweight customer entity that's specific to your Paddle Billing account. You have full access to customer data using the dashboard and API. A single customer can have multiple addresses and businesses — useful when you're dealing with a large customer with offices in different locations. ## Billing and subscriptions Present customers with a true one-page checkout experience, collecting customer, address, business, and billing details on one screen. Sell through a checkout or issue invoices — all from the same integrated platform. Easily move customers from self-serve to sales-assisted billing, with payments by bank transfer. Create invoices for recurring products. Paddle Billing automatically sends invoices for subscription lifecycle events, like renewals, upgrades and downgrades, and other charges. Preview charging for actions like upgrading or downgrading, adding or removing addons, changing billing dates, billing one-time charges, or making other changes to a subscription. Trialing subscriptions behave like other subscriptions, so you can upgrade, downgrade, or add or remove items. Plus, extend or cut short trial dates, or activate immediately. Get more control over how pausing and resuming subscriptions works — including setting a resume date and choosing whether to charge for a new billing cycle. Never think about dunning or retention again. Retain combines world-class subscription expertise and algorithms to automatically reduce churn and increase lifetime value. ## Finance and reporting Add contacts to a business for people in accounts or finance teams. Paddle Billing automatically emails business contacts when an invoice is issued, paid, or canceled. Use adjustments to refund or credit a transaction. Grab a credit note, sometimes called a credit memo in other platforms, to send to customers as a record of a refund or credit. When you send invoices, Paddle Billing generates unique bank transfer account numbers for each customer. This means invoices can be reconciled faster. By unifying catalog and customers with checkout and invoices, Paddle Billing lets you report on invoicing revenue in the context of all your business data. Let customers update their tax number, address, or other information on completed invoices and checkouts. The original transaction remains unchanged for compliance. ## Developer experience Paddle Billing includes a new unified REST API that brings together all your billing operations. It's consistent and built to modern standards, with JSON for requests and responses. Our public API powers the dashboard, so if you can do it in the dashboard, then you can do in the API. You have complete access to your data — including customers, catalog, and transactions. A new version of Paddle.js makes it easier to work with Paddle in your frontend. Plus, install Paddle.js as a module using `npm` or other package managers, with full TypeScript support. Use our hand-crafted SDKs to make integrating a pleasure, our Next.js starter kit to get up and running quickly, and our MCP server to bring Paddle into AI-powered IDEs. Go beyond sending a test event to your webhook endpoint. Use webhook simulator to send customized multi-event scenarios, playing out all webhooks for key lifecycle events. Built around a centralized event stream, there are over 40 webhooks for the entire customer lifecycle. Payloads mirror API responses, with the ability to configure multiple endpoints. --- # Plan your migration from Paddle Classic to Paddle Billing URL: https://developer.paddle.com/migrate/plan Learn how a migration from Paddle Classic to Paddle Billing works at a high level, and get key information about what you need to know before you migrate. After you activate Paddle Billing for your account, your Paddle Classic integration continues to work as before. To move to Paddle Billing, you need to build an integration with our new platform and migrate your Classic data. This guide helps you plan how to move from Paddle Classic to Paddle Billing. It provides high level steps that walk through what you need to consider and do before you migrate. ## Your migration plan ### Activate Paddle Billing Turn on Paddle Billing for your account to get access to the new API, webhooks, and pages in the Paddle dashboard. Paddle creates a new set of data for Paddle Billing that's separate to your Paddle Classic data. Go to **Business account > Account settings > Get Paddle Billing** to activate Paddle Billing. - When you turn on Paddle Billing, your existing integration continues to work as before. Customers can continue to buy items using your existing integration. - Existing customer and subscription data isn't ported automatically or changed in any way. Any subscriptions created using your Paddle Classic integration are created in Paddle Classic as before. - You can switch between Paddle Classic and Paddle Billing in the Paddle dashboard using the toggle at the bottom-left. ### Build your product catalog [Create products and prices](/build/products/create-products-prices) in Paddle to describe the items that you offer. Products describe the items that you offer, and related prices describe how much and how often they're billed. Toggle **Paddle Billing**, then go to **Paddle > Products** to start creating products and prices. You can also use the API. Use the [Paddle MCP server](https://github.com/PaddleHQ/paddle-mcp-server/) to use an AI assistant or AI-powered IDE to create your product catalog. - If you use invoices in Paddle Classic, any products you created already exist in Paddle Billing. You'll need to create new prices. - If you plan to import subscriptions, you can map plans in Paddle Classic to products in Paddle Billing as part of the migration process. We'll create new prices for the products due to the complexity of the data. This doesn't impact the customer experience. - Currency price overrides in Paddle Classic don't have a direct equivalent in Paddle Billing. We recommend [turning on automatic currency conversion, then adding country-specific prices](/build/products/offer-localized-pricing) to prices for your core markets later. ### Integrate with your frontend Paddle Billing comes with [a new version of Paddle.js](/paddle-js), designed to handle securely capturing payment details when customers signup or make a purchase, change their payment details, or want to pay invoices by card or other payment method. Update your pricing page and checkout to use Paddle.js v2, which has new and updated methods and events. You can install Paddle.js v2 using a package manager and work with TypeScript definitions using [the Paddle.js wrapper](https://github.com/PaddleHQ/paddle-js-wrapper/). - Invoices can be paid using a checkout link in Paddle Billing, so you need to complete this step even if you only bill by invoice. You don't need to build a complete checkout in this case — just include and initialize Paddle.js on a page on your website, then set it as [your default payment link](/build/transactions/default-payment-link). - You can install Paddle.js v2 using a package manager and work with TypeScript definitions using [the Paddle.js wrapper](https://github.com/PaddleHQ/paddle-js-wrapper/). - Paddle.js supports multi-product checkouts, so there's no need to create bundle products. - If you offer one-time products and often get repeat customers, consider letting customers sign up for accounts on your website so they can [save payment methods for future purchases](/build/checkout/saved-payment-methods). - To create pay links, [create a transaction](/build/transactions/create-transaction) and pass the transaction ID to Paddle.js to open a checkout. Transaction checkouts are hosted on your website, rather than Paddle.com. ### Handle fulfillment When a customer completes a purchase, you need to handle fulfillment. Paddle Billing includes [webhooks](/webhooks), a [unified event stream](/api-reference/events), and comprehensive documentation that you can use to build your own fulfillment workflows. Build or update post-purchase and fulfillment workflows to use webhooks in Paddle Billing. - [Invoices](/build/invoices/create-issue-invoices) in Paddle Billing are transactions where the collection mode is manual. If you used invoice webhooks for fulfillment in Paddle Classic, use [transaction webhooks](/webhooks/transactions/transaction-completed) in Paddle Billing. - Product delivery and license key generation aren't handled by Paddle in Paddle Billing. We recommend building this functionality yourself. - You can use the [customer portal](/concepts/sell/customer-portal) to let customers grab invoices and revise invoice details. - If you offer subscriptions, we recommend creating new tables in your database to hold Paddle Billing subscriptions rather than enriching existing subscription records. ### Build subscription lifecycle workflows Customers need a way to make changes to their subscription, including canceling, upgrading and downgrading, or changing their billing details. Paddle Billing comes with more ways to manage subscriptions using the API, and the [customer portal](/concepts/sell/customer-portal) that you can integrate instead of building workflows yourself. Build or update workflows for subscription lifecycle events, like letting customers upgrade or downgrade, pause or resume, or cancel their subscription. - As part of the migration process, you'll need to run Paddle Classic and Paddle Billing alongside each other for a short while. We recommend building your workflows in a way that means you can easily remove your Paddle Classic logic after migration is completed. - Paddle Billing has [four SDKs that you can use](/sdks), as well as [a Next.js starter kit](https://billing.new/) that includes core billing management. - You can integrate [the customer portal](/concepts/sell/customer-portal) to let customers cancel their subscription and update their payment details. - You can use [webhook simulator](/webhooks/simulator) to simulate complex scenarios, like new signups and dunning workflows. ### Start transacting through Paddle Billing When you're ready, start transacting using Paddle Billing. This means that you run customers through your [Paddle Billing checkout](/concepts/sell/self-serve-checkout), or use [the invoicing functionality](/concepts/sell/sales-assisted-invoice) in Paddle Billing. If you're porting subscription data, you'll need to run Paddle Classic and Paddle Billing alongside each other for a short while. Start processing new subscriptions through Paddle Billing, while routing customers in Paddle Classic to your existing subscription workflows. - If you [create and issue invoices](/build/invoices/create-issue-invoices) using Paddle Billing, the invoice numbering sequence carries over from Paddle Classic. This means you can maintain sequential and gapless numbering, required for compliance. - For invoicing, [customers, addresses, and businesses](/build/customers/create-update-customers) are shared between Paddle Classic and Paddle Billing. - [Reporting](/build/reports) is separate between Paddle Classic and Paddle Billing. Historic reporting data isn't ported and remains in Classic. You can always switch to Paddle Classic using the toggle to work with your historic data. - If you want to port subscription data to Paddle Billing, you'll need to have at least one completed transaction and at least one subscription that has renewed on Paddle Billing before you can start the process. You should create and manage new subscriptions using Paddle Billing, while running existing subscriptions through Paddle Classic. ### Port subscription data to Billing Once you're transacting through Paddle Billing, you can port your subscription data from Paddle Classic. Use [the migration screens in the dashboard](/migrate/start/port-data) to map your product catalog and port customers and subscriptions. Webhooks occur when subscriptions are migrated, so you can update records in your database and route customers to the correct workflows. - You can only start a migration once you've fully set up your Paddle Billing integration, have at least one completed transaction, and have had at least one subscription renew on Paddle Billing. - You can choose which subscriptions you want to migrate as part of the process. We recommend starting with a small number first. - When subscriptions are ported, they're canceled in Paddle Classic and imported into Paddle Billing. [`subscription.imported`](/webhooks/subscriptions/subscription-imported) occurs in Paddle Billing so you can update your records. - As part of porting your data, imported events occur in place of created events in Paddle Billing. For example, `subscription.imported` occurs instead of `subscription.created`. - You can export a list of subscriptions after a migration is completed, then use this list to update your database records instead of listening for webhooks. - The process is seamless for customers. They're not notified that their subscription has been canceled and imported, and subscriptions renew as normal with no disruption. - Only active subscriptions can be migrated. You can migrate past due subscriptions after [dunning](/concepts/retain/payment-recovery-dunning) when their account is in good standing. ### Remove your Paddle Classic integration You're done. You can remove Paddle Classic from your stack and use Paddle Billing. - If you have past due subscriptions that can't be migrated, you can follow up your initial migration with another migration to port those when they're in good standing or have canceled. - You can switch to Paddle Classic to get access to your historic data and reports. ### Integrate with Paddle Retain — live only Paddle Billing integrates with [Paddle Retain](/concepts/retain), an all-in-one solution that combines world-class subscription expertise with algorithms that use billions of datapoints to recover failed payments, reduce churn, and automatically increase customer lifetime value (LTV). Integrate [Payment Recovery](/concepts/retain/payment-recovery-dunning), [Cancellation Flows](/concepts/retain/cancellation-flows-surveys), and [Term Optimization](/concepts/retain/term-optimization) to proactively reduce churn and maximize customer lifetime value. - Paddle Retain powers dunning and payment recovery in Paddle Billing. - Turn on and set up [Payment Recovery](/concepts/retain/payment-recovery-dunning) in Paddle Retain to customize failed payment retries and determine what happens when dunning is exhausted. - If you don't turn on Payment Recovery in Paddle Retain, Paddle Billing automatically retries payment for failed subscription renewals, but doesn't email customers. - If you used Payment Recovery with Paddle Classic, you don't need to do anything. We'll use the same settings. - Retain works with [Paddle.js v2](/paddle-js), so you don't need to include an additional script. --- # Full feature comparison between Paddle Classic and Paddle Billing URL: https://developer.paddle.com/migrate/learn/feature-comparison Get a detailed breakdown of how features in Paddle Billing compare to Paddle Classic. Paddle Billing has parity with most features in Paddle Classic, with improvements across the board. This guide is a detailed comparison of features in Paddle Classic and Paddle Billing, presented in a series of tables. You can use it to understand exactly how Paddle Classic and Paddle Billing compare. ## Platform Built on the same world-class merchant of record foundations as Paddle Classic, Paddle Billing comes with everything you need for payments and tax. | Feature | Classic | Billing | |---|:---:|:---:| | Set product prices in [31 currencies](/concepts/sell/supported-currencies). | | | | Sell in over [200 countries and territories](/concepts/sell/supported-countries-locales). | | | | Self-serve customer portal. | | | | Intelligent payment routing for the best chance of success. | | | | Turn on digital wallets like [Apple Pay](/concepts/payment-methods/apple-pay), [Google Pay](/concepts/payment-methods/google-pay), and [PayPal](/concepts/payment-methods/paypal) in minutes. | | | | No third-party merchant accounts required. | | | | Accept local payment methods. | | | | Let customers pay by [bank transfer](/concepts/payment-methods/wire-transfer) — no local bank account required. | | | | Fraud and illegitimate chargeback protection. | | | | Automatic global tax collection and remittance. | | | | Paddle takes on full liability for sales tax for all payments. | | | | Fully SOC 2 compliant. | | | | Hold your balance in `USD`, `EUR`, `GBP`, `CAD`, and `AUD`. | | | | Get payouts in over ten currencies, including `USD`, `EUR`, `GBP`, `AUD`, `CAD`, `CNY` and others. | | | ## Technical Paddle Billing is a complete reimagining of Paddle as a developer-first platform for SaaS businesses. | Feature | Classic | Billing | |---|:---:|:---:| | Access to a [sandbox account](/sdks/sandbox) for testing. | | | | API accepts and responds with JSON data. | | | | API returns standard HTTP [success](/api-reference/about/success-responses) and [error responses](/api-reference/about/errors). | | | | API conforms to REST principles. | | | | Complete access to your data using the API. | | | | Store custom data against all entities. | | | | Webhook payloads mirror API responses. | | | | [Standardized webhook signature verification](/webhooks/about/signature-verification), with SDK helper methods. | | | | [Consistent, performant pagination](/api-reference/about/pagination) across list endpoints. | | | | Supports [sideloading entities](/api-reference/about/include-entities) in `GET` requests. | | | | Comprehensive reference documentation. | | | | Real world examples for all [API operations](/api-reference), [webhooks](/webhooks), and [Paddle.js events](/paddle-js/events). | | | | Public [Postman collection](https://bit.ly/paddlehq-postman). | | | | Public [OpenAPI specification file](https://github.com/PaddleHQ/paddle-openapi/). | | | | [Hand-crafted SDKs](/sdks) for web platforms. | | | | Desktop SDKs. | | | | TypeScript support for Paddle.js. | | | ## Customers Customer information is centralized in a single, lightweight customer entity that's specific to your Paddle Billing account and can be related to multiple subscriptions and transactions. | Feature | Classic | Billing | |---|:---:|:---:| | Collect and store customer, address, and business information. | | | | Automatic handling of countries under international sanctions. | | | | Access to all customer, address, and business information [using the API](/api-reference/customers). | | | | Collect customer marketing consent at checkout. | | | | Paddle Audience functionality in Paddle.js. | | | | Store multiple addresses and businesses per customer. | | | | Team discovery highlights customers across the same company. | | | | Set business contacts to receive copies of invoices and emails from Paddle. | | | ## Product catalog Paddle Billing comes with a product catalog that's designed to give you flexible ways to offer digital products and subscriptions. | Feature | Classic | Billing | |---|:---:|:---:| | Sell one-time products. | | | | Sell recurring subscriptions. | | | | Override prices for a product. | | | | Create [multiple prices per product](/build/products/create-products-prices). | | | | Set trial periods for subscriptions. | | | | Automatic currency conversion. | | | | [Localize prices](/build/products/offer-localized-pricing) by country. | | | | Model basic recurring, good-better-best, per-seat and metered billing plans. | | | | Bill for [products that aren't in your catalog](/build/transactions/bill-create-custom-items-prices-products). | | | | Offer [recurring and non-recurring discounts](/build/products/offer-discounts-promotions-coupons). | | | | Discount by percentage or flat amount per transaction. | | | | Discount per-unit on a transaction. | | | | Restrict the number of redemptions for a discount. | | | | Set discount expiry dates. | | | | Set a discount to recur for a limited number of billing periods. | | | | Create discounts without coupon codes. | | | ## Checkout We brought everything you liked about Paddle Checkout in Paddle Classic to Paddle Billing, including fully optimized payment workflows, intelligent payment routing, and field validation. | Feature | Classic | Billing | |---|:---:|:---:| | Easy-to-implement [JavaScript library (Paddle.js)](/paddle-js/events). | | | | [Import Paddle.js as a module](https://github.com/PaddleHQ/paddle-js-wrapper/) and work with TypeScript definitions. | | | | Build custom workflows with [events for all parts of the checkout lifecycle](/paddle-js/events). | | | | Authenticate using client-side tokens. | | | | Use [HTML data attributes](/paddle-js/about/html-data-attributes) to open checkouts. | | | | Dynamically update items and apply discounts to opened checkouts. | | | | Checkout translated into 18 languages. | | | | Short purchase journey, optimized for conversion. | | | | Present customers with a one-page checkout experience. | | | | [Prefill customer, address, and business information](/build/checkout/prefill-checkout-properties). | | | | Real-time card and email address verification. | | | | Let customers [save payment methods and securely present them for repeat purchases](/build/checkout/saved-payment-methods). | | | | Supports browser or password manager autofill. | | | | Create a [fully responsive overlay checkout](/concepts/sell/overlay-checkout) with a few lines of HTML and JavaScript. | | | | Includes support for dark mode. | | | | Build embedded checkout experiences using [inline checkout](/concepts/sell/branded-integrated-inline-checkout). | | | | Present multi-product subscriptions and complex billing scenarios. | | | | Supports 3DS2, with data stored in a fully PCI-1-compliant vault. | | | | Recover abandoned checkouts with automated follow-up emails. | | | ## Invoices Conquer upmarket and downmarket with Paddle Billing, with integrated invoicing that lets you draft and send invoices to customers with no data duplication. | Feature | Classic | Billing | |---|:---:|:---:| | Let customers pay by bank transfer. | | | | Draft and send [invoices](/concepts/sell/sales-assisted-invoice). | | | | No data duplication — bill any customer for any product. | | | | Automatically create subscriptions for issued invoices. | | | | Automatically generate invoices for subscription renewals, upgrades, and other changes. | | | | Offer a hybrid billing motion, moving customers between payment by checkout to billing by invoice. | | | | Paddle handles invoice reconciliation for you. | | | | Generate internationally compliant invoice documents. | | | | Set purchase order number, payment terms, and other billing information. | | | | Add billing contacts who automatically receive invoices for a business. | | | | Offer payment by checkout for smaller charges, like adding users mid cycle. | | | | Issue refunds. | | | | Generate [credit notes](/build/transactions/create-transaction-adjustments) for refunds and credits. | | | ## Subscriptions Subscriptions are a first-class entity in Paddle Billing, holding all the details of a recurring billing relationship for a customer. | Feature | Classic | Billing | |---|:---:|:---:| | Create subscriptions that recur weekly, monthly, annually, or a custom period. | | | | Create subscriptions with a trial period. | | | | Create card not present free trials. | | | | Bill for multiple recurring products on a subscription. | | | | Collect for subscriptions automatically by card, digital wallet, or local payment methods. | | | | Add billing workflows using the Paddle-hosted customer portal. | | | | [Bill subscriptions by invoice](/build/invoices/create-issue-invoices) (bank transfer). | | | | [Extend or cut short a trial](/build/trials/extend-activate-change-date-trials). | | | | Upgrade, downgrade, and add or remove recurring items on a trial. | | | | Change subscription currency. | | | | [Change billing dates](/build/subscriptions/change-billing-dates) for a subscription. | | | | [Upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade) a subscription term length (for example, monthly to annual). | | | | [Upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade) a subscription plan (for example, basic to pro). | | | | Bill for [one-time](/build/subscriptions/bill-add-one-time-charge) and [recurring addons](/build/subscriptions/add-remove-products-prices-addons). | | | | [Update payment details](/build/subscriptions/update-payment-details) for a subscription. | | | | Switch a subscription to billing by invoice. | | | | [Pause and resume](/build/subscriptions/pause-subscriptions) a subscription. | | | | Set a resume date against a paused subscription. | | | | [Cancel](/build/subscriptions/cancel-subscriptions) a subscription. | | | | Bill products that aren't in your catalog to a subscription. | | | | Automatic calculation of [prorated charges or credits](/concepts/subscriptions/proration) for subscription changes. | | | | Bill for subscription changes now or on the next billing period. | | | | Choose to make changes to a subscription without billing for them. | | | | Preview updates to subscriptions. | | | | Reminder emails before subscriptions renew. | | | ## Provisioning and fulfillment Paddle Billing includes webhooks, a unified event stream, and comprehensive documentation that you can use to build your own fulfillment workflows. Paddle-led fulfillment — including product delivery and license key generation — has been deprecated. | Feature | Classic | Billing | |---|:---:|:---:| | [Webhooks](/webhooks) for all parts of the subscription lifecycle. | | | | Unified event stream that you can poll. | | | | Product delivery by Paddle. | | | | License key generation and activation. | | | | Full access to transaction data. | | | | Send test webhooks using sample or customized data. | | | | [Test complex workflows involving multiple webhooks](/webhooks/simulator) using sample data. | | | ## Dunning and retention tools Paddle Billing seamlessly integrates with Paddle Retain, taking care of payment recovery for you. Retain includes retention tools like Cancellation Flows and Term Optimization that help you reduce churn and increase customer lifetime value. | Feature | Classic | Billing | |---|:---:|:---:| | Payment recovery emails when payment fails. | | | | Automatic payment retries (dunning). | | | | Automatically optimized [retry schedule](/build/retain/configure-payment-recovery-dunning). | * | | | Pause or cancel past due subscriptions where dunning exhausted. | | | | In-app payment recovery notifications, powered by Paddle Retain. | * | | | Payment recovery by SMS, powered by Paddle Retain. | * | | | In-app notifications before payment methods expire, powered by Paddle Retain. | * | | | One-click form to update payment details on your website, powered by Paddle Retain. | * | | | [Build cancellation workflows](/build/retain/configure-cancellation-flows-surveys) and salvage offers that reduce churn. | | | | [Proactively upgrade engaged customers](/build/retain/configure-term-optimization-automatic-upgrades) on monthly plans to annual plans. | | | Paddle Retain Payment Recovery is also available for Paddle Classic. Features with an asterisk are available for Classic when integrated with Retain. Cancellation Flows and Term Optimization are only available for Paddle Billing. ## Finance and reporting Paddle Billing includes [reports](/build/reports) for transactions, refunds and chargebacks, and product catalog. For subscription data, it's fully integrated with ProfitWell Metrics, which includes key performance information and powerful benchmarking tools to see how well your business compares to others in your industry. | Feature | Classic | Billing | |---|:---:|:---:| | Complete access to your data using the API. | | | | Pull reports for refunds and chargebacks. | | | | Pull reports on your sales data. | | | | Pull reports on products and prices. | | | | Pull reports on your account balance. | | | | Pull reports on your subscription data. | | | | Use ProfitWell Metrics to report on delinquent churn, customer lifetime value, and revenue per customer. | | | | View cash flow data from one-time payments alongside recurring revenue in ProfitWell Metrics. | | | --- # Reintegration checklist for Paddle Billing from Paddle Classic URL: https://developer.paddle.com/migrate/start Everything you need to do when reintegrating with Paddle Billing from Paddle Classic. Ready to start your reintegration with Paddle Billing? This guide outlines all the steps that you need to set up Paddle Billing, update your integration, and prepare for go-live. After you've reintegrated with Paddle Billing, you can [port your subscriptions from Paddle Classic to Paddle Billing](/migrate/start/port-data). ## Checklist ### Activate Paddle Billing and review settings Most settings are shared between Paddle Classic and Paddle Billing. This includes [payment methods](/concepts/payment-methods), [checkout branding options](/build/checkout/brand-customize-inline-checkout), business settings and team members, taxable categories, and payout settings. You don't need to go through account verification again. We've improved localization and added more flexibility to Paddle Checkout, so there are some settings to review. Turn on Paddle Billing for your account to get access to the new API, webhooks, and pages in the Paddle dashboard. Paddle creates a new set of data for Paddle Billing that's separate to your Paddle Classic data. Go to **Business Account > Account Settings > Get Paddle Billing** to activate Paddle Billing. You can switch between Paddle Classic and Paddle Billing in the Paddle dashboard using the toggle at the bottom-right. Paddle Billing automatically generates a payment link for transactions you create, using your default payment link as a base. It's a quick way to open [Paddle Checkout](/concepts/sell/self-serve-checkout) for a transaction. Your default payment link should be a page that [includes Paddle.js](/paddle-js/about/include-paddlejs), typically your checkout page. Go to **Paddle > Checkout > Checkout settings**, then add a default payment link. Set this to a domain that you've verified for use on Paddle. You can always change this later. To learn more, see [Set your default payment link](/build/transactions/default-payment-link). Paddle Billing can automatically convert prices into the best currency for a location at checkout. Choose the currencies you want to turn on automatic conversion for. Later, you can add country-specific overrides to prices to manually override base prices with custom prices for countries that you choose. Go to **Paddle > Business account > Currencies** to choose which currencies you want Paddle to automatically convert. We recommend turning on all currencies. To learn more, see [Localize prices](/build/products/offer-localized-pricing). API keys for Paddle Classic work with Paddle Billing, but we recommend generating new keys and familiarizing yourself with authentication before getting started. Go to **Paddle > Developer tools > Authentication** to generate API keys, then store them securely. Send a request to the `/event-types` endpoint to test your authentication. To learn more, see [Authentication](/api-reference/about/authentication). ### Build your product catalog Your [product catalog](/build/products/create-products-prices) includes subscription plans, recurring addons, and one-off items. Paddle Billing offers a more flexible way of structuring your catalog, and includes support for multi-product subscriptions. This is a great chance to review how your prices are modeled. Products describe the items that customers can purchase. They have related prices that describe how they're charged. Add products and prices to Paddle for your subscription plans, recurring addons, and one-off items. As part of the migration process, you can map plans in Paddle Classic to products in Paddle Billing. Go to **Paddle > Catalog > Products** to start adding products and prices. To learn more, see [Create products and prices](/build/products/create-products-prices). Discounts let you reduce the amount a customer has to pay by a percentage or fixed amount. They can be one-time or recurring, and apply to an entire transaction or just items that you choose. In Paddle Billing, discounts replace functionality offered by coupons, modifiers, and charges. You might like to come back to this step after you've ported your subscriptions to Paddle Billing. Go to **Paddle > Catalog > Discounts** to start adding discounts. To learn more, see [Create discounts](/build/products/offer-discounts-promotions-coupons). ### Integrate with your frontend Paddle Billing comes with a new version of [Paddle Checkout](/concepts/sell/self-serve-checkout), designed to handle securely capturing card details or payment using another payment method when customers sign up, change their payment details, or want to pay [invoices](/concepts/sell/sales-assisted-invoice) by card or other payment method. Migrating in your frontend is a case of swapping out Paddle.js v1 for [Paddle.js v2](/paddle-js). Depending on your stack, you can use our [npm package](https://github.com/PaddleHQ/paddle-js-wrapper), our [Next.js starter kit](https://billing.new/), and [our SDKs](/paddle-js/about/include-paddlejs) to speed up integration. You might encounter problems including Paddle.js v1 and v2 on the same page. We recommend creating new pages for your Paddle Billing checkout and frontend workflows. Contact us at [sellers@paddle.com](mailto:sellers@paddle.com) if you need to include Paddle.js v1 and Paddle.js v2 on the same page. Paddle.js uses client-side tokens for authentication. You use a client-side token when including and initializing Paddle.js, rather than passing your seller ID. Go to **Paddle > Developer tools > Authentication** to generate a new client-side token. Initialize Paddle.js by passing the `token` parameter passed to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) in your frontend. To learn more, see [Include Paddle.js](/paddle-js/about/include-paddlejs). Your default payment link is a quick way to open Paddle Checkout for a transaction. You can set this to the checkout page that you built, or create another page that includes Paddle.js. If you want your default payment link page to be different from your checkout page, build a new page that [includes Paddle.js](/paddle-js/about/include-paddlejs). Go to **Paddle > Checkout > Checkout settings** then update your default payment link. To learn more, see [Set your default payment link](/build/transactions/default-payment-link). Paddle.js v2 works similarly to v1 but introduces new ways to include and initialize Paddle.js, a new [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) method, updated parameters and methods, and [a simplified event system](/paddle-js/events). Include and initialize Paddle.js v2, swap authentication to use a client-side token, pass an array of items to your open checkout function, and update event callbacks. To learn more, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout). Paddle Billing introduces a new [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview) method that you can use to get localized pricing for multiple products for a given location — including discount calculations. Swap `Paddle.Product.Prices()` calls for [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview) in your pricing page. To learn more, see [Build a pricing page](/build/checkout/build-pricing-page). Paddle.js v2 integrates with Retain, so you don't have to include a separate Retain script. Client-side tokens for live accounts authenticate with both Paddle Billing and Paddle Retain, too. Update [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) calls to include `pwCustomer`, passing either the Paddle ID or email address of a customer. Remove the `Profitwell.js` snippet. To learn more, see [Initialize Paddle.js and Retain](/paddle-js/about/include-paddlejs). ### Handle fulfillment When a customer completes a purchase, you need to handle fulfillment. Paddle Billing includes [webhooks](/webhooks), [a unified event stream](/api-reference/events/list-events), and comprehensive documentation that you can use to build your own fulfillment workflows. Create notification destinations to tell Paddle where to deliver webhook notifications and which events you want to receive notifications for. There's no primary endpoint in Paddle Billing. Build or update your webhook handler function, then go to **Paddle Developer tools > Notifications** to start creating notification destinations. To learn more, see [Create notification destinations](/webhooks/about/notification-destinations) and [Handle provisioning and fulfillment](/build/subscriptions/provision-access-webhooks). For security, verify webhook signatures to make sure received webhooks are genuinely sent from Paddle. This helps you be sure they haven't been tampered with in-transit. Signature verification in Paddle Billing works by using a `Paddle-Signature` header and a secret key, rather than a signature in the payload. It doesn't require PHP serialization. Update your webhook signature verification workflow for Paddle Billing. Our SDKs include helper functions and classes to do this for you. To learn more, see [Verify webhook signatures](/webhooks/about/signature-verification). When a customer completes a purchase, you need to handle fulfillment. In Paddle Classic, Paddle handles sending license keys and digital downloads. In Paddle Billing, you'll need to build this yourself. You can listen for [`transaction.completed`](/webhooks/transactions/transaction-completed) webhooks to build your own fulfillment workflows. Paddle Billing automatically creates a new subscription for recurring items where a checkout completes or an invoice is issued. For one-time items, create your own workflow to generate license keys and send digital downloads, and consider using the [customer portal](/concepts/sell/customer-portal) for customer invoice management. Subscriptions are created automatically in Paddle Billing when a checkout completes or an invoice is issued; ensure you also [provision your app](/build/subscriptions/provision-access-webhooks) and create a corresponding database record. If you [bill by invoice](/concepts/sell/sales-assisted-invoice), update your workflows to use transaction webhooks instead of invoicing webhooks. To avoid data contamination, we recommend creating new tables in your database for subscription data rather than enriching existing records. Customers can have more than one subscription in Paddle Billing, so we recommend creating a table for customer data and a separate table for subscription data, then relating them using the customer ID. Create new tables in your database for customer and subscription data. To learn more, see [Prepare your database](/migrate/start/port-data). ### Build subscription lifecycle workflows In your backend, update workflows that handle subscription lifecycle events, like when customers upgrade or downgrade, or pause or cancel their subscription. You can use [our SDKs](/sdks) to speed up the process. The [Paddle Billing API](/api-reference) gives you more access to your data, and includes a new [subscription entity](/api-reference/subscriptions) that you can map to a subscription record in your database. You can use the customer portal to quickly add core workflows, rather than building them yourself. As part of the migration process, you'll need to run Paddle Classic and Paddle Billing alongside each other for a short while. We recommend building your workflows in a way that means you can easily remove your Paddle Classic logic after migration is completed. You should provide a way for customers to cancel their subscription when they no longer want to use your app. For compliance, emails from Paddle include a link to cancel subscriptions. Let customers cancel their subscription by integrating with the [customer portal](/build/customers/integrate-customer-portal), using [Paddle Retain Cancellation Flows](/build/retain/configure-cancellation-flows-surveys) via [`Paddle.Retain.initCancellationFlow()`](/paddle-js/methods/paddle-retain-initcancellationflow), or building your own workflow with the [cancel a subscription](/build/subscriptions/cancel-subscriptions) API operation. [Provision your app](/build/subscriptions/provision-access-webhooks) so that customers don't have access once canceled. Customers need a way to change the payment method that they use to pay for subscription renewals and charges. This is important where subscriptions are past due. Emails from Paddle include a link that customers may use to update their payment method. Build a workflow to let customers update their payment details using Paddle.js and the Paddle API. Either [integrate with the customer portal](/build/customers/integrate-customer-portal) or build [your own workflow using Paddle.js and the Paddle API](/build/subscriptions/update-payment-details). Pausing a subscription is more intuitive in Paddle Billing, and lets you specify a date that subscriptions should resume. Build a workflow to let customers pause and resume their subscription. Provision your app so that customers have limited access while paused. To learn more, see [Pause a subscription](/build/subscriptions/pause-subscriptions). Paddle Billing supports multi-product subscriptions, meaning upgrades and downgrades can happen in more scenarios. If you offer multiple plans, you should provide a way for customers to upgrade or downgrade their subscription. You might also sell addons, like additional users or modules, that you should let customers add or remove. Build a workflow to let customers upgrade or downgrade, and add or remove any recurring items. To learn more, see [Upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade) or [Add or remove items](/build/subscriptions/add-remove-products-prices-addons). ### Start transacting through Paddle Billing At this point, you're ready to start transacting through Paddle Billing. This means that you run customers through your [Paddle Billing checkout](/concepts/sell/self-serve-checkout), or use the [invoicing functionality in Paddle Billing](/concepts/sell/sales-assisted-invoice). If you offer subscriptions, start processing new subscriptions through Paddle Billing, while routing customers in Paddle Classic to your existing subscription logic. Test payment, fulfillment, and subscription lifecycle scenarios to check that your integration works. When you're happy, go live. Run through your checkout or invoicing workflow to make sure that everything works. If you offer subscriptions, you can use webhook simulator to test subscription lifecycle events like new signups, dunning, and cancellations. To learn more, see [Webhook simulator](/webhooks/simulator) and [Payment methods](/concepts/payment-methods). Update your app so that you process new subscriptions through your Paddle Billing integration. You still should handle changes to existing subscriptions using your Paddle Classic logic. Make code changes in your frontend and backend so that new subscriptions are managed through Paddle Billing. ### Port subscription data to Billing Now you're running through Paddle Billing, use the screens in the dashboard to [port your Paddle Classic subscriptions](/migrate/start/port-data) and customers to Paddle Billing. When customers and subscriptions are migrated, `imported` events occur in Paddle Billing. We recommend building logic to create customer and subscription records in your database when they've been imported. Subscribe to webhooks for [`subscription.imported`](/webhooks/subscriptions/subscription-imported) and other `imported` events. Make changes to records in your database when they've been imported so that they use the workflows you built for Paddle Billing. To learn more, see [Port your subscriptions](/migrate/start/port-data). Use the screens in the Paddle dashboard to migrate your subscriptions to Paddle Billing. The aim of migration is to move all your subscriptions to Paddle Billing, so you can turn off your Paddle Classic integration. Historic reporting data remains on Paddle Classic. Go to **Paddle > Migrate** to start. Make sure you toggle **Paddle Billing** in the nav bar first. To learn more, see [Port your subscriptions](/migrate/start/port-data). ### Remove your Paddle Classic integration Once you've completed the preceding steps, you're live with Paddle Billing — congratulations! You can safely turn off your Paddle Classic integration. We recommend keeping up to date with changes to the Paddle platform. Now you've fully migrated to Paddle Billing, you can safely remove any Paddle Classic logic from your frontend and backend. You'll still have access to your Paddle Classic data in the Paddle dashboard for historic reporting. Remove Paddle Classic logic from your app. We add features to Paddle Billing regularly. We recommend that you check our changelog regularly or sign up for emails to get updates about new features and changes. Check our developer changelog and use the email form to sign up for updates. To learn more, see [Developer changelog](/changelog). --- # Migrate URL: https://developer.paddle.com/migrate Understand what Paddle Billing is, how it compares to Paddle Classic, and how to migrate. In August 2023, we released a new version of Paddle called Paddle Billing. It's built on the Paddle platform, but includes an improved developer experience and new features to help you increase your revenue, retain customers, and scale your operations. If you use Paddle Classic, you can migrate your subscriptions to Paddle Billing in the dashboard, choosing how data is mapped between the systems. The Paddle team are here to help throughout. We've migrated over one million Paddle Classic subscriptions to Paddle Billing. Reintegrate with Billing, then use simple screens in the dashboard to port your data in four steps. Paddle Billing is the leader in the merchant of record space, landing 60 improvements in 2024. ## Exclusive to Paddle Billing Present customers with a one-page checkout experience, collecting customer, address, business, and billing details on one screen. Save and present payment methods so customers can complete repeat purchases without re-entering their card details. Let customers update payment details, manage subscriptions, and view invoices through a Paddle-hosted portal — no engineering needed. Send single events or play out full lifecycle scenarios to test your subscription and billing flows before going live. Spin up a Paddle-powered Next.js SaaS app in minutes, with auth, checkout, and subscription management already wired in. Build cancellation surveys and salvage offers that proactively prevent churn at the moment customers try to leave. ## Start your migration journey Learn about Paddle Billing, how it compares to Paddle Classic, and how to migrate. Plan your migration, including how to map your data and workflows. Migrate your data from Paddle Classic to Paddle Billing. --- # Workflow mapping between Paddle Classic and Paddle Billing URL: https://developer.paddle.com/migrate/plan/workflow-mapping Understand how the workflows you've built to integrate with Paddle Classic map to ways of working in Paddle Billing. After you activate Paddle Billing for your account, you get access to the Paddle Billing dashboard, API and webhooks, Paddle.js v2, and developer tools. To move fully to Paddle Billing, you need to replace your integration with Paddle Classic with an integration that uses the Paddle Billing API, webhooks, and Paddle.js v2. This guide walks through how typical workflows in Paddle Classic map to ways of working in Paddle Billing. You can use it to review your integration with Classic and understand how you should build your integration with Paddle Billing. ## Product catalog For each item that you offer, create either a product or a plan: - Create products for one-time items. - Create plans for subscriptions. If you offer subscriptions with different billing periods, you need to create a separate plan for each one. Everything you offer in Paddle is [a product, with related prices](/build/products/create-products-prices) describing how products are billed: 1. Create products to describe an item that you offer, including name and icon. 2. Create related prices to describe how much and how often a product is billed. For example, you might have a "Pro plan" product with two price entities for "Month" and "Annual." Use coupons, modifiers, or pay links depending on the kind of discount you need: - To create a discount that applies once or forever, create and apply a coupon to a checkout. - To apply a discount for a limited number of billing periods, create and apply a modifier to a subscription, then manually remove when no longer needed. - To pass a totally custom price, use the API to generate a pay link and pass to Paddle.js. Discounts in Paddle Billing give you [a unified, flexible way to offer discounts](/build/products/offer-discounts-promotions-coupons): 1. Create percentage, fixed fee, or per-seat discounts using the API or the dashboard. 2. Apply to a checkout, transaction, or subscription at any time using Paddle.js, the API, or the dashboard. Discounts can recur for a set number of billing periods, or indefinitely. They never add credit to a customer balance, and they can be prorated on redemption. Localization in Paddle Classic works at the currency level: - When creating products and plans, manually set overrides for each currency. - To create overrides for countries that share a currency, create a price for each country, then build your own logic in your frontend to apply the correct price at checkout. Paddle Billing includes [new ways to localize prices](/build/products/offer-localized-pricing) based on willingness to pay and purchasing power: 1. Turn on automatic currency conversion at an account level to automatically localize prices at checkout into the best currency for a region. 2. Add country-specific prices to prices in your catalog for markets that are important to you — including separate prices for countries that share a currency. 3. Paddle automatically presents customers with the best price at checkout. ## Billing Paddle Classic has different ways of presenting localized prices: - To get localized prices for one item, use `Paddle.Product.Prices()`. - To get localized prices for multiple items, or get discounted prices, make an AJAX request to the get prices operation in your frontend. Multi-product checkouts and subscriptions aren't supported, so you can't natively create shopping carts. Use frontend methods that match API operations to calculate localized prices for [pricing pages](/build/checkout/build-pricing-page): - To get localized prices for multiple items, use [`Paddle.PricingPreview()`](/paddle-js/methods/paddle-pricepreview). You can include a discount. - To build shopping carts or pricing pages where users can build their own plan, use `Paddle.TransactionPreview()` to get localized prices for multiple items, including totals that mirror those used on a checkout. You can include a discount. You can also use the [preview prices](/api-reference/pricing-preview/preview-prices) or [preview a transaction](/api-reference/transactions/preview-transaction-create) operations in the API to work with localized prices in your backend. Paddle Classic supports single product checkouts, with no way to update an opened checkout. - Use `Paddle.Checkout.open()` to open a checkout. You may pass one item. - Prefill customer information, pass a discount, and customize properties as part of the method call. - To update an item on a checkout, use `Paddle.Checkout.close()` to close it, then reopen. Handle the transition state in your frontend. Paddle Checkout includes support for multiple products, and includes [new ways to update a checkout once it's opened](/build/checkout/pass-update-checkout-items). - Use [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) to open a checkout. You can pass multiple items. - Prefill customer information, pass a discount, and customize properties as part of the initial method call, or using [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) once opened. - Use [`Paddle.Checkout.updateItems()`](/paddle-js/methods/paddle-checkout-updateitems) to dynamically update items or add a discount to an opened checkout. One-page checkout isn't natively supported in Paddle Classic. As a workaround, you might: 1. Capture email address and location information in a workflow before you launch checkout. 2. Pass email and address details to `Paddle.Checkout.open()`. 3. Paddle Checkout skips the first screen, since all the required information has been captured. Paddle Billing fully supports [one-page checkout](/concepts/sell/overlay-checkout), where all checkout fields are on one page: 4. Pass `one-page` as the value for the `variant` checkout setting parameter when calling [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open). 5. Paddle presents a new checkout experience where all fields are on one page. You can still prefill fields when using one-page checkout for an even quicker checkout experience. Use pay links to create a link to a checkout: 1. Use the API to generate a pay link with a custom total and description. 2. Pass to Paddle.js using the `override` parameter. You can [pass a transaction in Paddle Billing to Paddle.js](/build/transactions/pass-transaction-checkout) to open a checkout for it: 3. [Set a default payment link](/build/transactions/default-payment-link) in your dashboard. You only need to do this once. 4. Use the dashboard or the API to [create a transaction](/build/transactions/create-transaction). 5. Pass the transaction ID to Paddle.js when opening a checkout, or grab the `checkout.url` from the transaction. Checkout links use your default payment link, but you can pass an approved domain as the URL when creating or updating a transaction to use a different link. Multi-product subscriptions and checkouts aren't natively supported. As a workaround, you might: 1. Create a template package product in the dashboard, then store details about items that you bundle together on your side. 2. When customers purchase multiple items, calculate item totals and create a custom description. 3. Use the API to generate a pay link with the custom total and description. 4. Pass to Paddle.js using the `override` parameter. Multi-product subscriptions and checkouts are supported out-of-the-box using [Paddle.js v2](/paddle-js): 5. [Create products and prices](/build/products/create-products-prices) using the API or the dashboard. 6. Pass [multiple price IDs to Paddle.js when opening a checkout](/build/checkout/pass-update-checkout-items), or when [creating a transaction](/build/transactions/create-transaction). Saving payment methods isn't supported. As a workaround, you might: 1. Create a template package product in the dashboard for $0. 2. When customers want to save a card, use the API to generate a pay link with a custom description. 3. Pass to Paddle.js using the `override` parameter. 4. To let customers pay using their card, build a way for customers to log in, then use the API to create a one-off charge for the subscription to use their card. Built-in support for [saving payment methods](/build/checkout/saved-payment-methods) and [working with saved payment methods using the API](/api-reference/payment-methods): 5. Turn on saved payment methods in the dashboard. You only need to do this once. 6. Paddle Checkout automatically presents customers with an option to save their payment method at checkout. 7. To let customers pay using their card, [generate a customer authentication token using the API](/api-reference/customers/generate-customer-authentication-token) then pass to Paddle.js when opening a checkout. You can present all payment methods that a customer has saved, preselect a single payment method, or pass certain kinds of payment method. Use pay links to bill for one-off or bespoke items. A typical workflow might involve: 1. Use the API to generate a pay link with a custom total and description. 2. Pass to Paddle.js using the `override` parameter. Use [non-catalog products and prices](/build/transactions/bill-create-custom-items-prices-products) to bill for one-off or bespoke items. When working with transactions or subscriptions: - Pass a price object, rather than a price ID, to charge a custom amount for an existing product. - Pass product and price objects, rather than a price ID, to bill for an entirely custom product. Non-catalog products and prices are discrete entities for reporting purposes. Paddle Classic uses a separate invoicing module. A typical workflow might involve: 1. Create and maintain a separate product catalog and customer list for invoicing. 2. Use the Paddle dashboard or Paddle invoicing API to create invoices. 3. Issue an invoice to mark it as finalized. Send it to business contacts yourself. 4. Listen for `invoice.issued` and `invoice.completed` for fulfillment. 5. To create invoices for subscriptions, build your own logic to create an invoice on renewal. Invoicing and subscriptions are fully integrated in Paddle Billing, with [transactions](/api-reference/transactions) as the central entity for revenue capture: 6. Use the Paddle dashboard to [create invoices](/build/invoices/create-issue-invoices), or [use the API to create a transaction](/api-reference/transactions/create-transaction) where the collection mode is manual. 7. Mark a transaction as `billed` to mark it as finalized. Paddle automatically sends the invoice to any business contacts. 8. Listen for [`transaction.billed`](/webhooks/transactions/transaction-billed) and [`transaction.completed`](/webhooks/transactions/transaction-completed) for fulfillment. 9. Paddle automatically creates a subscription when an invoice for recurring items is issued, and for subscription renewals and other lifecycle events. Paddle Classic includes different ways of issuing refunds depending on the kind of purchase and revenue source: - You can refund orders for subscriptions or one-time products purchased through the checkout using the dashboard. - To refund invoices, use the Paddle invoicing API. You can't refund using the dashboard. There's no concept of a credit note or credit memo in Paddle Classic. All refunds must be approved. Paddle records post-billing changes to a transaction, including refunds, credits, and chargebacks using a [new adjustment entity](/api-reference/adjustments). 1. Create [a refund using the dashboard](/build/transactions/create-transaction-adjustments), or by [creating an adjustment](/api-reference/adjustments/create-adjustment) using the API. 2. Some [refunds are automatically approved](/build/transactions/create-transaction-adjustments), otherwise refunds are pending until they're approved by the Paddle team. 3. Once approved, Paddle automatically sends the customer a copy of a credit note as a record of the refund. ## Provisioning and fulfillment Paddle.js v1 comes with multiple ways to pass an event callback, depending on the kind of event. You can also pass a success URL. - To call a function when a checkout is closed or completed, use `closeCallback` or `successCallback`. - Use `eventCallback` with a case condition to determine if an event is `Checkout.Complete` and call a function. - You can pass a `successUrl` to `Paddle.Checkout.open()` to redirect to a page. [Paddle.js v2](/paddle-js) comes with a single event callback and a standardized checkout event shape. Passing a success URL works in the same way as Classic. - Use `eventCallback` with a case condition to determine if an event is [`checkout.completed`](/paddle-js/events/checkout-completed) and call a function. - You can pass a `successUrl` to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) to redirect to a page. Fulfillment for one-time items in Paddle Classic is Paddle-led: 1. Set up download links or license keys when creating products. 2. When a customer completes a purchase, Paddle sends downloads or license keys. You can also pass custom URLs to Paddle.js using the `Paddle.Download` method. Build your own logic to handle fulfillment in Paddle Billing. A typical workflow involves: 1. Listen for [`transaction.completed`](/webhooks/transactions/transaction-completed) webhooks. 2. Generate a unique download URL and send an email for the customer who paid. You can also create an event callback function to run on [`checkout.completed`](/paddle-js/events/checkout-completed) to enter into a success workflow. Build your own logic to handle fulfillment in Paddle Classic. A typical workflow involves: 1. Listen for `subscription_payment_succeeded` and `subscription_created` webhooks. 2. Create a record in your database for the subscription. 3. Provision access to your app. Build your own logic to handle fulfillment in Paddle Billing. You should: 4. Listen for [`transaction.completed`](/webhooks/transactions/transaction-completed) and [`subscription.created`](/webhooks/subscriptions/subscription-created) webhooks. 5. Create a record in your database for the customer and subscription. 6. [Provision access](/build/subscriptions/provision-access-webhooks) to your app. [Transactions](/api-reference/transactions) and [subscriptions](/api-reference/subscriptions) are closely related, making matching a checkout or invoice to a subscription easier. ## Subscriptions Update a subscription user in Paddle Classic to make changes to a subscription. 1. Use the update user operation in the API, passing the new `plan_id`, `prorate`, and `bill_immediately`. You can also use the dashboard. 2. If you're making prorated changes, Paddle calculates it accurate to the day. 3. Update records in your database and provision access. Paddle Billing includes [a new subscription entity](/api-reference/subscriptions), with flexible ways to work with subscription items: 4. Optionally [preview an update to a subscription](/api-reference/subscriptions/preview-subscription-update) using the API, passing an updated `items` list and `proration_billing_mode`. 5. Use the [update subscription operation](/api-reference/subscriptions/update-subscription) in the API, passing an updated `items` list and `proration_billing_mode`. You can also use the dashboard. 6. If you're making [prorated changes](/concepts/subscriptions/proration), Paddle calculates it accurate to the minute. 7. Update records in your database and [provision access](/build/subscriptions/provision-access-webhooks). In Classic, the main way to cancel a subscription is to cache links returned by subscription webhooks: - Subscribe to all subscription webhooks, store the `cancel_url` each time a webhook occurs, then pass this URL to Paddle.js using the `override` parameter. - To build your own workflow, use the cancel user operation in the API. Paddle Billing includes multiple ways to let customers [cancel their subscriptions](/build/subscriptions/cancel-subscriptions): - [Generate authenticated links to the customer portal](/build/customers/integrate-customer-portal) to let customers cancel. - To present customers with a cancellation survey that's designed to prevent churn, [use Paddle.js to launch Retain Cancellation Flows](/build/retain/configure-cancellation-flows-surveys). - To build your own workflow, use the [cancel a subscription operation](/api-reference/subscriptions/cancel-subscription) in the API. Paddle Classic supports basic pause and resume functionality: - Use the API to update a subscription, passing `pause=true`. You can also use the dashboard. - Resume a subscription in the same way, passing `pause=false`. You can also use the dashboard. - To resume on a specific date, build your own workflow that calls the API on a later date. In Paddle Billing, you can choose to [pause or resume](/build/subscriptions/pause-subscriptions) right away, or schedule a pause or resume to happen in the future. - Use the pause a subscription operation to [pause a subscription](/api-reference/subscriptions/pause-subscription). You can also use the dashboard. - Use the resume a subscription operation to [resume a subscription](/api-reference/subscriptions/resume-subscription). You can also use the dashboard. - Set a resume date when pausing or resuming a subscription. When resuming a subscription, you can choose whether to start a new billing period or continue an existing one. In Classic, the main way to update a payment method is to cache links returned by subscription webhooks: 1. Subscribe to all subscription webhooks. 2. Store the `update_url` each time a webhook occurs. 3. Pass the `update_url` to Paddle.js using the `override` parameter to let customers update. 4. Update records in your database, if required. Checkouts for updating a payment method don't use your checkout customization settings. Paddle Billing includes multiple ways to let customers update their payment method: - [Generate authenticated links to the customer portal](/build/customers/integrate-customer-portal) to let customers update their payment method. - Use the API to [create a transaction to update a payment method](/api-reference/subscriptions/get-subscription-update-payment-method-transaction), then pass it to Paddle.js to open a checkout for payment method update. It uses your checkout customization settings. [Payment methods](/api-reference/payment-methods) are first-class entities in the API that you can work with. Paddle Classic includes limited trial functionality, but doesn't let you update a subscription during trial. For flexible trials, a typical workflow might look like this: 1. Build your own signup workflow in your frontend. 2. Keep track of the trial period and details yourself in your backend. 3. At the end of the trial period, launch a checkout to collect payment information. Paddle Billing lets you work with subscriptions in trial in the same way as active subscriptions. - Use the dashboard or the API to switch plan, add or remove items, or change trial end date. - [Activate a subscription](/api-reference/subscriptions/activate-subscription) in one API call. ## Dunning and retention By default, you can configure limited retry rules in Paddle Classic. - Configure limited retry rules in the dashboard. - Integrate with Paddle Retain for additional retries. [Paddle Retain](/concepts/retain) powers dunning and payment recovery in Paddle Billing. - [Turn on and set up Retain](/build/retain) to customize [failed payment retries](/build/retain/configure-payment-recovery-dunning) and determine what happens when dunning is exhausted. - Without Retain, Paddle Billing automatically retries payment for failed subscription renewals, but doesn't email customers. You can integrate Paddle Classic with Paddle Retain, but it requires a separate script. 1. Add the `Profitwell.js` script to the head of any pages on your app or website. 2. Initialize by passing a Retain API key. 3. Pass the email address for a logged-in user to track engagement. 4. Include and initialize Paddle.js v1 as normal after. Paddle Retain is bundled with [Paddle.js v2](/paddle-js), so you can include and initialize Paddle.js in one call. 5. [Include and initialize Paddle.js](/paddle-js/about/include-paddlejs) using a client-side token. Retain is authenticated using your client-side token. 6. Pass the email address of a logged-in customer to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize). You can also use [the Paddle.js wrapper](https://github.com/PaddleHQ/paddle-js-wrapper) to install Paddle.js as a module. --- # Technical data mapping between Paddle Classic and Paddle Billing URL: https://developer.paddle.com/migrate/plan/data-mapping All the data in Paddle Classic can be mapped to Paddle Billing, but as a complete reimagining of how you work with the Paddle platform, some of the data doesn't map one-to-one. After you activate Paddle Billing for your account, you get access to Paddle Billing data alongside your Paddle Classic data. Paddle Billing and Paddle Classic share some core settings, but most data is separate. This includes customer, catalog, and subscription data. To move fully to Paddle Billing, you need to replace your integration with Paddle Classic with an integration that uses the [Paddle Billing API](/api-reference), [webhooks](/webhooks), and [Paddle.js v2](/paddle-js). This guide walks through how data in Paddle Classic maps to data in Paddle Billing at a technical level. You can use it to understand how you should build your integration with Paddle Billing. ## Shared settings Paddle Billing is built on the Paddle platform, so it shares core settings with Paddle Classic. - **Account verification** You don't need to verify your domains, business, or identity again. - **Your Paddle account settings** Business account settings and team members are the same across Paddle Classic and Paddle Billing. - **Checkout customization options** [Payment methods](/concepts/payment-methods), [branding options](/build/checkout/brand-customize-inline-checkout), and other checkout customization options work across Paddle Classic and Paddle Billing. - **Your balance and payouts** Earnings from transactions both Paddle Classic and Paddle Billing are added to your balance, and you're paid out using the same payout settings. - **Taxable categories** Paddle verifies what kinds of products you're able to offer at the account level, so you don't need to get approval for taxable categories in Paddle Billing if you were approved for them in Paddle Classic. - **Allowed IP addresses** Webhooks from Paddle Billing are sent from the same IP addresses as Paddle Classic. - **API keys** API keys work for both Paddle Classic and Paddle Billing, though we recommend you generate new keys when integrating with Paddle Billing. ## Technical comparison Where Paddle Classic is made up of a few different APIs, Paddle Billing is powered by one unified API. We prioritized consistency and conformity to REST standards when building the Paddle Billing API. | Property | Classic | Billing | |---|---|---| | **Base URL** | `vendors.paddle.com/api` | `api.paddle.com` | | **[Authentication](/api-reference/about/authentication)** | Passed in the body of a request | Bearer authentication | | **[Versioning](/api-reference/about/success-responses)** | URL versions; major/minor versions | Passed using a `Paddle-Version` header; major versions | | **[Success responses](/api-reference/about/success-responses)** | `"success": "true"` in the response body | `2xx` response codes; `data` and `meta` in the response body | | **[Error responses](/api-reference/about/errors)** | `"success": "false"` in the response body | `4xx` or `5xx` response codes; `error` and `meta` in the response body | | **[Deletion](/api-reference/about/delete-entities)** | Not possible | Not possible, but archiving supported across entities | | **[Pagination](/api-reference/about/pagination)** | Not always present | Cursor-based pagination, with details in `meta.pagination` | | **[IDs](/api-reference/about/paddle-ids)** | Auto-incrementing IDs | Paddle IDs for all entities | | **[Sideloading](/api-reference/about/include-entities)** | Not possible | Return related entities using `include` query parameter | | **[Empty values](/api-reference/about/data-types)** | May be omitted in responses | Returned in responses as `null` | | **[Custom data](/api-reference/about/custom-data)** | `passthrough` parameter; `custom_data` as stringified JSON for some endpoints | `custom_data` object across all core entities | | **Request bodies** | `application/x-www-form-urlencoded` | `application/json`; requests closely mirror responses | | **Standard fields** | No standardization | All entities return `status`, `created_at`, and `updated_at` fields | | **[Rate limiting](/api-reference/about/rate-limiting)** | Different limits across endpoints | Max `240` requests/minute; `429` response code returned | Notifications (webhooks) are important for provisioning and order fulfillment. They're more robust in Paddle Billing, built around a centralized event stream. | Property | Classic | Billing | |---|---|---| | **[Creation](/webhooks/about/notification-destinations)** | Using Paddle dashboard | Using Paddle dashboard and the API | | **Max webhook endpoints** | `5` | `10` active; no limit on inactive | | **Events available** | `18` | `42` | | **Event subscriptions** | Global | Endpoint specific | | **Payload** | `application/x-www-form-urlencoded`; may include data not returned in the API | `application/json`; includes the new or changed entity in the Paddle API | | **Sensitive fields** | Only sent to the primary endpoint | No concept of sensitive fields | | **Primary endpoint** | Receives all fields, including sensitive fields | No concept of a primary endpoint | | **Logs** | Using the Paddle dashboard; global only | Using Paddle dashboard and the API; global, endpoint specific, and for a specific notification | | **[Webhook simulator](/webhooks/simulator)** | Lets you send test notifications using mock data | Lets you simulate single events and complex scenarios, like subscription creation, renewals, and cancellation | | **[Event stream](/api-reference/events)** | Not available | Poll `/events` using the Paddle API | | **[Failed delivery](/webhooks)** | Retried every 15 minutes over three days | Replayed using an exponential backoff schedule; may be replayed using the Paddle API | | **Product fulfillment** | Paddle-led fulfillment by setting a webhook endpoint against products | Handle fulfillment using transaction webhooks | | **[Signature verification](/webhooks/about/signature-verification)** | Signature in payload; requires PHP serialization | `Paddle-Signature` header; hash using SHA256 | There's a new version of Paddle.js for Paddle Billing. Many concepts are the same, but it now supports multi-product checkouts and new ways to localize. | Property | Classic | Billing | |---|---|---| | **Version** | `1` | `2` | | **CDN URL** | `https://cdn.paddle.com/paddle/paddle.js` | `https://cdn.paddle.com/paddle/v2/paddle.js` | | **Include** | Manually load by adding to `` | Manually load by adding to ``, or import using the Paddle.js wrapper | | **[Initialization](/paddle-js/about/include-paddlejs)** | Pass vendor ID to `Paddle.Setup()` | Pass client-side token to `Paddle.Initialize()` | | **Retain authentication** | Pass a Retain API key to `Paddle.Setup()` | Automatically authenticated using client-side token | | **Work with Retain** | Available using `profitwell` method | Available using `Paddle.Retain` methods | | **TypeScript support** | Not available | Available using the Paddle.js wrapper | | **Callbacks** | `eventCallback`, `successCallback`, and `closeCallback` | Single `eventCallback` | | **Update checkouts** | Not possible | Dynamically update items and discounts on an opened checkout | | **Items** | Single product checkouts only | Multi-product checkouts supported | | **Pricing pages** | `Paddle.Product.Prices()` returns localized prices for one product | `Paddle.PricePreview()` returns localized prices for a list of prices | | **One-page checkout** | Prefill required properties at checkout to land on the second page | Pass `variant` with the value `one-page` to present a one-page checkout experience | #### Methods Core methods are the same, with new parameters to support multi-product checkouts, new ways to localize, and ways to work with Paddle IDs. | Classic | Billing | |---|---| | `Paddle.Setup()` | [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) | | `Paddle.Setup()` | [`Paddle.Update()`](/paddle-js/methods/paddle-update) | | `Paddle.Environment.set()` | [`Paddle.Environment.set()`](/paddle-js/methods/paddle-environment-set) | | `Paddle.Checkout.open()` | [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) | | | [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) | | | [`Paddle.Checkout.updateItems()`](/paddle-js/methods/paddle-checkout-updateitems) | | | [`Paddle.Checkout.close()`](/paddle-js/methods/paddle-checkout-close) | | `Paddle.Product.Prices()` | [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview) | | | [`Paddle.Retain.demo()`](/paddle-js/methods/paddle-retain-demo) | | | [`Paddle.Retain.initCancellationFlow()`](/paddle-js/methods/paddle-retain-initcancellationflow) | | `Paddle.Spinner.show()` | [`Paddle.Spinner.show()`](/paddle-js/methods) | | `Paddle.Spinner.hide()` | [`Paddle.Spinner.hide()`](/paddle-js/methods) | | `Paddle.Status.libraryVersion` | [`Paddle.Status.libraryVersion`](/paddle-js/methods/paddle-status-libraryversion) | | `Paddle.Audience` | | | `Paddle.Download` | | | `Paddle.Order` | | | `Paddle.User.History()` | | #### Events Events are simplified, with a standard event payload that you can compare across the checkout lifecycle. | Classic | Billing | |---|---| | `Checkout.Loaded` | [`checkout.loaded`](/paddle-js/events/checkout-loaded) | | `Checkout.Close` | [`checkout.closed`](/paddle-js/events/checkout-closed) | | | [`checkout.updated`](/paddle-js/events/checkout-updated) | | `Checkout.Complete` | [`checkout.completed`](/paddle-js/events/checkout-completed) | | `Checkout.Quantity.Change` | [`checkout.items.updated`](/paddle-js/events/checkout-items-updated) | | | [`checkout.items.removed`](/paddle-js/events/checkout-items-removed) | | `Checkout.Login` | [`checkout.customer.created`](/paddle-js/events/checkout-customer-created) | | `Checkout.Customer.Details` | [`checkout.customer.updated`](/paddle-js/events/checkout-customer-updated) | | `Checkout.Logout` | [`checkout.customer.removed`](/paddle-js/events/checkout-customer-removed) | | `Checkout.Payment.Selected` | [`checkout.payment.selected`](/paddle-js/events/checkout-payment-selected) | | | [`checkout.payment.initiated`](/paddle-js/events/checkout-payment-initiated) | | | [`checkout.payment.failed`](/paddle-js/events/checkout-payment-failed) | | `Checkout.Coupon.Applied` | [`checkout.discount.applied`](/paddle-js/events/checkout-discount-applied) | | `Checkout.Coupon.Remove` | [`checkout.discount.removed`](/paddle-js/events/checkout-discount-removed) | | | [`checkout.warning`](/paddle-js/events/checkout-warning) | | `Checkout.Error` | [`checkout.error`](/paddle-js/events/checkout-error) | | `Checkout.Location.Submit` | | | `Checkout.Vat.Applied` | | | `Checkout.Vat.Remove` | | | `Checkout.PaymentComplete` | | | `Checkout.User.Subscribed` | | | `Checkout.Language.Change` | | | `Checkout.Vat.Add` | | | `Checkout.Vat.Cancel` | | | `Checkout.Vat.Submit` | | | `Checkout.WireTransfer.Complete` | | | `Checkout.OfflinePayment.DetailsComplete` | | | `Checkout.PaymentMethodChange` | | | `Checkout.WireTransfer.PaymentMethodChange` | | ## Core entities Customers in Paddle Billing are the people and businesses that make purchases. Customer entities store key information like name, email, and localization information. They have two subentities: addresses and businesses. #### Key changes | Property | Classic | Billing | |---|---|---| | **Data structure** | Customer data stored against subscription user | Customer, address, and business entities | | **Data access** | Using Paddle dashboard; limited access using the API | Using Paddle dashboard and the API; all data may be updated by you | | **Tax and business information** | Stored against subscription user | Stored against a related business entity; customers may have multiple businesses | | **Address information** | Stored against subscription user | Stored against a related address entity; customers may have multiple addresses | | **Relation to subscriptions** | One customer, one subscription | Customers may be linked to multiple subscriptions | #### Customer entity [Customer entities](/api-reference/customers) in Paddle Billing hold top-level information about someone making a purchase. The closest equivalent in Paddle Classic is a subscription user, though information about a subscription has moved to [a new subscription entity](/api-reference/subscriptions). | Classic | Billing | |---|---| | `user.user_id` | `customer.id` | | | `customer.name` | | `user.user_email` | `customer.email` | | `user.marketing_consent` | `customer.marketing_consent` | | | `customer.status` | | | `customer.custom_data` | | | `customer.locale` | | | `customer.created_at` | | | `customer.updated_at` | | | `customer.import_meta` | | `user.subscription_id` | | | `user.plan_id` | | | `user.state` | | | `user.signup_date` | | | `user.last_payment` | | | `user.next_payment` | | #### Address entity [Address](/api-reference/addresses) is a new entity in Paddle Billing. It holds information about a billing address for a customer. There's no equivalent in Paddle Classic. | Classic | Billing | |---|---| | | `address.id` | | | `address.description` | | | `address.first_line` | | | `address.second_line` | | | `address.city` | | | `address.postal_code` | | | `address.region` | | | `address.country_code` | | | `address.custom_data` | | | `address.status` | | | `address.created_at` | | | `address.updated_at` | | | `address.import_meta` | #### Business entity [Business](/api-reference/businesses) is a new entity in Paddle Billing. It holds information about a business for a customer. There's no equivalent in Paddle Classic. | Classic | Billing | |---|---| | | `business.id` | | | `business.name` | | | `business.company_number` | | | `business.tax_identifier` | | | `business.status` | | | `business.contacts[]` | | | `business.created_at` | | | `business.updated_at` | | | `business.custom_data` | | | `business.import_meta` | Your catalog in Paddle Billing is made up of products, prices, and discounts. Products describe the items customers can purchase, and prices describe how much and how often a product is charged. #### Key changes | Property | Classic | Billing | |---|---|---| | **Data structure** | Product (one-time) and plan (recurring) entities | Product and related price entities | | **Data access** | Using Paddle dashboard; limited access using the API | Using Paddle dashboard and the API; all data may be updated by you | | **Fulfillment** | License keys and product downloads handled by Paddle | Build your own workflows using transaction webhooks | | **Price localization** | Set currency-specific prices for a currency against an item | System-wide automatic currency conversion; set country-specific prices for an item | | **Relation to subscriptions** | One item per subscription | Multi-item subscriptions | | **Non-catalog items** | Using pay links | Passing product and price attributes to a transaction or subscription | | **Discount types** | Percentage or flat fee | Percentage, flat fee, or flat fee per unit | | **Discount coupon codes** | Required | Optional | #### Product entity [Product entities](/api-reference/products) in Paddle Billing describe an item that customers can purchase. They don't hold information about a charge, which is held against related price entities. Products are comparable to products in Paddle Classic, but there's no distinction between one-time and recurring products in Paddle Billing. | Classic | Billing | |---|---| | `product.id` | `product.id` | | `product.name` | `product.name` | | | `product.tax_category` | | | `product.type` | | `product.description` | `product.description` | | `product.icon` | `product.image_url` | | | `product.custom_data` | | | `product.status` | | | `product.import_meta` | | | `product.created_at` | | | `product.updated_at` | | `product.base_price` | | | `product.currency` | | | `product.sale_price` | | | `product.screenshots` | | #### Price entity [Price](/api-reference/prices) is a new entity in Paddle Billing. Prices describe how products are charged. In Paddle Classic, recurring price information is set against the subscription plan. In Paddle Billing, prices may be one-time as well as recurring. | Classic | Billing | |---|---| | `plan.id` | `price.id` | | | `price.product_id` | | | `price.description` | | | `price.type` | | | `price.name` | | `plan.billing_type`, `plan.billing_period` | `price.billing_cycle` | | `plan.trial_days` | `price.trial_period` | | | `price.tax_mode` | | `plan.recurring_price` | `price.unit_price` | | | `price.unit_price_overrides[]` | | | `price.status` | | | `price.custom_data` | | | `price.import_meta` | | | `price.created_at` | | | `price.updated_at` | | `plan.initial_price` | | #### Discount entity [Discounts](/api-reference/discounts) in Paddle Billing let you reduce the amount a customer has to pay. They're applied to transactions and subscriptions. They're comparable to coupons or modifiers in Paddle Classic. There's no one-to-one entity for modifiers in Paddle Billing. You can create multiple prices for a product to offer it at different price points, or you create discounts to reduce the amount a customer has to pay by a percentage or flat fee. | Classic | Billing | |---|---| | | `discount.id` | | | `discount.status` | | `coupon.description` | `discount.description` | | | `discount.enabled_for_checkout` | | `coupon.coupon` | `discount.code` | | `coupon.discount_type` | `discount.type` | | `coupon.discount_amount` | `discount.amount` | | `coupon.discount_currency` | `discount.currency_code` | | `coupon.is_recurring` | `discount.recur` | | | `discount.maximum_recurring_intervals` | | `coupon.allowed_uses` | `discount.usage_limit` | | `coupon.product_id` | `discount.restrict_to` | | `coupon.expires` | `discount.expires_at` | | | `discount.custom_data` | | `coupon.times_used` | `discount.times_used` | | | `discount.created_at` | | | `discount.updated_at` | | | `discount.import_meta` | All revenue in Paddle Billing flows through transactions. They calculate and capture revenue for checkouts, invoices, and subscriptions. Every payment attempt in Paddle Billing has a related transaction entity. You may create transactions yourself to collect for a charge, passing it to Paddle.js to open a checkout or letting Paddle send an invoice for manual collection. Paddle automatically creates transactions for new checkouts, subscription renewals, and to bill for changes or one-time charges for subscriptions. #### Key changes | Property | Classic | Billing | |---|---|---| | **Data structure** | Orders and transactions (one-time); subscriptions and charges (recurring) | Transactions and related subscriptions | | **Data access** | Using Paddle dashboard and the API | Using Paddle dashboard and the API | | **Invoicing** | Using separate invoice module | Manually-collected transactions and subscriptions | | **Subscription collection options** | Automatic, using a saved payment method or Paddle Checkout | Automatic, using a saved payment method or Paddle Checkout; or manual, by sending an invoice | | **Relation to customers** | One customer, one subscription | Customers may have multiple subscriptions | | **Items** | Single product subscriptions only | Multi-product subscriptions supported | | **Non-catalog items** | Not supported | Bill for items that aren't in your catalog | | **Supported subscription workflows** | Pause/resume, cancel, upgrade/downgrade, change payment method, change billing date | Pause/resume, cancel, upgrade/downgrade, change billing date, change payment method, change currency, transition to billing by invoice | | **Integration journeys** | Build subscription workflows using the Paddle API | Build subscription workflows using the Paddle API; generate secure customer portal links to handle subscription workflows | | **Payments** | Stored against transactions (one-time), or subscriptions (recurring) | Stored against a transaction | | **Saved payment methods** | Not a standalone entity | Standalone entity you can work with using the API | | **Paddle Retain** | Payment Recovery | Payment Recovery, Cancellation Flows, Term Optimization | #### Subscription entity [Subscriptions](/api-reference/subscriptions) in Paddle Billing describe a recurring billing relationship with a customer. They bring together what was previously on a subscription user and a subscription plan in Paddle Classic. | Classic | Billing | |---|---| | `user.subscription_id` | `subscription.id` | | `user.state` | `subscription.status` | | `plan.user_id` | `subscription.customer_id` | | | `subscription.address_id` | | | `subscription.business_id` | | `user.next_payment.currency` | `subscription.currency_code` | | | `subscription.created_at` | | | `subscription.updated_at` | | `user.signup_date` | `subscription.started_at` | | | `subscription.first_billed_at` | | `user.next_payment.date` | `subscription.next_billed_at` | | `user.paused_at` | `subscription.paused_at` | | | `subscription.canceled_at` | | | `subscription.discount` | | | `subscription.collection_mode` | | | `subscription.billing_details` | | `user.last_payment.date`, `user.next_payment.date` | `subscription.current_billing_period` | | `plan.billing_type`, `plan.billing_period` | `subscription.billing_cycle` | | `user.paused_from` | `subscription.scheduled_change` | | `user.update_url`, `user.cancel_url` | `subscription.management_urls` | | `plan.plan`, `user.plan_id` | `subscription.items[]` | | `user.passthrough` | `subscription.custom_data` | | | `subscription.import_meta` | | `user.next_payment` | `subscription.next_transaction` | #### Transaction entity [Transactions](/api-reference/transactions) in Paddle Billing tie together products, prices, and discounts with customers to calculate revenue for checkouts, invoices, and subscriptions. Every payment attempt in Paddle Billing has a related transaction entity. They're most similar to orders in Paddle Classic, but contain elements of payments too. | Classic | Billing | |---|---| | `order.order.order_id`, `order.order.subscription_order_id` | `transaction.id` | | `order.state`, `payment.is_paid` | `transaction.status` | | `order.order.customer` | `transaction.customer_id` | | | `transaction.address_id` | | | `transaction.business_id` | | `passthrough` | `transaction.custom_data` | | `order.order.order_currency` | `transaction.currency_code` | | `payment.is_one_off_charge` | `transaction.origin` | | `order.order.is_subscription`, `order.order.subscription_id` | `transaction.subscription_id` | | | `transaction.invoice_number` | | | `transaction.collection_mode` | | `order.order.coupon_code` | `transaction.discount_id` | | | `transaction.billing_details` | | | `transaction.billing_period` | | | `transaction.items[]` | | `order.order.total`, `order.order.total_tax`, `order.order.quantity` | `transaction.details` | | `order.order.completed`, `payment` | `transaction.payments[]` | | `order.checkout` | `transaction.checkout` | | | `transaction.created_at` | | | `transaction.updated_at` | | | `transaction.billed_at` | | `order.order.has_locker`, `order.lockers` | | | `order.order.formatted_total`, `order.order.formatted_tax` | | | `order.order.customer_success_redirect_url` | | | `order.order.receipt_url`, `payment.receipt_url` | | | `payment.payout_date` | | #### Customer portal session entities [Customer portal session](/api-reference/customer-portals) is a new entity in Paddle Billing. You can create a customer portal session to generate authenticated links to [the customer portal](/concepts/sell/customer-portal), letting customers manage their subscriptions, payments, and account information. You can link to the customer portal to add core subscription management, billing information, and payment history to your app. There's no equivalent in Paddle Classic. | Classic | Billing | |---|---| | | `portal.id` | | | `portal.customer_id` | | `user.update_url`, `user.cancel_url` | `portal.urls` | | | `portal.created_at` | --- # Port your subscription data from Paddle Classic to Paddle Billing URL: https://developer.paddle.com/migrate/start/port-data Map your product catalog and migrate your subscription data from Classic to Billing using screens in the dashboard. Available when you've built an integration with Paddle Billing. After you've built an integration with Paddle Billing, use the dashboard to port subscription data from Paddle Classic to Paddle Billing. You can update records in your database, then turn off your Paddle Classic integration. This guide walks through how to use the migration screens, and how to handle migrated data. ![First page of the port subscriptions screen in the dashboard, showing no plans in Classic mapped to products in Billing.](/src/assets/images/tmp/migrations-hero-20250615.svg) ## How it works You can use screens in the Paddle dashboard to migrate data from Paddle Billing to Paddle Classic. It's a multistep process, with a final chance to review everything at the end. You can run as many migrations as you want, choosing how many subscriptions you want to migrate each time. ### Product catalog mapping In the first step of migration, you can [map plans](#map-products-and-prices) in Paddle Classic with [products](/api-reference/products) in Paddle Billing. You can map the same product to multiple plans. For example, you might have separate plans in Paddle Classic for monthly and annual pricing, which you could map to the same product in Paddle Billing. For each plan that you map to a product, Paddle creates [a new price](/api-reference/prices) for that product. You can review these on [step two of the migration screen](#map-products-and-prices). Price overrides in Paddle Classic and Paddle Billing can't be mapped one-to-one. If you have currency override prices in Paddle Classic, Paddle creates a new price in Paddle Billing for each override price. By default, the new override prices are created as [non-catalog prices](/build/transactions/bill-create-custom-items-prices-products). This means they're considered specific to the subscriptions that they're used on, and not presented in the dashboard or returned by the API by default. ### Subscription selection After you've mapped products and prices, you can choose which [subscriptions](/api-reference/subscriptions) you want to migrate. We recommend choosing a small number of subscriptions for your first migration, so you can familiarize yourself with the process and check that your new Paddle Billing integration flows work correctly. You can run as many migrations as you want, so you can go plan-by-plan, migrate a number at a time, or migrate all. You can only migrate active subscriptions. Where subscriptions are past due, wait for dunning to complete then follow-up with another migration when subscriptions are active. ### During migration During a migration, Paddle creates new records in Paddle Billing and cancels the subscription in Paddle Classic. This is how the process works: 1. **Prices imported.** New prices are created in Paddle Billing for any mapped products. 2. **Customer data imported.** A [customer](/api-reference/customers) and an [address](/api-reference/addresses) entity are created in Paddle Billing for each subscription. Where an existing customer exists, Paddle creates a new address for that customer instead. 3. **Business data imported** If a customer has a tax or VAT number, a [business](/api-reference/businesses) entity is created for the customer in Paddle Billing. 4. **Subscriptions imported.** A [subscription](/api-reference/subscriptions) entity is created in Paddle Billing for each subscription. It's linked to the customer, address, and business created earlier, and the products and prices mapped initially. 5. **Subscription canceled in Paddle Classic.** The subscription is canceled silently in Paddle Classic, with no disruption to the customer. If there's a problem importing a subscription to Paddle Billing, it's not canceled in Paddle Classic. ### Post-migration You can track the status of a migration in the dashboard. Paddle emails you when a migration is completed. As part of the migration process, you need to create or update records in your database for the subscriptions you ported over. You can either: - **Use webhooks** Listen for [`customer.imported`](/webhooks/customers/customer-imported), [`subscription.imported`](/webhooks/subscriptions/subscription-imported), and other imported webhooks, then create records in your database. - **Create a database migration script.** Export a list of migrated subscriptions from the dashboard, then use this to write a script to create records in your database. As part of migration, `imported` events occur in place of `created` events. For example, [`customer.imported`](/webhooks/customers/customer-imported) occurs in place of `customer.created`. In most cases, we recommend using webhooks because they contain all the information you need. You might like to create a database migration script if you're importing a large number of subscriptions that might overwhelm your webhook endpoint. If you're using a migration script, you may need to make additional calls to the Paddle API to fetch the data you need. ## Before you begin ### Build an integration with Paddle Billing Paddle Billing is built on [an entirely new API](/api-reference), with [new webhooks](/webhooks), [Paddle.js library](/paddle-js), and [SDKs](/sdks). Before you can port your subscriptions from Paddle Classic to Paddle Billing, you'll need to build an integration with Paddle Billing so that you're ready to run subscriptions through Paddle Billing rather than Paddle Classic. To learn more, see [Reintegration checklist](/migrate). ### Complete a transaction and renew a subscription You'll need to have at least one completed transaction and at least one subscription that has renewed on Paddle Billing. This is how we verify that your integration is ready to handle renewals for migrated subscriptions. Make sure you've had one new customer signup through your Paddle Billing integration, or launch a checkout and take a payment through it yourself to verify that your Paddle Billing integration works correctly. You'll also need to wait for at least one subscription to renew before starting a migration. ### Subscribe to imported events We recommend using [webhooks](/webhooks) to [handle fulfillment and provisioning](/build/subscriptions/provision-access-webhooks) for subscription lifecycle events. Build a [webhook handler function](/webhooks/about/respond-to-webhooks) and [set up a notification destination](/webhooks/about/notification-destinations) in Paddle. If you're using webhooks to handle your post-migration workflow, subscribe to [`customer.imported`](/webhooks/customers/customer-imported) and [`subscription.imported`](/webhooks/subscriptions/subscription-imported) events. To learn more, see [Create or update notification destinations](/webhooks/about/notification-destinations) and [Handle webhook delivery](/webhooks/about/respond-to-webhooks). You can use [webhook simulator](/webhooks/simulator) to check that your webhook endpoint and fulfillment workflows are working correctly. ## Prepare your database Create or update records in your database for new subscriptions in Paddle Billing. To avoid data contamination, we recommend creating new tables in your database for subscription data rather than enriching existing records. Customers can have more than one subscription in Paddle Billing, so we recommend creating a table for customer data and a separate table for subscription data, then relating them using the customer ID. As part of the migration, [`customer.imported`](/webhooks/customers/customer-imported) occurs in Paddle Billing. You should store: | Description | Field name | Reason to store | |-------------------|------------------------------------|-----------------------------------------------------------------------------| | Customer ID | `customer.id` | Used to identify a customer and relate to record in the subscription table. | | Email address | `customer.email` | Used to identify a customer. | | Classic reference | `customer.import_meta.external_id` | Used to match this subscription to a Paddle Classic user record. | As part of the migration, [`subscription.imported`](/webhooks/subscriptions/subscription-imported) occurs in Paddle Billing. You should store: | Description | Field name | Reason to store | |-----------------------|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| | Customer ID | `subscription.customer_id` | Used to relate this record with a record in the customer table. | | Subscription ID | `subscription.id` | Used to identify this subscription and work with this subscription using the API. | | Subscription status | `subscription.status` | Used to limit or stop access when paused or canceled, or determine if a subscription is past due or trialing. | | Subscription items | `subscription.items[].price.id`, `subscription.items[].quantity` | Used to change items on a subscription as part of an upgrade or downgrade workflow. | | Subscription products | `subscription.items[].price.product_id` | Used to determine which features in your app a customer should have access to. | | Collection mode | `subscription.collection_mode` | Used to determine whether a subscription bills automatically or whether Paddle sends an invoice for charges that customers must pay manually. | | Scheduled change | `subscription.scheduled_change` | Used to determine whether a subscription is scheduled to pause or cancel. You can't change items on a subscription when there's a pending scheduled change. | | Classic reference | `subscription.import_meta.external_id` | Used to match this subscription to a Paddle Classic subscription record. | ## Migrate subscription data ### Get started 1. Choose the **Paddle Billing** option in the toggle in the nav bar. 2. Go to **Paddle > Migrate**. 3. Click **Get started**. You'll need to have at least one completed transaction and at least one subscription that has renewed on Paddle Billing to be able to start a migration. ![Illustration showing the products and prices page in the dashboard. It shows a list of products, with a checkbox for each, and a button to continue to the next step.](/src/assets/images/tmp/migration-step-1-20250411.svg) ### Map products and prices 1. Use the drop-down to map plans in Classic to products in Billing. Paddle creates a new price for each plan against the product you select here. 2. Click **Continue**, then review prices. 3. Click **Price details** next to any price to update the name or description of a price. You can change other details later. 4. Click **Continue**. ![Illustration showing the products and prices page in the dashboard. It shows a list of products, with a checkbox for each, and a button to continue to the next step.](/src/assets/images/tmp/migration-step-2-20250411.svg) ### Select subscriptions 1. Use the checkboxes to select subscriptions that you want to migrate. You can search, sort, and filter using the options at the top. 2. Click **Continue**. We recommend selecting a small number of subscriptions for your first migration, so you can check your new integration works. ![Illustration showing the subscriptions page in the dashboard. It shows a list of subscriptions, with a checkbox for each, and a button to continue to the next step.](/src/assets/images/tmp/migration-step-3-20250411.svg) ### Review and start 1. Review the products and subscriptions you're migrating, then click **Start migration**. 2. Complete the final check by clicking **Start migration**. ![Illustration showing the final page of the port subscriptions screen in the dashboard, showing the number of subscriptions being migrated, and a button to start the migration.](/src/assets/images/tmp/migration-step-4-20250411.svg) ## Handle migrated subscriptions If you built a post-migration workflow using [`customer.imported`](/webhooks/customers/customer-imported) and [`subscription.imported`](/webhooks/subscriptions/subscription-imported) events, you should check that: - You received webhooks for imported events. Paddle automatically queues events that failed for retry, using an exponential backoff schedule. You can view logs in the Paddle dashboard or [using the API](/api-reference/notification-logs/list-notification-logs). - You processed webhooks successfully, and records are created in your database. You can export a list of subscriptions from **Paddle > Migrate** and check against your records. - You're running subscriptions in Paddle Billing through your Paddle Billing integration. When all your subscriptions are ported to Paddle Billing, you can safely remove your Paddle Classic integration. If you're using a database migration script to create and update records in your database, you should: 1. Toggle **Paddle Billing** in the nav bar, then export a list of subscriptions from **Paddle > Migrate**. 2. Write a database migration script to create or update records in your database. 3. Check that you're running subscriptions in Paddle Billing through your Paddle Billing integration. When all your subscriptions are ported to Paddle Billing, you can safely remove your Paddle Classic integration. --- # Build a pricing page URL: https://developer.paddle.com/build/checkout/build-pricing-page Get a step-by-step overview of how to build a pricing page that displays localized prices, including taxes and discount calculation. Open a checkout when a prospect wants to sign up. Pricing pages show prospects the subscription plans, addons, or one-time charges that you offer and how much they cost. They're one of the most important pages on your website, and typically [play a key part in customer conversion](https://www.paddle.com/resources/pricing-page-examples). You can use [Paddle.js](/paddle-js) to build pricing pages that show prospects prices that are [relevant for their country](/build/products/offer-localized-pricing), displayed in their local currency with estimated taxes. If you're running a sale or promo, you can calculate discounts too. Explore the code for this tutorial and test right away using our pricing page pen. ## How it works [Paddle Checkout](/concepts/sell/self-serve-checkout) automatically shows the correct prices for a customer using geolocation to estimate where a customer is buying from. Customers see prices in their local currency, with taxes estimated for their country or region. You can use the [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview) method in Paddle.js to get localized prices for pricing pages or other pages on your website. This means you can show the same information on your pricing page that a customer sees when they open checkout to subscribe. You don't need to do any calculations yourself or manipulate returned data. Paddle returns totals formatted for the country or region you're working with, including the currency symbol. ## What are we building? In this tutorial, we'll create a simple, three-tier pricing page. It includes a toggle to switch between monthly and annual plans. We'll learn how to: - Include and set up Paddle.js using a client-side token - Build an items list that we can send to `Paddle.PricePreview()` - Present and update prices on our page - Toggle between monthly and annual prices for products If you like, you can [view on CodePen](https://codepen.io/heymcgovern/pen/VwgvgNb) and follow along. ## Before you begin ### Choose a pricing page This tutorial walks through creating a simple pricing page. You can also create a cart-style pricing page for more advanced implementations using transaction previews. Pass a batch of price IDs and location information to Paddle.js. Paddle returns localized pricing for each item. **Recommended for most pricing pages.** Simply returns localized prices. Uses [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview). Requests and responses mirror the [preview prices operation](/api-reference/pricing-preview/preview-prices). Returns item totals formatted for the currency and region, including currency code. Response only includes calculations for each item included in the request. You can send prices with different billing periods and trial periods. Send a batch of price IDs and location information to Paddle.js. Paddle returns a preview of a transaction. Recommended for more advanced pricing pages where users can build their own plans. Uses [`Paddle.TransactionPreview()`](/paddle-js/methods/paddle-transactionpreview). Requests and responses mirror the [preview a transaction operation](/api-reference/transactions/preview-transaction-create). Returns item totals in the lowest denomination for a currency (for example, cents for `USD`). Response includes calculations for line items and grand totals. Requests mirror creating a transaction, so billing and trial periods must match for all items. ### Create products and prices You'll need to [create a product and at least one related price](/build/products/create-products-prices) for the items that you want to include on your pricing page. ### Localize prices To show localized prices, [turn on automatic currency conversion or add price overrides](/build/products/offer-localized-pricing) to your prices. ## Overview To build a pricing page: 1. [**Include and initialize Paddle.js**](#include-and-initialize-paddlejs) Add Paddle.js to your app or website, so you can securely work with your product catalog. 2. [**Pass prices to Paddle.js**](#pass-prices-to-paddlejs) Build a pricing preview request body and pass to `Paddle.PricePreview()`. 3. [**Update your page based on the response**](#update-page) Present information returned by Paddle.js to a customer on your page. ## Include and initialize Paddle.js [Paddle.js](/paddle-js) is a lightweight JavaScript library that lets you build rich, integrated subscription billing experiences using Paddle. We can use Paddle.js to securely work with products and prices in our Paddle system, as well as opening checkouts and capturing payment information. ### Include Paddle.js script Start with a blank webpage, or an existing page on your website. Then, [include Paddle.js](/paddle-js/about/include-paddlejs) by adding this script to the ``: ```html ``` ### Set environment We recommend [signing up for a sandbox account](https://sandbox-login.paddle.com/signup?utm_source=dx&utm_medium=dev-docs) to test and build your integration, then switching to a live account later when you're ready to go live. If you're testing with the [sandbox](/sdks/sandbox), call [`Paddle.Environment.set()`](/paddle-js/methods/paddle-environment-set) and set your environment to `sandbox`: ```html ``` ### Pass a client-side token Next, go to **Paddle > Developer tools > Authentication** and create a client-side token. [Client-side tokens](/paddle-js/about/client-side-tokens) let you interact with the Paddle platform in frontend code, like webpages or mobile apps. They have limited access to the data in your system, so they're safe to publish. In your page, call [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) and pass your client-side token as `token`. For best performance, do this just after calling `Paddle.Environment.set()`, like this: ```html ``` Client-side tokens are separate for your [sandbox and live accounts](/paddle-js/about/client-side-tokens). You'll need to [create a new client-side token](/paddle-js/about/client-side-tokens) for your live account. Sandbox tokens start with `test_` to make them easy to distinguish. ## Pass prices to Paddle.js Next, we'll pass prices to Paddle.js so that we can get localized prices for them. When previewing prices, Paddle returns calculated totals for line items only — it doesn't include grand totals. This means that we can include prices with different billing cycles and trial periods in our request, unlike when [opening a checkout](/build/checkout/pass-update-checkout-items) or [creating a transaction](/build/transactions/create-transaction). ### Define lists of prices Our page includes four prices: ```mermaid graph TD Starter["Starter"] Pro["Pro"] Starter_Monthly["Starter (monthly)"] Starter_Yearly["Starter (yearly)"] Pro_Monthly["Pro (monthly)"] Pro_Yearly["Pro (yearly)"] Starter --> Starter_Monthly Starter --> Starter_Yearly Pro --> Pro_Monthly Pro --> Pro_Yearly ``` In Paddle, we've set these up as two products called 'Starter' and 'Pro,' each with two prices for monthly and annual. This is an example from the [list products operation](/api-reference/products/list-products) in the Paddle API. It shows the two products we're using, including an array of prices for each. To define these, create variables for the products in your script section and set them to the Paddle IDs for the products. We'll use these later to determine which products returned prices are for. Then, create arrays for your prices. Each array should contain an object that includes the Paddle ID for a price (`priceId`) and a `quantity`. We've created two arrays: - `monthItems`, which contains monthly prices for our products. - `yearItems`, which contains yearly prices for our products. We'll present localized prices for `monthItems` when the monthly toggle is selected, and `yearItems` when the yearly toggle is selected. ```html ``` ### Get prices Next, we'll create a function to get prices. This should pass our list of monthly or yearly items to Paddle.js. In our sample, we've created a function called `getPrices()` that takes a parameter called `cycle`. Here's how it works: 1. We create a variable called `billingCycle` and set this to `year`. This is the billing cycle that we'd like to show when customers first visit our page. 2. We check to see if `cycle` is `month`, then set a variable called `itemsList` to either `monthItems` or `yearItems`. We also set a variable called `billingCycle` to the value of `cycle` for later. 3. We define a variable called `request`. This is what we're going to send to Paddle.js. It includes an object with an `items` key. The format of our request should match the request body for the pricing preview operation in the Paddle API, except with `camelCase` names for fields. 4. We call `Paddle.PricePreview()`, passing in `request` as a parameter. 5. `Paddle.PricePreview()` returns a promise that contains a pricing preview object. We use the `.then()` method to attach a callback that logs the resolved value to the console, and the `.catch()` method to log errors to the console. ```html ``` ### Test your work Save your page, then [open your browser console](https://developer.chrome.com/docs/devtools/console/) and type `getPrices('year')` or `getPrices('month')`. You should see a promise that contains a pricing preview object from Paddle returned the console. Use `⌘ Command` + `⌥ Option` + `J` (Mac) or `Ctrl` + `⇧ Shift` + `J` (Windows) to quickly open your browser console in Google Chrome. ![Short animation showing Google Chrome with the browser console open. "getPrices('year')" is typed into the console, which returns a line saying data and meta. This section is expanded to show the full object.](/src/assets/images/tmp-build/pricing-page-console-test-20231027-high.gif) ## Update page Our function doesn't do anything to our page yet. We'll update `getPrices()` so that it displays pricing information returned by Paddle.js on our page. ### Create HTML for pricing table First, we need to add some HTML for a simple pricing table with options for monthly and yearly. We'll add some CSS to the `` of the page, too. Add this to the `` of your page. In this sample, there are radio buttons for our pricing toggle, then a `
` with three `
` elements for each product that we offer. The radio buttons have an `onclick` attribute that runs our `getPrices()` function when clicked, passing either `month` or `year` as the parameter for `cycle`. It sets `id`s `

` elements that contain prices. We'll use these IDs to replace the contents of these elements with returned prices from Paddle.js later. ```html

Choose your plan

Starter

$100.00

per user

Pro

$300.00

per user

Enterprise

Contact us

bespoke pricing

``` Add this to the `` of your page. It applies some styling to the HTML so that the `
` elements are arranged in three columns. For this sample, we also include [MVP.css](https://andybrewer.github.io/mvp/), which applies light styling to HTML elements. You don't need to do this if you're updating an existing page in your app or website that already has its own styling. ```html ``` ### Update elements using JavaScript Next, we'll change our script to update the `starter-price` and `pro-price` elements so they return pricing from Paddle. First, we'll get elements in our pricing table using their `id` and assign them to variables that we can use later. Then, we'll update our `getPrices()` function to iterate through `result.data.details.lineItems`. This array contains calculated totals for the prices that we passed to Paddle.js. To make sure we show the correct prices for our products, we check to see if the Paddle ID of the related product of a price matches the product IDs we defined earlier: - If the product for a returned price is `starterProduct`, we replace the contents of the `starter-price` element with `item.formattedTotals.subtotal` - If the product for a returned price is `proProduct`, we replace the contents of the `pro-price` element with `item.formattedTotals.subtotal`. For this sample, we also log `item.formattedTotals.subtotal` to console. This can be useful for debugging. ```html ``` ### Set getPrices() to run on page load Right now, our function only runs when the monthly or annual radio buttons are clicked. We can add `onLoad` to our `` tag to run our `getPrices()` function immediately after the page has loaded: ```html ``` ### Test your work Save your page, then open it in your browser. You should see prices from Paddle.js in your pricing table. Selecting the monthly or annual toggle should change the prices that you see. ![Short animation showing toggling between monthly and annual pricing. The prices change when the monthly and annual radio buttons are clicked.](/src/assets/images/tmp-build/pricing-page-toggle-test-20231027-high.gif) Paddle.js automatically detects visitor location using their IP address and returns localized prices. To see localization in action, see the [get started](/build) demo. We don't recommend including a country selector in real implementations. ## Next steps That's it. Now we've built a simple pricing page, you might like to add other fields to your page, pass a discount, or open a checkout. ### Add other fields to your pricing page [`Paddle.PricePreview()`](/paddle-js/methods/paddle-pricepreview) returns a pricing preview object for the prices and location passed. We show `details.lineItems.formattedTotals.subtotal` in our sample. This is the calculated total for an item before estimated taxes and discounts, formatted for a particular currency. You might like to use another value for the price you show on your page, or include other values. Here are some fields in the response that you might like to use on your page: | | | |---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| | `details.address.countryCode` | Country code for the pricing preview. If you sent an IP address, Paddle returns the detected country. | | `details.line_items[].formattedTotals` | Totals for a particular line item, formatted as a string for the currency you're working with. | | `details.line_items[].formattedUnitTotals` | Totals for one unit of a particular line item, formatted as a string for the currency you're working with. | | `details.line_items[].price.trialPeriod` | Details of the trial period for a price. | | `details.line_items[].discounts[].formattedTotal` | Total amount discounted for a discount applied to a line item, formatted as a string for the currency you're working with. | For a full list of values, see [Pricing preview object](/api-reference/pricing-preview) ### Pass a discount Extend your pricing page by passing `discountId` in your request to `Paddle.PricePreview()`. The response includes a `discount` array that has information about the discount applied. Calculated totals in `details.lineItems` include discounts, where applicable. ### Open a checkout Pass items to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) or use [HTML data attributes](/paddle-js/about/html-data-attributes) to open a checkout. --- # Create a cardless trial URL: https://developer.paddle.com/build/trials/cardless-trials Get a step-by-step overview of how to create a cardless trial — including creating prices, creating a transaction, and collecting for payment. [Cardless trials](/concepts/subscriptions/trials) let customers try your app before they commit to paying. Unlike card-required trials, they don't require a credit card to sign up, making it easier for customers to try your product. ## What are we building? In this tutorial, we'll create a subscription that doesn't require a payment method when signing up. We'll then add a payment method to the subscription, so that it's ready to transition to paying. We'll learn how to: - Create a price for an item that has a trial period that doesn't require a payment method - Create a transaction using the API for a customer, which automatically creates a subscription. - Build a payment workflow using the API and Paddle.js, so customers can transition to paying. You can view the demo app on GitHub to see how a basic cardless trial implementation works. It includes signup, cardless trial detection, and payment collection. Get sample code on GitHub to see how to build a cardless trial implementation. ## How it works Cardless trials work in a similar way to [card-required trials](/build/trials/update-trials), except that they can only be created using the API — not Paddle.js. To create a cardless trial, [create a transaction](/build/transactions/create-transaction) using the API. Because no payment is required, the transaction is automatically completed and Paddle automatically creates a subscription for the customer. A core part of the cardless trial lifecycle is collecting payment details from the customer. You should email customers with details about their signup and encourage them to convert throughout their trial period. To build [a payment workflow](/build/subscriptions/update-payment-details), you can use the API and Paddle.js. If customers don't enter payment details before the trial ends, Paddle automatically cancels the subscription. ```mermaid sequenceDiagram participant PC as Paddle.js participant Cust as Customer/Frontend participant YS as Your Server participant PA as Paddle API participant PW as Paddle Webhooks Note over PC,PW: Customer signup Cust->>YS: Sign up for trial YS->>PA: POST /transactions
(status: billed) PA->>YS: Transaction created (status: paid) Note over PC,PW: Fulfillment PA<<->>PW: transaction.completed webhook PW->>YS: Subscription ID and details YS->>YS: Store subscription, grant access YS->>Cust: Welcome email and app access Note over PC,PW: Collect payment method (during trial) Cust->>YS: Click "Add payment method" YS->>PA: GET /subscriptions/{id}/update-payment-method-transaction PA->>YS: Extract transaction ID YS->>PC: Pass transaction ID to Paddle.Checkout.open() PC->>Cust: Show checkout Cust->>PC: Enter payment details PC->>PA: Payment method stored securely PA<<->>PW: subscription.updated webhook PW->>YS: Payment method added Note over PC,PW: End of trial alt No payment method PA<<->>PW: subscription.paused/canceled webhook PW->>YS: Subscription paused/canceled YS->>Cust: Trial expired email else Payment method on file PA<<->>PW: subscription.activated webhook PW->>YS: Subscription now active PA->>Cust: First payment charged end ``` ## Overview Create a cardless trial in five steps: 1. [**Create a price with a cardless trial period**](#create-a-price) Create a price for a product that has a cardless trial period. When added to a subscription, the customer doesn't need to enter a payment method when signing up. 2. [**Create a transaction for a customer**](#create-a-transaction) Use the Paddle API to create a transaction for a customer, which automatically creates a subscription on completion. 3. [**Handle fulfillment**](#handle-fulfillment) Create a record for a cardless trial in your database, provision access to your app, and email the customer with details about their signup. 4. [**Incentivize customers to convert**](#incentivize-customers-to-convert) Incentivize customers to convert by emailing them throughout their trial period. 5. [**Handle non-converting trials**](#handle-non-converting-trials) Handle non-converting trials by giving customers a way to reactivate their subscription. ## Before you begin - **Build a page that includes Paddle.js** You need to use Paddle.js to collect a payment method from customers so they can convert to paying. [Include and initialize Paddle.js](/paddle-js/about/include-paddlejs) on a page in your app or website. - **Set your default payment link** Set a payment link under **Paddle > Checkout > Checkout settings > Default payment link**, then get it approved if you're working with the live environment. - **Use one-page checkout** You can only present customers with a workflow to enter payment details for a cardless trial using one-page checkout. Multi-step checkouts aren't supported. We recommend starting the domain approval early in your integration process, so your domains are approved for when you're ready to go-live. ## Create a price ### Model your pricing In Paddle, [a complete product](/build/products/create-products-prices) is made up of a product and a price. Whether a subscription has a trial period is determined by whether the prices on the subscription have a trial period. Prices can have [two kinds of trial periods](/concepts/subscriptions/trials): - **Card-required trials** Customers must enter payment details at signup, but aren't charged until the trial ends. - **Cardless trials** Customers can sign up for a subscription without entering their payment details. For this tutorial, we're going to create a price for a product that has a cardless trial. ### Create products and prices **Dashboard support is coming soon.** While in developer preview, you can only create or update prices with a cardless trial period using the API. You can [create products and prices](/build/products/create-products-prices) using the Paddle dashboard or API, but you can only set a cardless trial period using the API while in developer preview. We recommend creating a new price for cardless trials, rather than updating an existing price. This makes it easier for you to compare how cardless trials perform against card-required trials or no-trial prices over time. #### Get or create a product Prices relate to products. To create a price, you'll need to get the Paddle ID of an existing product to relate it to, or create a new product: - **Get an existing product** Send a request to the [`/products` endpoint](/api-reference/products/list-products) to get a list of all products. Extract the Paddle ID of the product you want to relate the price to. - **Create a new product** Send a request to the [`/products` endpoint](/api-reference/products/create-product) to create a new product. Extract the Paddle ID of the product you create. #### Create a price with a cardless trial period Build a request [that includes information about your new price](/api-reference/prices/create-price), then send a request to the `/prices` endpoint with the payload you built. When creating a price, you must include the `trial_period` object with `requires_payment_method` set to `false` to make it a cardless trial. ```ts import { Paddle, Environment } from '@paddle/paddle-node-sdk'; // Initialize the Paddle SDK with your API key and environment. const paddle = new Paddle('YOUR_API_KEY', { environment: Environment.sandbox // or Environment.production }); async function createPrice() { try { const newPrice = await paddle.prices.create({ productId: 'pro_01k5c106wy997av8jmz1qfng2q', description: 'Monthly/seat with cardless trial', name: 'Monthly (per seat)', trialPeriod: { requiresPaymentMethod: false, interval: 'day', frequency: 30, }, billingCycle: { interval: 'month', frequency: 1, }, unitPrice: { amount: '1500', currencyCode: 'USD', }, }); console.log('Successfully created price:', newPrice.id); } catch (error) { console.error('Error creating price:', error); } } createPrice(); ``` Extract the Paddle ID of the price you create — you'll need this to create a transaction in the next step. ## Create a transaction [Transactions](/api-reference/transactions) are the central billing entity in Paddle. Paddle automatically creates a transaction when a customer opens a checkout, when a subscription renews, and for other subscription lifecycle events. [Subscriptions](/api-reference/subscriptions) are automatically created when a transaction is completed. In a card-required workflow, when the customer completes their purchase using [Paddle Checkout](/concepts/sell/self-serve-checkout), the related transaction is automatically completed. At this point, Paddle automatically creates a subscription for the items on the transaction. Cardless trials work in a similar way in that Paddle automatically creates a subscription for items on the transaction on completion. However, Paddle Checkout doesn't support cardless trials, so you must [create a transaction](/build/transactions/create-transaction) manually using the API. Because there's no payment required, the transaction is automatically completed once it's billed. ```mermaid flowchart TD subgraph CardRequired["Card-required trial"] CR1[Customer opens checkout] --> CR2[Paddle.js creates transaction] CR2 --> CR3[Paddle.js creates or updates
customer entities] CR3 --> CR4[Customer completes checkout] CR4 --> CR5[Payment captured, so
transaction completed] end subgraph Cardless["Cardless trial"] CL1[Customer lands on your
signup page] --> CL2[You create or update
customer entities] CL2 --> CL3[You create transaction
via API
status:billed] CL3 --> CL4[No payment required, so
transaction completed] end CR5 --> SUB[✅ Subscription automatically created] CL4 --> SUB style CR5 fill:#fff3cd style CL4 fill:#fff3cd style SUB fill:#d4edda style CR2 fill:#e3f2fd style CL3 fill:#e3f2fd ``` ### Capture customer details Transactions require a customer and address to say who the transaction is for, what currency they should be billed in, and how tax is calculated. If users are signed in already, you can include the Paddle ID for the customer, address, and business of the signed in user in your request. If they're not signed in, you should create a new customer and address, and optionally a business: 1. **Create a customer** [Customers](/api-reference/customers) hold information about the people and businesses that make purchases. Send a request to the [`/customers` endpoint](/api-reference/customers/create-customer) to create a new customer. Extract the Paddle ID of the customer you create. 2. **Create an address** [Addresses](/api-reference/addresses) hold billing address information for customers. Send a request to the [`/customers/{customer_id}/addresses` endpoint](/api-reference/addresses/create-address) to create a new address for a customer, passing the `customer_id` you extracted previously. Extract the Paddle ID for the address you create. 3. **Create a business** [Businesses](/api-reference/businesses) entities hold information about customer businesses. Send a request to the [`/customers/{customer_id}/businesses` endpoint](/api-reference/businesses/create-business) to create a new business for a customer, passing the `customer_id` you extracted previously. Extract the Paddle ID for the business you create. If you're building a signup workflow, we recommend using the [`Paddle.TransactionPreview()`](/paddle-js/methods/paddle-transactionpreview) method (client side) or [preview a transaction operation](/api-reference/transactions/preview-transaction-create) (server side) to present localized prices to your customer. To prevent free trial abuse, consider [blocking known disposable email address domains](https://github.com/disposable-email-domains/disposable-email-domains) or implementing a CAPTCHA using a service like [reCAPTCHA](https://developers.google.com/recaptcha) or [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile). ### Create a transaction Once you've captured the customer and address information, you can [create a transaction](/build/transactions/create-transaction) by calling the Paddle API. Build a request that includes the customer ID, address ID, business ID (optional), and an array of objects for each item. Items should be prices with trial periods where `requires_payment_method: false`. Include the `status` field with the value `billed` to say that the transaction is finalized. Paddle automatically completes the transaction once it's billed, creating a subscription for you. If you don't want to automatically complete the transaction, you can omit the `status` field. Paddle creates a `ready` transaction. Update a transaction to `billed` using the API to complete it. ```ts import { Paddle, Environment } from '@paddle/paddle-node-sdk'; // Initialize the Paddle SDK with your API key and environment. const paddle = new Paddle('YOUR_API_KEY', { environment: Environment.sandbox // or Environment.production }); async function createTransaction() { try { const newTransaction = await paddle.transactions.create({ items: [ { priceId: 'pri_01k5c14mgh9dc3wgk3vb23p0t7', quantity: 10, }, ], customerId: 'ctm_01hx93hx7d5fj4f0ah1x4t22yq', addressId: 'add_01hyjbr14xazf3hhgz79ysp6hj', currencyCode: 'USD', collectionMode: 'automatic', status: 'billed', }); console.log('Successfully created transaction:', newTransaction.id); } catch (error) { console.error('Error creating transaction:', error); } } createTransaction(); ``` The response status is `paid` — this is an interim status while Paddle completes transaction processing (typically less than a second), after which the subscription is automatically created. Extract the transaction ID to match this to the created subscription. ## Handle fulfillment Paddle automatically creates a subscription for the items on the transaction once it's completed. Fulfillment for cardless trials is the same as for card-required trials or other kinds of subscriptions: 1. Create a webhook endpoint and [create notification destinations](/webhooks/about/notification-destinations) for subscription and transaction events. 2. Listen for the [`transaction.completed`](/webhooks/transactions/transaction-completed) webhook, using the transaction ID from the create transaction response to match the event to the transaction. 3. Extract and store the `subscription_id` and other relevant information from the payload, then grant the appropriate level of access to your app. For full details on how to handle fulfillment, see [Handle provisioning and fulfillment](/build/subscriptions/provision-access-webhooks). ### Determine if a subscription is a cardless trial It's likely that you'll want to present customers with different screens in your app or website if they're on a cardless trial. For example, they won't have a payment method on file, so you might want to present them with a screen that asks them to [enter their payment method](/build/subscriptions/update-payment-details). ```mermaid flowchart TD Start{Is status = 'trialing'?} Start -->|No| NotTrial[Not a trial] Start -->|Yes| CheckNext{Is next_billed_at = null?} CheckNext -->|No| CardReq[Card-required trial, or
cardless trial with payment method added] CheckNext -->|Yes| CheckScheduled{Is scheduled_change = null?} CheckScheduled -->|Yes| Cardless[Cardless trial] CheckScheduled -->|No| CardReqScheduled[Card-required trial
scheduled to cancel] style Cardless fill:#d4edda,stroke:#28a745,stroke-width:3px style NotTrial fill:#f8f9fa style CardReq fill:#cfe2ff style CardReqScheduled fill:#cfe2ff ``` You can determine that a subscription is a cardless trial by checking the following fields against a [subscription entity](/api-reference/subscriptions): | Field | Value | Description | | ------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `status` | `trialing` | `trialing` is used for both card-required and cardless trials. | | `next_billed_at` | `null` | Cardless trials don't have a next billing date because there's no payment method on file. Card-required trials have a next billing date. | | `scheduled_change` | `null` | Cardless trials can't be scheduled to cancel, so there can't be a scheduled change. Card-required trials can be scheduled to cancel. | You can also use the [list subscriptions operation](/api-reference/subscriptions/list-subscriptions) and pass the `status`, `next_billed_at`, and `scheduled_change_action` parameters to filter for cardless trials. Pass `null` for `next_billed_at` to return subscriptions with no billing date. ## Collect payment details **Customer portal support is coming soon.** While in developer preview, you can't use [customer portal](/concepts/sell/customer-portal) to add payment details for cardless trials. You need to build your own payment workflow. [Paddle Checkout](/concepts/sell/self-serve-checkout) handles securely capturing card details or [other payment method details](/concepts/payment-methods). To convert cardless trials to paying, you'll need to build a payment method update workflow by getting a payment method update transaction, then passing it to Paddle.js to open a checkout for it. ### Get a payment method update transaction [Payment method update transactions](/build/subscriptions/update-payment-details) are a special kind of zero-value transaction that you can pass to Paddle.js to store a payment method. To create a payment method update transaction, use the [get a transaction to update payment method operation](/api-reference/subscriptions/get-subscription-update-payment-method-transaction). You only need the subscription ID. ```ts import { Paddle, Environment } from '@paddle/paddle-node-sdk'; // Initialize the Paddle SDK with your API key and environment. const paddle = new Paddle('YOUR_API_KEY', { environment: Environment.sandbox // or Environment.production }); async function getPaymentMethodUpdateTransaction() { try { const transaction = await paddle.subscriptions.getPaymentMethodChangeTransaction( 'sub_01hv8wptq8ztpc9j05szh22cgn' ); console.log('Transaction ID for payment method update:', transaction.id); } catch (error) { console.error('Error retrieving payment method update transaction:', error); } } getPaymentMethodUpdateTransaction(); ``` ### Pass the transaction ID to Paddle.js Extract the transaction ID from the response, then use the [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) method to open a checkout for it. Only one-page checkout is supported. ```javascript Paddle.Checkout.open({ transactionId: "txn_01k71zrv404gcm17jgtxm8escg", settings: { variant: "one-page" } }); ``` You can also use the `checkout.url` field in the transaction response to automatically open a checkout for the transaction using [your default payment link](/build/transactions/default-payment-link). For more information, see [Pass a transaction to a checkout](/build/transactions/pass-transaction-checkout) ### Activate immediately When a customer adds their payment details, they still have free access to your app until the end of the trial period. Some customers might want to start paying right away. You can activate a subscription immediately to cut the trial period short and start charging a customer for it. Use the [activate a trialing subscription operation](/api-reference/subscriptions/activate-subscription) in the Paddle API to build a workflow to activate a subscription immediately. We recommend providing a way for customers to [make changes to their subscription](/build/trials/update-trials), like adding or removing users or changing their plan, before activating a subscription. For more details, see [Activate a trialing subscription](/build/trials/extend-activate-change-date-trials) ## Incentivize customers to convert **Paddle emails are coming soon.** While in developer preview, Paddle doesn't email customers trial ending reminders for cardless trials. You need to send your own emails. The barrier of entry for cardless trials is lower than for card-required trials, which means you'll typically see a higher signup rate compared to card-required trials. However, this often means a lower conversion rate since customers haven't committed to paying yet. To incentivize customers to convert, we recommend emailing customers throughout their trial period: | Email type | When to send | What to include | | ---------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | **Trial welcome** | When the trial starts | Highlight key features and give customers a reminder of their signup information. | | **Mid-trial check-in** | Halfway through the trial | Prompt to add payment details. | | **Expiring reminder** | 2-3 days before the trial ends | Reminder that the trial is ending and prompt to add payment details or extend the trial period. | | **Paid plan welcome** | If a payment method is added | Confirmation that the payment method was added and the customer is all set. | | **Expired follow-up** | After the trial has ended, if no payment method is added | Offer an incentive to reactivate the subscription. | ## Handle non-converting trials By default, when a cardless trial ends and there's no payment method on file, Paddle automatically cancels the subscription. It's common for customers to sign up for a trial but forget to add their payment details. Customers looking to reactivate a trial have a strong intent to buy, so you should build a way for them to reactivate rather than letting them sign up for another trial. Once reactivated, you can funnel them into a conversion workflow. To reactivate non-converting trials, build a custom workflow to reinstate the subscription: 1. When a user whose trial expired returns to your app, [get the previous subscription](/api-reference/subscriptions/list-subscriptions) from Paddle or from your database. 2. Extract the items and details like the customer, address, business, and currency code from the previous subscription. 3. [Create a transaction](/build/transactions/create-transaction) using the items and other information that you extracted. Set to `billed` to complete the transaction and create a new subscription. 4. As part of your fulfillment workflow, update the existing subscription record in your database to point to the new subscription in Paddle rather than creating a new one. On reactivation, we recommend giving customers a shorter trial period and encouraging them to convert by [offering a discount](/build/products/offer-discounts-promotions-coupons) or other incentive. You might like to launch a checkout for the customer to collect payment right away, then [transition the subscription to active](/build/trials/extend-activate-change-date-trials). --- # Create a transaction URL: https://developer.paddle.com/build/transactions/create-transaction Transactions are the central billing entity in Paddle. Create a transaction to collect using checkout or invoice. Paddle automatically creates transactions for subscription lifecycle events. All purchases are [transactions](/api-reference/transactions). They hold all the information about a customer purchase, including customer details, items, calculated tax and localized pricing, and payments. Paddle automatically creates transactions for subscription lifecycle events and when checkouts are opened, but you can create your own transactions using the API or Paddle dashboard. ## How it works [Transactions](/api-reference/transactions) are at the heart of Paddle. They tie together products, prices, and discounts with customers to calculate and capture revenue for checkouts, invoices, and subscriptions. All revenue in Paddle is calculated and captured using transactions. Paddle creates transactions automatically for subscription lifecycle events and when checkouts are opened, and you may also create your own transactions using the API or Paddle dashboard. ### Transaction lifecycle Transactions are initially created as `draft` or `ready`, depending on the information supplied. As you work with a transaction entity, they move to `completed`: A transaction is in the `draft` state when it’s missing required fields for billing. For example, checkouts opened by [Paddle.js](/paddle-js) with only items will create `draft` transactions, since they initially lack customer and address information. A transaction moves to `ready` once it has all required billing fields. For Paddle Checkout, this happens when details like the customer’s name, country, and (in some regions) ZIP or postal code have been captured. You can optionally mark a transaction as `billed`, making it a financial record that can’t be changed. This is usually part of [an invoicing workflow to issue an invoice](/build/invoices/create-issue-invoices), or to [lock items and quantities](/build/checkout/pass-update-checkout-items) at checkout so customers can’t edit them. When Paddle successfully collects payment, the transaction moves to `paid`. This is a brief, interim state while Paddle finishes transaction processing—updating things like fees, earnings, payout totals, and setting related fields like `subscription_id` or `invoice_number` for automatically-collected transactions. Transaction processing typically takes less than a second, so you’ll rarely see transactions in the `paid` state when using the API. Once all transaction processing is finished, the transaction becomes `completed`. Paddle automatically sets transactions as `draft`, `ready`, `paid`, and `completed`. You can set transactions as `billed` or `canceled` using the API. This guide focuses on creating **automatically-collected transactions**. You can also [create manually-collected transactions](/build/invoices/create-issue-invoices), which send invoice documents the customer must pay manually. ## Before you begin - **Set your default payment link** [Set your default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link** and get it approved. - **Create products and prices** Transactions work with products and prices to say what a customer is purchasing, so you'll also need to [create a product and a related price](/build/products/create-products-prices) to bill for an item in your catalog. If you're working with the Paddle dashboard, you can create all the entities that you'll be working with as you create your invoice (manually-collected transaction). If you're working with the API, you'll need to: - [Create a customer](/build/customers/create-update-customers) and [a related address](/build/customers/create-update-customers) - Optionally create [a related business](/build/customers/create-update-customers) ## Create a draft or ready transaction Draft transactions contain an `items` list, but don't include address or customer details which are required for billing. Ready transactions contain an `items` list and all required fields for billing, including address and customer details. You may [pass a draft or ready transaction to a checkout](/build/transactions/pass-transaction-checkout) to capture customer or address information, and collect for payment. 1. Go to **Paddle > Invoices**. 2. Click New invoice. 3. Set customer details, including the customer, address, and business that this transaction is for. 4. Use the **Currency** dropdown box to select a currency for this transaction. 5. Choose a product, related price, and quantity for your first item. 6. Click Add line item to add more items if you want. 7. Under the **How do you want the customer to pay?** section, choose **Automatically, using a stored payment method**. 8. Click Continue, then review the summary. 9. Click Generate payment link to create a ready transaction. 10. Use the Copy button to copy the link to send to your customer to collect payment using Paddle Checkout. ![Simplified illustration showing the invoices screen in Paddle. There's a button that says 'New invoice'.](/src/assets/images/tmp-build/create-invoice-20241021.svg) ![Simplified illustration showing create transaction screen in Paddle. There are two radio buttons: automatic-collection and manual-collection. Automatic is selected.](/src/assets/images/tmp-build/create-transaction-automatic-20241021.svg) Create a draft or ready transaction using the API in two steps: 1. **Preview the transaction** Preview charging information before creating the transaction. 2. **Create the transaction** Send the request to create the transaction. Build an `items` array with an object containing a price ID and quantity (catalog items) or price objects (non-catalog items) to specify what the customer is purchasing. Include `customer_id` and `address_id` to create a `ready` transaction. Paddle automatically marks it as `ready` when both are present. Omit them for a `draft` transaction you'll pass to a checkout later. Include `business_id` if the transaction is for a business. ### Preview transaction Send a request to the `/transactions/preview` endpoint to see tax and localized pricing before creating a transaction. Paddle returns a preview of the new transaction entity, but doesn't create it at this point. ### Create transaction Send a request to the `/transactions` endpoint to create a transaction. In your request, include the same body as the preview request. Paddle responds with a copy of the new transaction entity with the status of `draft` or `ready`, depending on the information you sent. The new transaction has `collection_mode: automatic`. If no payment method is saved, [pass this transaction to a checkout](/build/transactions/pass-transaction-checkout) to collect payment. ## Update a transaction While a transaction is `draft` or `ready`, you can make changes to it and the items on it. You can work with items, [apply a discount](/build/products/offer-discounts-promotions-coupons), change customer information, or [add or remove custom data](/build/transactions/custom-data). If you're working with a `draft` transaction, Paddle automatically marks it as `ready` when you add `customer_id` and `address_id`. Transactions are financial records. You can't edit them if they're billed, canceled, or completed. [Cancel a transaction](/build/invoices/cancel-invoices) and create another or [create an adjustment](/build/transactions/create-transaction-adjustments) if you need to make changes to a billed or completed transaction. 1. Go to **Paddle > Transactions**. 2. Find a transaction in the list, then click the button and choose View transaction. 3. Click Edit transaction 4. Change customer details or items on the transaction. 5. Click Continue, then review the change summary. 6. Click Save when you're done. ![Simplified illustration of the transactions screen in Paddle. The first transaction is hovered over, and the action menu is open for the transaction. The 'View transaction' option is selected.](/src/assets/images/tmp-build/edit-transaction-20241021.svg) Send a request to the `/transactions/{transaction_id}` endpoint to update a transaction. In your request, include the fields you want to change. When working with `items`, send the complete list you want against the transaction — Paddle removes any items you omit. To learn more, see [Work with lists](/api-reference/about/lists). This example adds a discount to a transaction using `discount_id`. ## Change collection mode While a transaction is `draft` or `ready`, you can switch between automatic and manual collection modes. To learn more, see [Change transaction collection mode](/build/transactions/change-collection-mode-transaction) ## Mark a transaction as billed You can mark a transaction as `billed` using the API to say it's finalized, meaning it's considered a financial record and can't be changed. This is typically used for [manually-collected transactions (invoices)](/build/invoices/create-issue-invoices), as part of an invoicing workflow. Marking a transaction as billed is the same as issuing an invoice. It gets an invoice number and is sent to the customer. You don't need to mark an automatically-collected transaction as `billed`, and it's not typically part of a self-service workflow. However, you may like to do this if you plan to create a checkout for this transaction to [prevent a customer from changing items or quantities at checkout](/build/checkout/pass-update-checkout-items). To learn more, see [Issue an invoice](/build/invoices/create-issue-invoices) You can create a transaction and mark it as `billed` by including `"status": "billed"` in your initial request, along with the other required fields — no need to make a separate request. ## Pass a transaction to a checkout Automatically-collected transactions include `checkout.url`, which you can send to customers to open a checkout to capture customer information and collect payment for this transaction. You can also [pass a transaction to a checkout](/build/transactions/pass-transaction-checkout) using Paddle.js to collect for it. ## Revise customer information for billed or completed transactions Billed and completed transactions are considered financial records for compliance purposes. This means they can't be deleted or changed directly. You can revise customer information for billed or completed transactions to update information like tax or VAT number, address details, or customer name. To learn more, see [Revise customer details on a billed or completed transaction](/build/transactions/revise-transaction-customer-details) --- # Create and issue invoices URL: https://developer.paddle.com/build/invoices/create-issue-invoices Create invoices as part of a sales-assisted billing process using manually-collected transactions. Paddle automatically creates a subscription for you once an invoice is issued. [Invoices](/concepts/sell/sales-assisted-invoice) let you offer sales-assisted billing. Your sales team can draft and send invoices for subscriptions, collecting payment manually rather than by charging a card on file. They're generally used for bigger-dollar deals, like enterprise plans, and might include one-time or recurring fees for things like implementation or support. ![Illustration of an invoice from Paddle](/src/assets/images/tmp-build/invoices-hero-20240814.svg) ## How it works When customers sign up and pay for a subscription using [Paddle Checkout](/concepts/sell/self-serve-checkout), Paddle collects using an automatically-collected transaction and creates a [subscription](/api-reference/subscriptions) where the collection mode is automatic. The payment method used is stored internally for renewals and other subscription-related charges; it's only saved as a [saved payment method](/api-reference/payment-methods) if the customer opts in. Invoices work using manually-collected transactions. This means that Paddle sends an invoice that customers must pay themselves by [bank transfer](/concepts/payment-methods/wire-transfer) or using Paddle Checkout. Paddle creates a subscription when an invoice is issued, and future renewals and charges send new invoices that the customer must pay manually. ### Invoice lifecycle Transaction statuses mirror the invoice lifecycle: When you create an invoice (manually-collected transaction), it's initially a draft. You can work through changes with the customer, adding or removing items as you scope out their requirements. Draft invoices may have the status `draft` or `ready`. They're `ready` when they have all the required fields to be issued. Once you and the customer are happy with an invoice (manually-collected transaction), you can issue it by marking the transaction as billed. At this point, it is assigned an invoice number and becomes a financial record. Paddle automatically sends a copy to the customer and creates a subscription for them. In Paddle, the status updates to `billed`. When the customer pays an invoice (manually-collected transaction), Paddle automatically handles reconciliation and marks the invoice as paid. The status in Paddle is now `completed`. Payments, including failed payment attempts, are logged against a transaction. Customers can pay invoices by [bank transfer](/concepts/payment-methods/wire-transfer), or you can turn Paddle Checkout for an invoice to let customers pay using card, digital wallet, or [a local payment method](/concepts/payment-methods). This is especially handy for lower-value changes, like adding users or modules mid-cycle. This guide focuses on creating **manually-collected transactions**. You can also [create automatically-collected transactions](/build/transactions/create-transaction), then pass them to Paddle.js to open a checkout for the items on them. ## Before you begin - **Set your default payment link** [Set your default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link** and get it approved. - **Create products and prices** Transactions work with products and prices to say what a customer is purchasing, so you'll also need to [create a product and a related price](/build/products/create-products-prices) to bill for an item in your catalog. If you're working with the Paddle dashboard, you can create all the entities that you'll be working with as you create your invoice (manually-collected transaction). If you're working with the API, you'll need to: - [Create a customer](/build/customers/create-update-customers) and [a related address](/build/customers/create-update-customers) - Optionally create [a related business](/build/customers/create-update-customers) ## Create a draft invoice To create an invoice, create a transaction with the `collection_mode` set to `manual`. Manually-collected transactions have the status of either `draft` or `ready` when you first create them. They're draft `draft` while they're missing `items`, `customer_id`, and `address_id`, and they automatically change to `ready` when you add those fields. 1. Go to **Paddle > Invoices**. 2. Click New invoice 3. Choose or create a customer, business, and address for the customer. 4. Choose whether to bill in `USD`, `EUR`, or `GBP`. 5. Choose a product, related price, and quantity for your first item. 6. Click the Add line item link to add items new items to the items list. Choose a product, related price, and quantity for each item. 7. Click Save draft to save as a draft. Grab a copy of the draft invoice as a PDF from the main invoices screen by clicking next to the invoice in the list, then choosing Download PDF from the menu. Send a request to the `/transactions` endpoint to create a manually-collected transaction. Set `collection_mode` to `manual` to say this is an invoice, rather than a transaction for a checkout. In your request, include: - `items`: an array of price IDs and quantities from your catalog, or [custom items](/build/transactions/bill-create-custom-items-prices-products) with inline price objects - `billing_details`: invoicing-specific fields like purchase order number and payment terms - `customer_id` and `address_id`: who the invoice is for; include `business_id` if billing a business Transactions are automatically marked `ready` (eligible to issue) when they have `customer_id`, `address_id`, and `items`. ## Update a draft invoice While transactions are `draft` or `ready`, they don't have invoice numbers and aren't considered financial records yet. This means that you can make changes to the transaction and its items. 1. Go to **Paddle > Invoices**. 2. Search for the invoice that you want to change. 3. Click the next to an invoice in the list, then choose Edit invoice from the menu. 4. Change customer details or items on your invoice. 5. Click Save draft to save as a draft. Send a request to the `/transactions/{transaction_id}` endpoint to update a manually-collected transaction. Send the complete list of items you want on the invoice — including any existing items you want to keep. Omitting an item removes it. See [work with lists](/api-reference/about/lists). ## Issue an invoice When you're happy with an invoice, issue it to mark it as finalized. At this point, it becomes a financial record so you can't make changes to it. It gets an invoice number and is sent to the customer. You can create a transaction and mark it as `billed` by including `"status": "billed"` in your request to create or update, along with the other required fields — no need to make a separate request. 1. Go to **Paddle > Invoices**. 2. Search for the invoice that you want to issue. 3. Click the next to an invoice in the list, then choose Edit invoice from the menu. 4. Click Continue 5. Review the summary of your invoice. 6. Click Send invoice to issue and send. Send a request to the `/transactions/{transaction_id}` endpoint to update a manually-collected transaction, setting `status` to `billed` in your request. The transaction must be `ready`. If successful,Paddle automatically assigns an invoice number and creates a related subscription. `invoice_number` and `subscription_id` are added asynchronously and may not appear in the initial response. Send a follow-up request to [get the transaction](/api-reference/transactions/get-transaction), or listen for [the `transaction.completed` webhook](/webhooks/transactions/transaction-completed). ## Get created subscription Paddle automatically creates a subscription when you issue an invoice for recurring items. This lets you [provision your app](/build/subscriptions/provision-access-webhooks) for the customer right away, rather than waiting for payment to complete. This is especially important when working with high-value invoices where payment is by bank transfer. ### Using webhooks - `subscription.created` If you've subscribed to a webhook for [`subscription.created`](/webhooks/subscriptions/subscription-created), you'll get a notification that includes the `id` of the new subscription. Use the `transaction_id` against the `subscription.created` notification to match to the correct transaction. - `transaction.updated` After `subscription.created` occurs, Paddle updates the transaction with the created `subscription_id` and [`transaction.updated`](/webhooks/transactions/transaction-updated) occurs. Use `subscription_id` against the `transaction.updated` notification to get the related subscription. ### Using the API Use the `subscription_id` against the transaction you just marked as billed to find the new subscription. Subscriptions are created asynchronously after a transaction is billed, so this may not be present in the initial response. 1. Send a request to [the `/transactions/{transaction_id}` endpoint](/api-reference/transactions/get-transaction), using the `transaction_id` of the manually-collected transaction you marked as `billed`. 2. Extract the `subscription_id` from the response. 3. Send a request to [the `/subscriptions/{subscription_id}` endpoint](/api-reference/subscriptions/get-subscription), using the `subscription_id` you just extracted. --- # Create or update customers URL: https://developer.paddle.com/build/customers/create-update-customers Customers, addresses, and businesses are the people and businesses that make purchases. Paddle automatically creates customers as part of checkout. Customers are the people and businesses that make purchases. Paddle creates customers for you as part of checkout, or you can create them yourself. If you're looking to update customer, address, and business information on a completed checkout or issued invoice, see [Revise customer details on a billed or completed transaction](/build/transactions/revise-transaction-customer-details) ## How it works All purchases in Paddle require a customer. Customers are lightweight entities that hold key information like name, email, and localization information. They have two subentities: - **Addresses**, which hold information about billing addresses - **Businesses**, which hold information that you need when working with a business Customers can have multiple addresses and businesses against them — useful when you're dealing with a large customer with offices in different locations. They can be linked to multiple subscriptions, too. ### Paddle Checkout creates customers If you offer products using a self-serve motion, letting customers sign up and pay for subscriptions using a checkout, you don't generally need to create customers yourself. [Paddle Checkout](/concepts/sell/self-serve-checkout) automatically creates customers, addresses, and businesses as part of the checkout process. When a customer enters an email address at checkout and there's already an existing customer entity for them in your system, Paddle uses the existing customer entity rather than creating a new one. This means transactions and subscriptions for the same customer are kept together, and lets you create complex multi-subscription offerings. Paddle always creates a new address for a customer, even if matching addresses are found. This is because addresses are closely related to payment methods. ### Required fields for invoicing To make buying as frictionless as possible, [Paddle Checkout](/concepts/sell/self-serve-checkout) only asks for the required information to complete a purchase online. This includes a customer's email address and country. As [invoices](/concepts/sell/sales-assisted-invoice) are legal documents, Paddle requires more data against customers and addresses to make sure that they're compliant across the markets we serve. | | Checkout (automatically-collected transactions) | Invoices (manually-collected transactions) | | ---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------: | | `customer.name` | ✗ | ✓ | | `customer.email` | ✓ | ✓ | | `address.first_line` | ✗ | ✓ | | `address.second_line` | ✗ | ✗ | | `address.city` | ✗ | ✗ | | `address.postal_code` | In some markets | ✓ | | `address.region` | In some markets | ✓ | | `address.country_code` | ✓ | ✓ | You don't have to set a business for a transaction, even when working with an invoice. ### Update information for completed transactions Billed and completed transactions are considered financial records for compliance purposes. This means they can't be deleted or changed directly. If you update a customer, address, or business after it's been added to a transaction, the information against the transaction isn't updated. You can revise customer information for billed or completed transactions to update information like tax or VAT number, address details, or customer name. To learn more, see [Revise customer details on a billed or completed transaction](/build/transactions/revise-transaction-customer-details) ## Create a customer Create a customer to create a transaction for a person or business. Customer email addresses must be unique in your system. Customers can be linked to multiple subscriptions, so there's no need to create a new customer for each subscription. 1. Go to **Paddle > Customers**. 2. Click New customer 3. Enter the details for your new customer. 4. Click Save when you're done. ![Illustration of the create customer drawer in Paddle.](/src/assets/images/tmp-build/procedure-new-customer-20240920.svg) Send a request to the `/customers` endpoint to create a customer. Include `email` in your request. We recommend including `name` too — it's required for [billing by invoice](/build/invoices/create-issue-invoices). You can optionally include `locale` to specify the language of emails Paddle sends, and `custom_data` to add your own structured key-value data. ## Create an address Create an address related to a customer to say where a person or business is located. 1. Go to **Paddle > Customers**. 2. Find the customer you'd like to add an address to in the list, then click the button and choose View customer. If you haven't created a customer already, you can create one. 3. Under the **Addresses** heading, click New address 4. Enter the details for your new customer. 5. Click Save when you're done. ![Illustration of the create address drawer in Paddle.](/src/assets/images/tmp-build/procedure-new-address-20240920.svg) Create an address using the API in two steps: 1. **Get a customer** Get the Paddle ID of the customer you want to create an address for. You can skip this if you already have it. 2. **Create address** Send a request to the `/customers/{customer_id}/addresses` endpoint to create an address. ### Get a customer Send a request to the `/customers` endpoint to list customers. Extract the Paddle ID of the customer you want to create an address for. You can skip this if you already have it. ### Create address Send a request to the `/customers/{customer_id}/addresses` endpoint to create an address. Include `country_code` in your request. For some countries, `postal_code` or `region` is also required — see [supported countries](/concepts/sell/supported-countries-locales). You can optionally include address lines, a description, and `custom_data`. ## Create a business You should add a business if you're dealing with a company. You don't need to add a business if you're working with a private individual. 1. Go to **Paddle > Customers**. 2. Find the customer you'd like to add a business to in the list, then click the button and choose View customer. If you haven't created a customer already, you can create one. 3. Under the **Businesses** heading, click New business 4. Enter the details for your new business. 5. Click Save when you're done. ![Illustration of the create business drawer in Paddle. It shows fields for name, tax ID, and company ID.](/src/assets/images/tmp-build/procedure-new-business-20241003.svg) Create a business using the API in two steps: 1. **Get a customer** Get the Paddle ID of the customer you want to create a business for. You can skip this if you already have it. 2. **Create business** Send a request to the `/customers/{customer_id}/businesses` endpoint to create a business. ### Get a customer Send a request to the `/customers` endpoint to list customers. Extract the Paddle ID of the customer you want to create a business for. You can skip this if you already have it. ### Create business Send a request to the `/customers/{customer_id}/businesses` endpoint to create a business. Include `name` in your request. We recommend including `tax_identifier` so Paddle charges the correct amount of tax. Add `contacts` to automatically copy them on invoices. You can also include `company_number` (appears on invoices) and `custom_data`. --- # Create products and prices URL: https://developer.paddle.com/build/products/create-products-prices Products and prices make up your catalog in Paddle. They hold information about what's being sold, how much items are being sold for, and how often a charge is made. Your product catalog holds the products that are being sold to customers, including subscription plans, recurring addons, and one-time charges. Create products and related prices to start billing. ## How it works A complete product in Paddle is made up of two parts: 1. A [product entity](/api-reference/products) that describes the item, like its name, description, and an image. 2. At least one related [price entity](/api-reference/prices) that describes how much and how often a product is billed. Products are the items that customers buy — like subscription plans, recurring addons, or one-time charges. Prices describe how they pay for them. In this example, Enterprise is a product and $3000/mo is a price for it. When switching from monthly to yearly: - Products remain the same — the name and details remain as Enterprise. - Prices change — your page should fetch prices for products where the billing cycle is yearly rather than monthly. ![Illustration showing a pricing page. One of the prices is Enterprise at $3000/mo. There's an arrow pointing to enterprise that says 1. product. Another arrow points to $3000/mo saying 2. price.](/src/assets/images/tmp-build/get-started-product-price-20250122.svg) You can create as many prices for a product as you want to describe all the ways it's billed. However, prices may only relate to one product. ### Billing cycle The billing cycle against a price determines how often Paddle bills for it. You can set an interval and a frequency. This is typically things like every month, every three months, or every year, but you can be as flexible as you like. Paddle supports multi-product subscriptions, letting you [add recurring addons alongside subscription plans](/build/subscriptions/add-remove-products-prices-addons). When building multi-product subscriptions, all items on a subscription must have the same billing period. This means that if a customer subscribes to your "Pro plan" with a yearly price, you must also create yearly price for any recurring addons. You can't add a monthly addon to a subscription for a yearly plan. ### One-time charges Prices don't have to have a billing cycle. These are called "one-time charges." One-time charges can be billed to subscriptions. They're typically used for things like setup or onboarding fees at the start of a subscription, or data auditing or support incident fees during a subscription. You might also use them to offer ebooks, access to webinars, or other learning resources to customers. ## Create a product Create products to describe items being sold. You can add prices to products afterward. We recommend creating products using the Paddle dashboard. 1. Go to **Paddle** > **Catalog** > **Products**. 2. Click New product 3. Enter details for your new product. 4. Click Save when you're done. ![Illustration showing the create product screen in Paddle. There are fields for product name, tax category, and description](/src/assets/images/tmp-build/dashboard-create-product-20230831.svg) Send a request to the `/products` endpoint to create a product. In your request, include the product `name` and `tax_category`. Optionally include `description`, `image_url` (HTTPS, square images recommended), and `custom_data`. For `tax_category`, `standard` or `saas` are available by on all Paddle accounts by default. You can request other tax categories for your account in the Paddle dashboard. This example creates a new product for a subscription plan called "AeroEdit Student." It includes an object of custom data. If successful, Paddle responds with a copy of the new product entity. Paddle creates an `id` starting with `pro_` — keep this for the next step to associate a price. This example creates a product for a recurring addon called "Voice rooms addon." If successful, Paddle responds with a copy of the new product entity. Paddle creates an `id` starting with `pro_` — keep this for the next step to associate a price. This example creates a new product for a one-time charge called "Custom domains setup." If successful, Paddle responds with a copy of the new product entity. Paddle creates an `id` starting with `pro_` — keep this for the next step to associate a price. ## Create a related price Once you've created products, create related prices that describe how you bill for them. You can't offer a product for sale without creating a price. Add price overrides to a price to set country specific prices. Price overrides let you override the base price with a custom price and currency for any country. See: [Localize prices](/build/products/offer-localized-pricing) 1. Go to **Paddle** > **Catalog** > **Products**, then click the product you want to add a price to in the list. 2. Under the Prices section, click New price 3. Enter details for your new price. 4. Click Save when you're done. ![Illustration showing the create price page. There are fields for base price, tax, type, billing period, trial period, name, and description.](/src/assets/images/tmp-build/dashboard-create-price-20231012.svg) Send a request to the `/prices` endpoint to create a price. In you request, include `product_id` (Paddle ID of the product), `unit_price` (base price), and a `description`. To make a price recurring, include `billing_cycle` with `interval` and `frequency`. Optionally include `trial_period` (requires `billing_cycle`), `name` (shown at checkout and on invoices), `unit_price_overrides` for [localized pricing](/build/products/offer-localized-pricing), `tax_mode`, `quantity` limits (defaults to 1-100), and `custom_data`. This example creates a monthly price for a subscription plan with a trial period. It's related to the subscription plan product using the `product_id` field. If successful, Paddle responds with a copy of the new price entity. This example creates a monthly price for a recurring addon. It's related to the recurring addon product using the `product_id` field. If successful, Paddle responds with a copy of the new price entity. This example creates a monthly price for a one-time charge. It's related to the one-time charge product using the `product_id` field. Only one charge may be applied at once, so it includes the `quantity` object with `minimum` and `maximum` values of `1`. If successful, Paddle responds with a copy of the new price entity. ## Review prices and products Once you've created products and related prices, you can use the `include` query parameter in the Paddle API to review them. You can: - [Get a product and include its related prices](#get-a-product-and-all-prices) - [Get a price and include its related product](#get-a-price-and-related-product) - [List prices for a product](#list-prices-for-a-product) ### Get a product and all prices Use the `include` query parameter with the value `prices` to include related prices in the response. Return entities related to the specified product. Use a comma-separated list to specify multiple product IDs. Include related entities in the response. Use a comma-separated list to specify multiple entities. ### Get a price and related product Use the `include` query parameter with the value `product` to include the related product in the response. Return entities related to the specified price. Use a comma-separated list to specify multiple price IDs. Include related entities in the response. Use a comma-separated list to specify multiple entities. ### List prices for a product Use the `product_id` query parameter to filter prices by product. Pass a comma-separated list to list prices for more than one product. Return entities related to the specified product. Use a comma-separated list to specify multiple product IDs. --- # Add a hosted checkout to your mobile app URL: https://developer.paddle.com/build/mobile-apps/link-out-mobile-app-hosted-checkout-app Get a step-by-step overview of how to add a Paddle-hosted external purchase 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 link users in the to an external checkout for purchases in iOS apps. You can use [hosted checkouts](/concepts/sell/hosted-checkout-mobile-apps) to let users securely make purchases outside your app — no hosting required. Customers tap a button in your app to open a checkout that's fully hosted by Paddle, then they're redirected to your app when they complete their purchase. ## What are we building? In this tutorial, we'll use [hosted checkouts](/concepts/sell/hosted-checkout-mobile-apps) in Paddle to build an external purchase flow for in-app purchases in iOS apps. We'll walk through handling fulfillment using the [RevenueCat x Paddle integration](https://www.paddle.com/revenuecat-integration-beta) or [webhooks](/webhooks). ## What's not covered This tutorial doesn't cover: - **Handling authentication** We assume you already have a way to identify your users, like [Firebase](https://firebase.google.com/) or [Sign in with Apple](https://developer.apple.com/sign-in-with-apple/). - **Native in-app purchases** We'll launch Paddle Checkout in Safari then redirect users back to your app. Like the App Store, Paddle Checkout supports [Apple Pay](/concepts/payment-methods/apple-pay) with no additional setup, plus [other popular payment options](/concepts/payment-methods). - **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](/concepts/sell/customer-portal). We cover that elsewhere in our docs. ## Before you begin ### Sign up for Paddle You'll need a Paddle account to get started. You can sign up for two kinds of account: - [Sandbox](/sdks/sandbox) — for testing and evaluation - Live — for selling to customers For this tutorial, we recommend signing up for a sandbox account. You can transition to a live account later when you've built your integration and you're ready to start selling. If you sign up for a live account, you'll need to: - [**Complete account verification**](/build/set-up-checklist) We'll ask you for some information to make sure that we can work together. - [**Request hosted checkout access**](mailto:sellers@paddle.com) You should contact support to check you're eligible to use hosted checkouts. ### Prep your iOS development environment As part of our tutorial, we're going to update our app to include a link to a hosted checkout for purchases. You'll need: - Some knowledge of iOS development, access to your iOS project, and Xcode on macOS. - A [correctly configured URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) so you can redirect users back to your app. You don't need to make changes to your iOS app to create a hosted checkout in Paddle, so you can come back to this later if you're working with a developer. ## Overview Add a hosted checkout to your app to link out for in-app purchases in five steps: 1. [**Map your product catalog**](#map-your-product-catalog) Create products and prices in Paddle that match your in-app purchase options. 2. [**Create a hosted checkout**](#create-a-hosted-checkout) Create a hosted checkout in the Paddle dashboard, including where to redirect customers to after purchase. 3. [**Add a checkout button to your app**](#add-a-checkout-button-to-your-app) Create a button that opens the hosted checkout URL when tapped. 4. [**Handle fulfillment and provisioning**](#handle-fulfillment-and-provisioning) Use RevenueCat or process webhooks to fulfill purchases after a customer completes a checkout. 5. [**Take a test payment**](#test-the-complete-flow) Make a test purchase to make sure your purchase flow works correctly. ## Map your product catalog Before we add a hosted checkout to our app, we need to set up our product catalog in Paddle to match the in-app purchases we offer. ### Model your pricing A [complete product](/build/products/create-products-prices) 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](/build/products/create-products-prices) using the Paddle dashboard or the API. 1. Go to **Paddle > Catalog > Products**. 2. Click New product 3. Enter details for your new product, then click Save when you're done. 4. Under the **Prices** section on the page for your product, click New price 5. Enter details for your new price. Set the type to **One-time** to create a one-time price. 6. Click Save when you're done. 7. Click the button next to a price in the list, then choose Copy price ID from the menu. Keep this for later. ![Illustration showing the new product drawer in Paddle. It shows fields for product name, tax category, and description](/src/assets/images/tmp-build/dashboard-create-product-20230831.svg) ![Prices list in the Paddle dashboard, with the action menu open and copy ID selected.](/src/assets/images/tmp-build/copy-price-id-20250127.svg) ## Create a hosted checkout Next, create a hosted checkout. A [hosted checkout](/concepts/sell/hosted-checkout-mobile-apps) is a link that users can use to make a purchase. It's unique to your account. You can create multiple hosted checkouts if you have different apps or want to create links that redirect to different places in your app. When creating a hosted checkout, you can set default prices. If you don't pass prices or a transaction to the checkout directly, the default prices are used instead. [Add a custom subdomain](/build/checkout/custom-subdomains) to personalize your hosted checkout link with your company or app name, like `aeroedit.paddle.io`. This is optional, but recommended for best customer experience and conversion rates. 1. Go to **Paddle > Checkout > Hosted checkout**. 2. Click New hosted checkout 3. Enter a name and a description. This is typically your app name and any details for your reference. They're not shown to customers. 4. Enter a redirect URL. This should be a custom URL scheme or [universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/) that bounces users back to your app when their purchase is completed, for example `myapp://example-redirect`. 5. Optionally, paste the price ID you copied previously to the list of **Default prices** if you want this to be opened on every launch of the hosted checkout. You can add multiple price IDs if you have them to hand. 6. Click Save when you're done. 7. Click the button next to the hosted checkout you just created, then choose Copy URL from the menu. Keep this for the next step. ![Screenshot of the Paddle dashboard showing the 'New hosted checkout' form. The form includes required fields for Name, an optional Description text area with an information icon, and an optional Redirect URL field with an information icon. There's a 'Save' button in the blue color in the top right corner of the form."](/src/assets/images/tmp-build/dashboard-create-hosted-checkout-20250507.svg) ![Screenshot of the Paddle dashboard 'Hosted checkouts' list view. The interface displays a table with columns for Name, Description, and Redirect URL. Multiple entries are shown with placeholder content. A context menu is open for one of the items showing three options: Edit (with pencil icon), Copy URL (with copy icon), and Archive (with trash icon in red).](/src/assets/images/tmp-build/dashboard-copy-hosted-checkout-20250507.svg) ## Add a checkout button to your app Now, update your iOS app to add a button that: 1. Checks to see if in-app purchases are allowed on the device. 2. Checks to see if a user already purchased the item. 3. Constructs a URL using your hosted checkout launch URL, and a `price_id` query parameter with the price ID you copied previously as the value. Here's an example using SwiftUI: ```swift import SwiftUI import StoreKit // required for checking device payment capabilities using SKPaymentQueue struct PurchaseView: View { let checkoutBaseURL = "https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a" // replace with your checkout launch URL let priceId = "pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with a price ID or dynamically set it var body: some View { VStack { // Check if the device can make payments if SKPaymentQueue.canMakePayments() { // Create a purchase button with styling Button("Buy now") { openCheckout() } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } else { // Fallback text when purchases aren't available Text("Purchases not available on this device.") .foregroundColor(.secondary) } } .padding() } // Function to construct and open the checkout URL func openCheckout() { // Create URL with price_id parameter let checkoutURL = "\(checkoutBaseURL)?price_id=\(priceId)" if let url = URL(string: checkoutURL) { // Open URL in the default browser UIApplication.shared.open(url) } } } ``` If you've set a default price for the hosted checkout, you don't need to pass a `price_id` in the URL unless you want to open a checkout for a different price. For an optimized Apple Pay experience on mobile, append `&variant=express` to your hosted checkout URL to open an [express checkout](/concepts/sell/express-checkout). This requires [domain verification for Apple Pay](/concepts/payment-methods/apple-pay#verify-your-domain-for-apple-pay). ### Prefill information To make for a more seamless user experience, you can use [URL parameters](/paddle-js/about/hosted-checkout) to pass additional information to the hosted checkout. In this updated example, we pass customer details and a unique identifier for the customer in RevenueCat. ```swift import SwiftUI import StoreKit // required for checking device payment capabilities using SKPaymentQueue struct PurchaseView: View { let checkoutBaseURL = "https://pay.paddle.io/checkout/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a" // replace with your checkout launch URL let priceId = "pri_01h1vjg3sqjj1y9tvazkdqe5vt" // replace with a price ID or dynamically set it // Additional information // In a real app, this would come from your user authentication platform let appUserId = "85886aac-eef6-41df-8133-743cbb1daa4b" let userEmail = "sam@example.com" let countryCode = "US" let postalCode = "10021" var body: some View { VStack { // Check if the device can make payments if SKPaymentQueue.canMakePayments() { // Create a purchase button with styling Button("Buy now") { openCheckout() } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) } else { // Fallback text when purchases aren't available Text("Purchases not available") .foregroundColor(.secondary) } } .padding() } // Function to construct and open the checkout URL with customer data func openCheckout() { // Create URL components for building a URL with query parameters var urlComponents = URLComponents(string: checkoutBaseURL)! // Add the price ID and customer information as query parameters urlComponents.queryItems = [ URLQueryItem(name: "price_id", value: priceId), URLQueryItem(name: "app_user_id", value: appUserId), URLQueryItem(name: "user_email", value: userEmail), URLQueryItem(name: "country_code", value: countryCode), URLQueryItem(name: "postal_code", value: postalCode) ] // Open the checkout URL if it's valid if let url = urlComponents.url { UIApplication.shared.open(url) } } } ``` ## Handle fulfillment and provisioning When a customer completes a purchase, they'll be redirected back to your app. At this point, you need to handle fulfillment and unlock the features they bought. If you use the [RevenueCat x Paddle integration](https://www.paddle.com/revenuecat-integration-beta) to handle entitlements, you're all set. Here's how it works: 1. Paddle automatically sends data to RevenueCat about the completed checkout. 2. RevenueCat grants the user an entitlement based on [your product configuration](https://www.revenuecat.com/docs/offerings/products-overview). 3. Use the RevenueCat SDK to [check entitlement status](https://www.revenuecat.com/docs/customers/customer-info) in your iOS app. You can use webhooks to build your own fulfillment workflow. In this example, we'll grant users access when they've purchased our `Lifetime Access` product. #### Build a webhook handler When a customer creates or completes a transaction, Paddle can send a webhook to an endpoint you set up. 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](/build/mobile-apps/link-out-mobile-app-custom-workflow). ```typescript app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => { try { // You can verify the webhook signature here // We don't cover this in the tutorial but it's best practice to do so // https://developer.paddle.com/webhooks/signature-verification const payload = JSON.parse(req.body.toString()); const { data, event_type } = payload; const occurredAt = payload.occurred_at; // Listen for vital events from Paddle switch (event_type) { // 1. Record transactions in the database // Handle a new transaction // You can create a Transaction database to store records and associate them to a user case 'transaction.created': // Find the user associated with this transaction const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } }); if (userForTransaction) { await Transaction.create({ transactionId: data.id, userId: userForTransaction.id, subscriptionId: data.subscription_id, status: data.status, amount: data.amount, currencyCode: data.currency_code, occurredAt: occurredAt }); } break; // Handle a completed transaction // If you have a Transaction database, you can update the transaction record case 'transaction.completed': // Find the transaction by its ID const completedTransaction = await Transaction.findOne({ where: { transactionId: data.id } }); if (completedTransaction) { await completedTransaction.update({ status: data.status, subscriptionId: data.subscription_id, invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, billedAt: data.billed_at, updatedAt: data.updated_at }); } break; } res.json({ received: true }); } catch (error) { console.error('Error processing webhook:', error); return res.status(500).json({ error: error.message }); } }); ``` #### Unlock user access When you receive the [`transaction.completed`](/webhooks/transactions/transaction-completed) webhook, you can use the details to handle order fulfillment 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. ```typescript app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => { try { // You can verify the webhook signature here // We don't cover this in the tutorial but it's best practice to do so // https://developer.paddle.com/webhooks/signature-verification const payload = JSON.parse(req.body.toString()); const { data, event_type } = payload; const occurredAt = payload.occurred_at; // Listen for vital events from Paddle switch (event_type) { // 1. Record transactions in the database // Handle a new transaction // You can create a Transaction database to store records and associate them to a user case 'transaction.created': // Find the user associated with this transaction const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } }); if (userForTransaction) { await Transaction.create({ transactionId: data.id, userId: userForTransaction.id, subscriptionId: data.subscription_id, status: data.status, amount: data.amount, currencyCode: data.currency_code, occurredAt: occurredAt }); } break; // Handle a completed transaction // If you have a Transaction database, you can update the transaction record case 'transaction.completed': // Find the transaction by its ID const completedTransaction = await Transaction.findOne({ where: { transactionId: data.id } }); if (completedTransaction) { await completedTransaction.update({ status: data.status, subscriptionId: data.subscription_id, invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, billedAt: data.billed_at, updatedAt: data.updated_at }); // 2. Provision access to your app // Fetch the user associated with this transaction const user = await User.findOne({ where: { id: completedTransaction.userId } }); if (user) { // Fetch the items from the transaction const purchasedItems = data.items || []; // Add what access the user has based on the items they purchased // For this example, we're using access permissions and storing them in the user model on an accessPermissions field // We also map the Paddle product IDs to the access permissions // In a real app, you could use a database table for this mapping // Get existing permissions to see if any first const accessPermissions = user.accessPermissions ? JSON.parse(user.accessPermissions) : {}; // Map product IDs to access permissions // We add an additional product as an example of how you can handle multiple const productToPermission = { 'pro_01h1vjes1y163xfj1rh1tkfb65': 'lifetimeAccess', 'pro_01gsz97mq9pa4fkyy0wqenepkz': 'temporaryAccess' } purchasedItems.forEach(({ price }) => { const permissionKey = productToPermission[price.product_id]; if (permissionKey) accessPermissions[permissionKey] = true; }); // Update the user with their new access permissions await user.update({ accessPermissions: JSON.stringify(accessPermissions), }); } } break; } res.json({ received: true }); } catch (error) { console.error('Error processing webhook:', error); return res.status(500).json({ error: error.message }); } }); ``` #### Create a notification destination To start receiving webhooks, [create a notification destination](/webhooks/about/notification-destinations). This is where you can tell Paddle which events you want to receive and where to deliver them to. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click New destination. 3. Set **Notification type** to **URL** and enter the URL for your webhook handler. 4. Choose the `transaction.completed` event. You can always edit events later. 5. Click Save destination. ![Illustration of the new destination drawer in Paddle. It shows fields for description, type, URL, and version. Under those fields, there's a section called events with a checkbox that says 'select all events'](/src/assets/images/tmp-build/create-webhook-destination-20240912.svg) ## Test the complete flow We're now ready to test the complete purchase flow end-to-end! If you're using a sandbox account, you can take a test payment using [our test card details](/concepts/payment-methods/card): An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` Any name Any valid date in the future. `100` Before you go live, extend your Apple Pay integration by [verifying your domain for Apple Pay](/concepts/payment-methods/apple-pay). This lets you launch the Apple Pay modal directly from your checkout. ## Next steps That's it. Now you've built a purchase workflow that links out to Paddle Checkout, 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. Accept global payment methods out of the box, including credit/debit cards, Apple Pay, and more. Get revenue metrics, customer analytics, and detailed sales reports in your dashboard. Allow customers to manage invoices, subscriptions, and account details without your intervention. ### Build a web checkout Our tutorial uses a hosted checkout to build a payment workflow. You can also Paddle.js to build pricing pages and signup flows on the web, then redirect people to your app. End-to-end onboarding, API keys, and going live with Paddle Billing. Show local currencies and pricing by country, out of the box. Trigger business logic from Paddle.js events in your web checkout. ### 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. Let customers temporarily suspend or reactivate their subscriptions with your app. Learn how to offer and manage free trials to attract more signups. Recover failed payments and reduce churn with Paddle Retain. --- # Add or remove items from a subscription URL: https://developer.paddle.com/build/subscriptions/add-remove-products-prices-addons Add items to subscriptions when customers want to take additional users, modules, or other kinds of recurring addons. Remove items when customers want to change or cancel them. Paddle supports multi-product subscriptions, letting you build complex pricing models. Add or remove items from a subscription to make changes to customer plans. Add or remove items from a subscription when: - Customers pay per user, and they add users to their subscription - A customer wants to stop paying for ("cancel") an additional recurring product, like a reporting module - You have a tiered or volume pricing model and need to set the tier to bill for You can add items, remove items, and change quantities in the same request. We've split them into separate examples here for simplicity. ## How it works You might have a subscription billing model that's made up of multiple components. For example, you might offer a base plan and additional modules or users. In Paddle, you don't need to create multiple subscriptions for multiple products. Each subscription holds an `items` list that can contain multiple products. When a customer does something like takes on a new module or decreases their user count, you can make changes to the items list on their subscription bill for the changes. Keep in mind that customers might say: - They're "canceling" a product, like a module or other recurring addon, when they'd like to remove it. Canceling items doesn't [cancel a subscription entirely](/build/subscriptions/cancel-subscriptions). - They're "subscribing" or "upgrading" when they add a product or change quantities. You don't need to create a new subscription to bill for new products. Recurring items on a subscription must have the same billing interval. For example, you can't have a subscription with some prices that are billed monthly and some products that are billed annually. ### Proration When you make changes to items, you can determine how Paddle should bill for those changes. This is called [proration](/concepts/subscriptions/proration). Paddle's subscription billing engine calculates proration to the minute, allowing for precise billing. Before you add or remove items on a subscription, you can preview the changes first. This lets you see prorated charges for any changes that you're making before charging for them. You might present this to the customer in your frontend. ### Related subscription changes Adding or removing items is typically used when you want to make changes to recurring addons, like optional modules or seats. Depending on what you're looking to do, you might also like to: - [**Replace items to upgrade or downgrade a subscription**](/build/subscriptions/replace-products-prices-upgrade-downgrade) Upgrading or downgrading a subscription plan typically involves replacing products. For example, you might replace a "Starter plan" product with a "Premium plan" product to upgrade. - [**Create one-time charges for non-recurring items**](/build/subscriptions/bill-add-one-time-charge) One-time charges aren't included in the `items` list for a subscription, since they aren't recurring. Create a one-time charge instead. For example, you might bill for an "Out of hours support incident" as and when it happens. ## Before you begin To add or remove items from a subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `trialing` to get a list of subscriptions in trial. ## Add items to a subscription Add recurring items to subscriptions when customers want to take additional modules or services. They might call this "upgrading." You can use any [proration billing mode](/concepts/subscriptions/proration). 1. Go to **Paddle > Customers**. 2. Find the customer whose subscription you want to add an item to, then click them to open the customer page. 3. Find the subscription that you want to change under the Subscriptions heading and click it to open the subscription page. 4. From the subscription overview page, click Edit subscription 5. Click the Add line item link to add items new items to the items list. Choose a product, related price, and quantity for each item. 6. Choose a product, related price, and quantity for each item. 7. Choose whether to bill for the prorated amount or full amount, and when it should be charged. 8. If you don't want to bill for these changes at all, toggle **Charge for what's remaining of the current billing period?** off. 9. Click Continue to review. 10. Review the summary of your changes. 11. Click Save to apply your changes. Add items to a subscription using the API in three steps: 1. **Extract existing items** Get the existing items on the subscription to extract the `price.id` and `quantity` for each item. 2. **Preview change** Preview how much the customer is going to be charged on a regular basis. Present this to customers if you offer an in-app workflow to update items. 3. **Update subscription** Send a request to update the subscription with the changes you previewed. ### Extract existing items Send a request to the `/subscriptions/{subscription_id}` endpoint to get the existing items on the subscription. Extract the `price.id` and `quantity` for each item in the `items` array — you'll need these to build your update request. ### Preview change Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. In your request, include an an `items` array with an object containing a `price_id` and `quantity` for each item you want on the subscription — include existing items you want to keep, plus any new ones. Omit `quantity` for existing items where you're not changing the amount. Include `proration_billing_mode` to tell Paddle how to bill for the changes. For `prorated_immediately` or `full_immediately` on automatically-collected subscriptions, Paddle tries to collect right away. Optionally, include `on_payment_failure` to control payment failure behavior (defaults to `prevent_change`). This example adds two new items with `quantity: 1` each. The existing item has no `quantity` so it stays unchanged. `proration_billing_mode` is `prorated_next_billing_period`. Previews include `immediate_transaction`, `next_transaction`, and `recurring_transaction_details`. In this example, `proration_billing_mode` is `prorated_next_billing_period`, so `immediate_transaction` is `null` and proration is charged on the next renewal. You should present this to customers if you offer an in-app workflow to update items. ### Update subscription Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, include the same body as the preview request. Paddle updates the subscription. ## Remove items from a subscription Remove items from subscriptions when customers no longer want additional modules or other addons. They might call this "canceling" those modules or addons. Depending on your pricing model, you might also do this as part of [an upgrade or downgrade workflow](/build/subscriptions/replace-products-prices-upgrade-downgrade), too. For example, some items might be incompatible with entry-level plans, or might be included in higher-level plans. You can choose any [proration billing mode](/concepts/subscriptions/proration). 1. Go to **Paddle > Customers**. 2. Find the customer whose subscription you want to remove an item from, then click them to open the customer page. 3. Find the subscription that you want to change under the Subscriptions heading and click it to open the subscription page. 4. From the subscription overview page, click Edit subscription 5. Click the button next to an item in the list, then choose Remove item from the menu. 6. Choose whether to credit the prorated or full amount, and when to issue it. 7. If you don't want to credit for these changes at all, toggle **Issue credit for what's remaining of the current billing period?** off. 8. Click Continue to review. 9. Review the summary of your changes. 10. Click Save to apply your changes. Remove items from a subscription using the API in three steps: 1. **Extract existing items** Get the existing items on the subscription to extract the `price.id` and `quantity` for each item. 2. **Preview change** Preview how much the customer is going to be charged on a regular basis. Present this to customers if you offer an in-app workflow to update items. 3. **Update subscription** Send a request to update the subscription with the changes you previewed. ### Extract existing items Send a request to the `/subscriptions/{subscription_id}` endpoint to get the existing items on the subscription. Extract the `price.id` and `quantity` for each item in the `items` array — you'll need these to build your update request. ### Preview change Subscriptions must have at least one item. Consider [canceling a subscription entirely](/build/subscriptions/cancel-subscriptions), or [pausing](/build/subscriptions/pause-subscriptions) to stop billing temporarily. Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. Include an `items` array with price IDs and quantities for items you want to **keep** — omit any items you want to remove. Include `proration_billing_mode` to determine how to credit the removed items. Optionally, include `on_payment_failure` to control payment failure behavior (defaults to `prevent_change`). This example keeps two items and removes the third by omitting it. `proration_billing_mode` is `prorated_next_billing_period`, so Paddle credits the removed item on the next renewal. Previews include `immediate_transaction`, `next_transaction`, and `recurring_transaction_details`. In this example, `proration_billing_mode` is `prorated_next_billing_period`, so `immediate_transaction` is `null` and Paddle calculates a credit deducted from the next renewal. ### Update subscription Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, include the same body as the preview request. Paddle updates the subscription. ## Change quantities Change quantities when customers want to increase or decrease the number of units of an item that they pay for. You might change quantities if your pricing is per user or seat. They might call it "upgrading" when increasing units, and "downgrading" when decreasing units. You can choose any [proration billing mode](/concepts/subscriptions/proration). 1. Go to **Paddle > Customers**. 2. Find the customer whose subscription you want to change quantities for items on, then click them to open the customer page. 3. Find the subscription that you want to change under the Subscriptions heading and click it to open the subscription page. 4. From the subscription overview page, click Edit subscription 5. Make changes to quantities in the items list. 6. Depending on the changes you made, choose to either credit or charge for the prorated or full amount, and charge or issue for it now or later. 7. If you don't want these changes to be charged or credited at all, toggle the proration option off. 8. Click Continue to review. 9. Review the summary of your changes. 10. Click Save to apply your changes. Change quantities on a subscription using the API in three steps: 1. **Extract existing items** Get the existing items on the subscription to extract the `price.id` and `quantity` for each item. 2. **Preview change** Preview how much the customer is going to be charged on a regular basis. Present this to customers if you offer an in-app workflow to update items. 3. **Update subscription** Send a request to update the subscription with the changes you previewed. ### Extract existing items Send a request to the `/subscriptions/{subscription_id}` endpoint to get the existing items on the subscription. Extract the `price.id` and `quantity` for each item in the `items` array — you'll need these to build your update request. ### Preview change Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. Include an `items` array with the same price IDs and updated quantities. You can omit `quantity` for items you're not changing. Include `proration_billing_mode` to tell Paddle how to bill for the quantity changes. For `prorated_immediately` or `full_immediately` on automatically-collected subscriptions, Paddle tries to collect right away. Optionally, include `on_payment_failure` to control payment failure behavior (defaults to `prevent_change`). This example increases seats from `10` to `30`. The voice rooms addon has no `quantity` so it stays unchanged. Previews include `immediate_transaction`, `next_transaction`, and `recurring_transaction_details`. In this example, `proration_billing_mode` is `prorated_immediately`, so Paddle calculates and charges proration right away, detailed in `immediate_transaction`. ### Update subscription --- # Build an overlay checkout URL: https://developer.paddle.com/build/checkout/build-overlay-checkout Get a step-by-step overview of how to build a complete overlay checkout — including initializing Paddle.js, passing settings and items, prefilling customer information, and next steps. The checkout is where customers make purchases. For SaaS businesses, it's the process where customers enter their details and payment information, and confirm that they'd like to sign up for a subscription with you. You can use [Paddle.js](/paddle-js) to quickly add an overlay checkout into your app. [Overlay checkout](/concepts/sell/overlay-checkout) lets you present customers with an overlay that handles all parts of the checkout process — minimal frontend coding required. Explore the code for this tutorial and test right away using our overlay checkout pen. ## What are we building? In this tutorial, we'll launch a multi-page overlay checkout for two items in our product catalog, then we'll extend it by passing customer information. ![Illustration of an overlay checkout. The payment method form is open, with buttons for Apple Pay and PayPal along with the card details form underneath. The items list shows one item for 'Professional plan', with tax and totals underneath. The total is $3600, displayed at the top-left of the checkout.](/src/assets/images/tmp-build/checkout-overlay-checkout-hero-20230831.svg) We'll learn how to: - Include and set up Paddle.js using a client-side token - Pass items to overlay checkout using `Paddle.Checkout.open()` or HTML data attributes - Take a test payment - Prefill customer information using `Paddle.Checkout.open()` or HTML data attributes If you like, you can [view on CodePen](https://codepen.io/heymcgovern/pen/wvZMmGq) and follow along. ## Before you begin ### Choose a checkout implementation This tutorial walks through creating an [overlay checkout](/concepts/sell/overlay-checkout). You can also create [inline checkouts](/concepts/sell/branded-integrated-inline-checkout), which lets you build Paddle Checkout right into your app or website. We recommend building an overlay checkout if you're new to Paddle. Inline checkouts use the same JavaScript methods as overlay checkouts, so you can switch to an inline checkout later. ![Illustration showing an overlay checkout. The items summary is simplified, showing gray boxes to illustrate items and totals. The right-hand side of the overlay checkout shows the payment form, with buttons for Apple Pay, PayPal, and the card details entry form.](/src/assets/images/tmp-build/checkout-overlay-checkout-overview-20230824.svg) Integrate Paddle in just a few lines of code. Launches an overlay to capture payment. Quick to set up: add Paddle.js to your site and call `Paddle.Checkout.open()` or use `data-*` HTML attributes to launch the checkout. Checkout opens as a modal overlay on top of your page, focusing the user on payment. Customize colors, logo, and branding in the Paddle dashboard. Structure and experience is managed by Paddle. ![Illustration showing an inline checkout. The inline checkout frame shows the payment form, and is in crop-marks on the left to show where it's been embedded. The items list is on the right with a total at the top.](/src/assets/images/tmp-build/checkout-inline-checkout-overview-20230824.svg) Get complete control of the checkout experience. Captures payment directly in your app. Embed Paddle Checkout directly into your page layout. Use a container element and set `displayMode` to `inline`. Checkout form is part of your page flow—lets you create fully integrated or branded payment experiences. Full control over surrounding layout and styling; you can design the context and surrounding UX. To learn more about the differences between overlay and inline checkouts, see [Paddle Checkout](/concepts/sell/self-serve-checkout) ### Create products and prices Paddle Checkout works with products and prices to say what a customer is purchasing, so you'll need to [create a product and at least one related price](/build/checkout/build-branded-inline-checkout) to pass to your checkout. ### Set your default payment link You'll also need to: - [Set your default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link**. - Get your default payment link domain approved, if you're working with the live environment. ## Overview Add an overlay checkout to your website or app in four steps: 1. [**Include and initialize Paddle.js**](#include-and-initialize-paddlejs) Add Paddle.js to your app or website, so you can securely capture payment information and build subscription billing experiences. 2. [**Add an overlay checkout button**](#add-an-overlay-checkout-button) Set any element on your page as a launcher for Paddle Checkout. 3. [**Take a test payment**](#take-a-test-payment) Make sure that your checkout loads successfully, then take a test payment. 4. [**Prefill customer information**](#prefill-customer-information) Extend your checkout by prefilling customer and address information. ## Include and initialize Paddle.js [Paddle.js](/paddle-js) is a lightweight JavaScript library that lets you build rich, integrated subscription billing experiences using Paddle. We can use Paddle.js to securely work with products and prices in our Paddle system, as well as opening checkouts and capturing payment information. ### Include Paddle.js script Start with a blank webpage, or an existing page on your website. Then, [include Paddle.js](/paddle-js/about/include-paddlejs) by adding this script to the ``: ```html ``` ### Set environment We recommend [signing up for a sandbox account](https://sandbox-login.paddle.com/signup?utm_source=dx&utm_medium=dev-docs) to test and build your integration, then switching to a live account later when you're ready to go live. If you're testing with the [sandbox](/sdks/sandbox), call [`Paddle.Environment.set()`](/paddle-js/methods/paddle-environment-set) and set your environment to `sandbox`: ```html ``` ### Pass a client-side token Next, go to **Paddle > Developer tools > Authentication** and create a client-side token. [Client-side tokens](/paddle-js/about/client-side-tokens) let you interact with the Paddle platform in frontend code, like webpages or mobile apps. They have limited access to the data in your system, so they're safe to publish. In your page, call [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) and pass your client-side token as `token`. For best performance, do this just after calling `Paddle.Environment.set()`, like this: ```html ``` ## Add an overlay checkout button Next, we'll set an element on our page as a launcher for our overlay checkout. Overlay checkout works by presenting an overlay to handle the entire checkout process. When our button or other launcher element is clicked, Paddle.js launches a checkout for us. ### Create checkout button element Any element can be a launcher for an overlay checkout. In our sample, we're using a link (``) that points to `#`. This means it doesn't open a new page. ```html Sign up now ``` ### Set as a checkout launcher Next, we'll make our checkout element open an overlay checkout by making it a launcher. Paddle.js comes with the [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) method, which lets you open a checkout with [settings](/build/checkout/set-up-checkout-default-settings), [items](/build/checkout/pass-update-checkout-items), and [customer](/build/checkout/prefill-checkout-properties) information. In our sample, we've created a function called `openCheckout()` to open a checkout. Here's how it works: 1. We create a variable called `itemsList` and pass an array of objects, where each object contains a `priceId` and `quantity`. 2. We create a function called `openCheckout()` that takes a parameter called `items`. 3. In our `openCheckout()` function, we call `Paddle.Checkout.open()`, passing the value of `items` as the items list for the checkout. 4. We add an `onclick` event to our checkout button to call `openCheckout()` when clicked, passing our `itemsList` variable as a parameter. ```html Sign up now ``` ## Take a test payment We're now ready to test. Save your page, then open it in your browser. Click on your "Sign up now" button and Paddle.js should open an overlay checkout for the items that we passed. If you're using a sandbox account, you can take a test payment using [our test card details](/concepts/payment-methods/card): An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` Any name Any valid date in the future. `100` ![Short animation showing launching an overlay checkout, entering contact information, and entering test payment details.](/src/assets/images/tmp-build/overlay-checkout-test-payment-20230314.gif) If the checkout doesn't appear, or you get a message saying "Something went wrong," you can open your browser console to see any specific error messages from Paddle.js to help you troubleshoot. Use `⌘ Command` + `⌥ Option` + `J` (Mac) or `Ctrl` + `⇧ Shift` + `J` (Windows) to quickly open your browser console in Google Chrome. #### Common problems Check that: - You added a default payment link to your checkout under **Paddle > Checkout > Checkout settings > Default payment link**, and that this matches the domain where you're testing. You can use `localhost` if you're testing locally on sandbox. - You included Paddle.js correctly. If you're moving from Paddle Classic, the CDN URL has changed. - Your client-side token is correct and passed to `Paddle.Initialize()`. - You set the correct environment. - The Paddle IDs for price entities that you passed are correct. Sandbox and live systems are separate, so make sure you're passing price IDs for the environment that you're working in. - If you're using HTML data attributes, you used single quotation marks around the value of `data-items`, and double quotation marks around the property names and string values inside it ## Prefill customer information At this point, we've passed items to our checkout. When we click our launcher, Paddle opens a checkout for the items that we passed. Paddle.js also lets you [pass customer information to a checkout](/build/checkout/prefill-checkout-properties). When we click our launcher, Paddle opens a checkout with the customer information prefilled. This means the first page of checkout is skipped entirely, so customers land on a screen where they can enter their payment information. You can present customers with a one-page checkout experience by passing `variant` with the value `one-page` [as a checkout setting](/build/checkout/set-up-checkout-default-settings). You don't need to prefill information. [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) takes a `customer` parameter, which lets you pass customer and address information. In our sample, we've extended our `openCheckout()` function so that it passes customer and address information to our checkout. Here's what's going on: 1. We create a variable called `customerInfo`, with an `email` key and an object for `address`. You may also pass Paddle IDs for an existing customer or address here. 2. We update our `openCheckout()` function so it takes another parameter called `customer`. 3. In our `openCheckout()` function, we added the `customer` parameter and passing the value of `customer` to this. 4. We updated the `onClick` event on our checkout button to pass our `customerInfo` variable as the parameter for `customer`. ```html Sign up now ``` ### Test your work Save your page, then open it in your browser. Click on your "Sign up now" button and Paddle.js should open an overlay checkout with the customer information prefilled. You should land on the second screen, ready to enter payment information. ![Short animation showing overlay checkout landing on a screen asking for card details.](/src/assets/images/tmp-build/overlay-checkout-one-page-20230314.gif) Where there's a problem passing optional information to a checkout, Paddle.js opens the checkout but emits a [`checkout.warning`](/paddle-js/events/checkout-warning) event. This means customers are always able to complete a purchase provided you've initialized Paddle.js and passed items successfully. You can use the `eventCallback` parameter for [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) to work with events emitted by Paddle.js. To print events emitted by Paddle.js to the console, update your `Paddle.Initialize()` function so it looks like this. ```javascript highlightLines="4-6" Paddle.Initialize({ token: "test_7d279f61a3499fed520f7cd8c08", // replace with a client-side token // prints events to console for debugging eventCallback: function(data) { console.log(data); } }); ``` Then, open your browser console and check for events with the name [`checkout.warning`](/paddle-js/events/checkout-warning). They should tell you what the problem is. Use `⌘ Command` + `⌥ Option` + `J` (Mac) or `Ctrl` + `⇧ Shift` + `J` (Windows) to quickly open your browser console in Google Chrome. #### Common problems Check that: - The email address that you passed is formatted correctly. Email addresses must not include spaces or non-ASCII characters. - If you passed an address country, you also passed a ZIP/postal code if the country requires it. Most countries don't require a ZIP/postal code, but it's [required in some places](/concepts/sell/supported-countries-locales) for tax calculation and fraud prevention. - The Paddle IDs for customer, address, or business entities that you passed are correct. Sandbox and live systems are separate, so make sure you're passing Paddle IDs for the environment that you're working in. ## Next steps That's it. Now you've built a checkout, you might like to extend Paddle Checkout by automatically applying a discount, passing optional checkout settings, or building a success workflow. ### Automatically apply a discount Extend your checkout by passing a discount. When we click our launcher, Paddle opens a checkout with the discount automatically applied (where it's valid). Learn how to pass customer or business info (like email, name, or address) to Paddle Checkout. Set up and manage discount codes or time-limited promotions for your products. ### Pass checkout settings You don't need to pass checkout settings when working with overlay checkout, but you can use them to give you more control over how opened checkouts work. For example, you can set the language that Paddle Checkout uses, hide the option to add a discount, or restrict payment methods shown to customers. See all the settings you can pass to configure your overlay checkout. Full API documentation for opening and customizing checkouts. ### Build a success workflow When customers complete checkout, Paddle Checkout has a final screen that lets customers know that their purchase was successful. If you like, you can redirect customers to your own page or use JavaScript event callbacks to build a more advanced success workflow. Redirect customers or add logic to handle post-checkout workflows. See all of the events you can listen to for customizing your checkout experience. --- # Cancel an invoice URL: https://developer.paddle.com/build/invoices/cancel-invoices Cancel an invoice if you created it in error. Canceled invoices remain on your system for record-keeping purposes. If you've created an invoice in error, you can cancel it to say that it's no longer needed. ## How it works Invoices work using manually-collected transactions. These are transactions where the `collection_mode` is set to `manual`, which means that Paddle sends an invoice that customers must pay themselves by [bank transfer](/concepts/payment-methods/wire-transfer) or using [Paddle Checkout](/concepts/sell/self-serve-checkout). You can change the status of a manually-collected transaction to `canceled` to mark it as canceled. When you cancel an invoice: - It's no longer due, so customers don't need to pay it - Links to pay the invoice using Paddle Checkout no longer work - It doesn't count as revenue for your [reports](/build/reports) - You can't uncancel it, or make any other changes to it You can cancel manually-collected transactions that are `billed` (issued invoices) or `ready`. If a transaction is `completed`, meaning a customer has paid an invoice, you can [create an adjustment to refund](/build/transactions/create-transaction-adjustments) all or part of it instead. ### Credit an invoice You might be able to correct some errors on an issued invoice by [creating a credit](/build/transactions/create-transaction-adjustments) rather than canceling it. For example, if you [create and issue an invoice](/build/invoices/create-issue-invoices) then want to remove an item, you can create an adjustment for the item you want to remove. Paddle automatically applies the credit to the issued invoice, so the amount the customer owes is reduced. When you credit the full value of a transaction, it's marked as `completed`. It's no longer due. ## Cancel an invoice Send a request to the `/transactions/{transaction_id}` endpoint to update a manually-collected transaction, setting `status` to `canceled` in your request. 1. Go to **Paddle > Transactions**. 2. Use the search box and the **Filter by** drop-down to find a transaction you want to cancel. 3. Click into the transaction, then click the menu and choose Cancel transaction 4. Click Cancel transaction to confirm. Set `status` to `canceled` in your request body. You don't need to include anything else. Transactions must be manually collected and `billed` or `ready` to cancel. Use [list transactions](/api-reference/transactions/list-transactions) with `collection_mode=manual` and `status=billed,ready` to find eligible transactions. ## Cancel related subscription Paddle automatically creates a subscription when you mark a manually-collected transaction as `billed`. When you cancel an invoice, its related subscription isn't automatically canceled. This means the subscription remains active, so a customer may still have access to your app. Paddle reinvoices for the subscription on renewal. ### Cancel using the Paddle dashboard or API Use the Paddle dashboard or API to [cancel the related subscription](/build/subscriptions/cancel-subscriptions) after you have canceled the transaction. ### Build a workflow using webhooks You can build a workflow to programmatically cancel subscriptions using webhooks: 1. **Subscribe to transaction canceled events** [Create or update a notification destination](/webhooks/about/notification-destinations), then subscribe to [`transaction.canceled`](/webhooks/transactions/transaction-canceled) events. 2. **Check that the transaction is manually collected and for a subscription** In the `data` payload for received events, check that `collection_mode` is `manual` and that `subscription_id` isn't `null`. This means the transaction is an issued invoice related to a subscription. 3. **Extract subscription ID, then cancel** Extract `subscription_id` from the data payload, then use the [cancel a subscription operation](/api-reference/subscriptions/cancel-subscription) to cancel. --- # Configure Payment Recovery and dunning options URL: https://developer.paddle.com/build/retain/configure-payment-recovery-dunning Choose what happens when payment fails for an automatically-collected subscription. You can turn on Tactical Retries, send optimized email notifications, and show retry payment forms in your app or website. When payment fails for an automatically-collected subscription, the subscription status changes to past due and [Payment Recovery](/concepts/retain/payment-recovery-dunning), part of [Paddle Retain](/concepts/retain), gets to work to automatically recover the payment for you. This process is called dunning. You can configure how Payment Recovery notifies customers about failed payments, turn on Tactical Retries, and choose what happens to past due subscriptions when the payment isn't recovered. ![Screenshot of the Retain Payment Recovery dashboard. It has a hero image detailing key metrics like "Current recovering" and "Recovery rate," and shows various configurable features like Tactical retries, Payment recovery emails, and notifications, each with a toggle switch.](/src/assets/images/tmp-build/retain-payment-recovery-20250701.svg) ## How it works [Paddle Retain](/concepts/retain) combines world-class subscription expertise with algorithms that use billions of data points to automatically reduce churn. Paddle Billing is fully integrated with Retain, meaning it automatically handles dunning and retention for you. [Payment Recovery](/concepts/retain/payment-recovery-dunning), part of Paddle Retain, automatically recovers subscriptions that are at risk of churning because of payment failure and expired payment methods. If you use Paddle Billing without integrating with Paddle Retain, failed payments for automatically-collected subscriptions are retried up to seven times over a 30-day window before they're canceled. Turn on Paddle Retain for more comprehensive payment recovery options and control over the customer experience. To learn more about Payment Recovery, see [Payment Recovery](/concepts/retain/payment-recovery-dunning) ## Before you begin - **Set up Paddle Retain** If you haven't already, connect Paddle Retain to your billing platform and [set up Paddle Retain](/build/retain). - **Make sure you've installed Paddle.js** Paddle.js must be installed and verified as installed on a public page on your site. [Follow the instructions during setup](/build/retain), click **Edit** under **Paddle.js is not installed**, or click **Install** under **Paddle.js is not installed in web app**. - **Check your Retain email settings** Emails sent from Paddle Retain are designed to look like they come from you or someone on your team. Review the email sender and reply-to addresses for your email addresses in **Paddle > Retain > Settings** under **Email sender details**. Use Paddle Billing? Billing automatically [integrates with Retain](/paddle-js/about/include-paddlejs), so you're ready to get started. Go to **Paddle > Retain > Settings** and review your setup. ## Pause or cancel past due subscriptions When all payment recovery attempts are exhausted, Paddle Retain can automatically pause or cancel subscriptions for you. You can choose whether subscriptions should [pause](/build/subscriptions/pause-subscriptions) or [cancel](/build/subscriptions/cancel-subscriptions). In Paddle Billing, canceled subscriptions can't be reinstated. Create a new subscription for customers who have canceled if they want to return. 1. Go to **Paddle > Retain > Payment recovery**. 2. Select the amount of days in which payment recovery is attempted from the first dropdown under **Subscription status**. 3. Choose "Pause subscription" or "Cancel subscription" from the second dropdown under **Subscription status**. 4. Click Save when you're done. ![Illustration of the subscription status settings in Retain. It shows a rule being configured: "If a customer's payment has not been recovered for 30 days, then..." with options to "Cancel subscription" or "Pause subscription".](/src/assets/images/tmp-build/retain-payment-recovery-sub-status-20250701.svg) ## Send optimized payment recovery emails Paddle Retain sends emails to customers to let them know that their renewal wasn't processed successfully. You can preview messages, but we handle the content for you. Our team of experts have sent millions of messages, testing and optimizing the content and cadence across hundreds of thousands of transactions. We found that [plaintext, personal emails work best](https://www.profitwell.com/recur/all/lessons-from-sending-millions-of-delinquent-churn-emails). Retain sends up to four payment recovery emails. The exact timing of the Retain emails depends on some algorithmic work that we do behind the scenes, but this is when emails tend to be sent: | # | Subject | Day sent | |:-:|--------------------------------------------------------------------------|----------| | 1 | Your 💳 payment for [your product] failed | Day 1 | | 2 | 2nd notice: Another unsuccessful payment for your [your product] account | Day 3 | | 3 | 3rd attempt: Unsuccessful payment for [your product] | Day 5 | | 4 | Final notice: Need updated [your product] billing information | Day 7 | We've A/B tested thousands of emails and their cadence to optimize recovery rates. 1. Go to **Paddle > Retain > Payment Recovery**. 2. Toggle **Payment recovery emails** on to send notifications by email. 3. Click Preview to show a preview of the email that sends. ![Illustraion of a preview for a payment recovery email within the Retain interface. The example email is the first attempt, with the subject "Your card payment for AeroEdit failed" and a personalized message asking the customer to update their information.](/src/assets/images/tmp-build/retain-payment-recovery-email-20250701.svg) ## Turn on payment recovery notifications Payment recovery notifications reach customers when they're using your product, maximizing the likelihood of payment recovery. They prompt customers to update expired credit cards in your web app or on your commercial website with an unobtrusive notification. When customers click the link to update, they're presented with a secure form to enter their details there and then — no need to go to another page. 1. Go to **Paddle > Retain > Payment recovery**. 2. Toggle **Payment recovery notifications** on to prompt customers to update their payment details in-app. 3. Click Preview to show a preview of the notification that sends. ![Illustration of a preview for a "Payment recovery notification" within the Retain interface. The pop-up notification informs the user that their recent payment has failed and prompts them to update their credit card information.](/src/assets/images/tmp-build/retain-payment-recovery-notification-20250701.svg) ## Turn on Tactical Retries You only have so many chances to retry a credit card payment. Tactical Retries analyzes billions of credit card transactions across the Retain network and uses algorithms to retry payments at the best time for success. We look at product type, card type, customer location, and more than fifteen other factors to [boost payment recovery by an additional 10 to 15 percent](https://www.profitwell.com/recur/all/introducing-retain-tacical-retries). 1. Go to **Paddle > Retain > Payment recovery**. 2. Toggle **Tactical retries** on. ![Illustration of the "Tactical retries" feature in the Retain dashboard. The toggle switch is enabled, indicating that the system will automatically retry failed payments at optimal times.](/src/assets/images/tmp-build/retain-payment-recovery-tactical-retries-20250701.svg) ## Send payment recovery messages by SMS SMS is a reliable way of reaching customers, with 90% of text messages being read within three minutes of receipt. Retain can send text messages to customers to further boost payment recovery, targeting customers at the right time in a highly personalized way. Messages include a link to update payment information — all handled by [Paddle.js](/paddle-js) on your website, with no sign in required. 1. Grab a copy of the [Retain SMS phone number template](https://docs.google.com/spreadsheets/d/10HsgmjFnxcNCA7mzEd-NsDKZRmvevTmgCzcCHzUiy1k/edit#gid=0) and fill it in. Send it to us at [sellers@paddle.com](mailto:sellers@paddle.com?subject=RETAIN%20Payment%20recovery%20SMS). 2. Fill out the [Retain SMS recovery form](https://docs.google.com/forms/d/133Fvd-O_Tx4Re7zEIyLEQ6EMxeXMePr7nLRSMi0-4TI/viewform). We use this information to register your details with Twilio, our SMS provider. 3. We'll take care of the rest and reach out when you're ready. This may take fifteen business days. ## Simulate a payment recovery attempt Now you've set up Payment Recovery, [simulate a payment recovery attempt](/paddle-js/about/test-retain) to see how it looks to customers. --- # Localize prices URL: https://developer.paddle.com/build/products/offer-localized-pricing Improve conversion rates by offering customers prices in local currencies. Let Paddle automatically convert amounts, and set country specific pricing for key markets. Paddle supports [over 200 countries and territories](/concepts/sell/supported-countries-locales) and [30 currencies](/concepts/sell/supported-currencies), with no extra setup or engineering effort required. You can localize prices to build customer confidence and improve payment acceptance. Localized pricing in Paddle lets you do things like: - Automatically convert prices to local currencies. - Offer US Dollar pricing in non-US markets. - Set different prices for different countries or regions that share a currency. - Price according to willingness-to-pay and purchasing power. Paddle automatically handles conversion into your balance currency for you, meaning you see the amount you earned in your preferred currency no matter what currency customers pay in. ## How it works [Complete products in Paddle](/build/products/create-products-prices) are made up of a product entity and related price entities. [Price entities](/api-reference/prices) describe how much and how often a product is charged. When you create a price, you can set how much it costs and its currency. This is called the base price. You can charge all customers your base price, or you can localize your prices using: - [**Automatic currency conversion**](#automatic-currency-conversion) Let Paddle automatically convert your prices into the local currency for a customer at checkout. - [**Country specific pricing**](#country-specific-pricing) Override base prices with custom prices and currencies for countries that you choose. Automatic currency conversion and country specific pricing work together — you don't have to pick one or the other, you can use both. If you like, you can just charge your base prices, turning off price localization altogether. ### Automatic currency conversion Paddle can automatically convert prices into local currencies for customers at checkout. For example, your base price can be in US Dollar (`USD`), but customers can pay in Pound Sterling (`GBP`), Brazilian Real (`BRL`), or Indian Rupee (`INR`) depending on their location. Offering local currencies is recommended because: - It builds trust by helping customers understand exactly what they're paying. - It means customers don't incur FX fees from their bank when making a payment. - Local banks are more likely to approve payments in the local currency. You can turn on automatic currency conversion for all supported currencies, or choose the currencies that you want to enable it for. ### Country specific pricing While offering prices in local currencies is important, you can further boost conversion by tailoring prices to local market conditions. For example, 100 `USD` buys you less in the and more in than it does in the . Use country specific pricing in Paddle to manually override base prices with custom prices for countries that you choose. It lets you price according to purchasing power and willingness-to-pay, meaning: - You can maximize revenue in markets where willingness-to-pay is higher, charging more than your base price. - You can increase your volume of sales by expanding into emerging markets, pricing according to purchasing power. For example: | | United States | United Kingdom | Brazil | India | |-----------------------------------|---------------|----------------------|----------------------|-----------------------| | **Automatic currency conversion** | `100 USD` | `79 GBP` | `478 BRL` | `8200 INR` | | **Price overrides** | `100 USD` | `90 GBP` [`115 USD`] | `52 USD` [`248 BRL`] | `2320 INR` [`28 USD`] | These figures are illustrative. They may not be exactly what you see at checkout. As well as setting the unit price, you can set the currency too. This is useful for countries like Brazil, where `USD` is often preferred to `BRL`. You can create country specific prices when creating prices in Paddle. They're called price overrides in the API. ### Customer experience Paddle automatically shows the correct prices for a customer at [checkout](/concepts/sell/self-serve-checkout). When opening a checkout, Paddle uses geolocation to estimate where a customer is buying from. If a customer changes the preselected country, Paddle gets localized prices for the country they selected. Paddle shows localized prices in this order: 1. Country specific price and currency (price override) for the customer country 2. Automatically converted price in the local currency for the customer country 3. Base price in base currency When [building a pricing page](/build/checkout/build-pricing-page), you can pass an IP address or location information to return localized prices. ## Before you begin Country specific prices (price overrides) are set against prices in Paddle, so you'll need to [create products and prices](/build/products/create-products-prices) first. You can add country specific prices when creating a price initially, or update prices to add them later. ## Turn on automatic currency conversion 1. Go to **Paddle** > **Business account** > **Currencies**. 2. Use the checkboxes to select currencies that you want Paddle to automatically convert, or check **Select all**. 3. Click Save when you're done. ![Illustration showing the currencies screen in Paddle. There's a callout showing the list of currencies available to be automatically converted, and a callout showing the save button.](/src/assets/images/tmp-build/dashboard-currencies-automatic-conversion-20230831.svg) ## Add price overrides to a price You can add price overrides to a price when [creating](/build/products/create-products-prices) or updating it. We recommend creating price overrides using the Paddle dashboard. 1. Go to **Paddle** > **Catalog** > **Products**, then click the product you want to create a price override for in the list. 2. Find the price you want to create a price override for in the list click, then click and choose Edit from the menu 3. Under the country specific prices section, enter details for the countries you want to create price overrides for. 4. Click Save when you're done. ![Illustration showing the edit price page in Paddle showing the 'country specific prices' section.](/src/assets/images/tmp-build/dashboard-price-overrides-20230831.svg) Send a request to the `/prices/{price_id}` endpoint to update a price to include price overrides. In your request, include an array of `unit_price_overrides`. For each override, include `country_codes` (array of country codes) and `unit_price` (`amount` and `currency_code`). If creating a new price, include other required fields too, see [Create products and prices](/build/products/create-products-prices). The euro (`EUR`) is shared across 20 countries. Some countries are willing to pay more, while other countries have less purchasing power. This example adds price overrides for some countries that use the euro: - Luxembourg (`LU`) and Ireland (`IE`) have a price override set at 40 `EUR` - Germany (`DE`), France (`FR`), and the Netherlands (`NL`) have a price override set at 35 `EUR` - All other countries paying in `EUR` fall back to the base price of 30 `EUR` If successful, Paddle responds with a copy of the updated price entity. Customers in some countries may prefer to pay in `USD`, rather than their local currency. This example adds a price override for Brazil, where some customers prefer to use US Dollar online. - Brazil (`BR`) has a price override set at 52 `USD` - All other countries paying in `USD` fall back to the base price of 100 `USD` If successful, Paddle responds with a copy of the updated price entity. This example adds price overrides for countries based on their purchasing power. Pricing for purchasing power lets you increase the amount customers pay in countries where willingness-to-pay is higher, and offer accessible pricing in emerging markets. - United Kingdom (`GB`) has a price override set at 90 `GBP` [115 `USD`] - India (`IN`) has a price override set at 2320 `INR` [28 `USD`] - All other countries paying in `USD` fall back to the base price of 100 `USD` If successful, Paddle responds with a copy of the updated price entity. ## Update price overrides You can update price overrides when editing a price in the Paddle dashboard, [as in the previous section](#add-price-overrides-to-a-price). Send a request to the `/prices/{price_id}` endpoint to update price overrides against a price. When updating price overrides, send the complete list you want to keep — including existing overrides. If you omit a price override, it's removed from the price. Get existing overrides first: Extract price overrides you want to keep, then build your request with the complete `unit_price_overrides` array. For each override, include `country_codes` and `unit_price` (`amount` and `currency_code`). To learn more, see [Work with lists](/api-reference/about/lists). ## Remove price overrides Send a request to the `/prices/{price_id}` endpoint to update a price to remove price overrides. Set `unit_price_overrides` to an empty array in your request to remove all price overrides. --- # Set your default payment link URL: https://developer.paddle.com/build/transactions/default-payment-link Your default payment link is a quick way to open Paddle Checkout for a transaction, and used when updating a payment method. Set it in the Paddle dashboard. Transactions have a checkout payment link that you can use to open a checkout to collect for payment. Set a default payment link to tell Paddle which page in your app or website that checkout payment links should point to by default. Your default payment link is also used to open a checkout to let customers update their payment method. It's included automatically in emails sent by Paddle to customers. You must set your default payment link to start selling with Paddle. You can't create transactions without it — including [manually-collected transactions (invoices)](/build/invoices/create-issue-invoices). ## How it works Every Paddle account has a default payment link. It's used for: - Unique payment links against [transactions](/api-reference/transactions) that automatically open a [Paddle Checkout](/concepts/sell/self-serve-checkout) to collect for payment. - As a redirect for the payment method update URL against automatically-collected subscriptions to let customers update payment details. - In emails sent by Paddle to customers to let them update their payment details for automatically-collected subscriptions. Your default payment link should be a page for an approved website that [includes Paddle.js](/paddle-js/about/include-paddlejs). You don't need to do anything to get Paddle.js to open a checkout, it automatically opens a checkout for the transaction when the query parameter is present. ### Transaction payment links All automatically-collected transactions include a checkout payment link. Manually-collected transactions don't include a checkout payment link by default, but you can set `billing_details.enable_checkout` to `true` when [creating or updating](/build/invoices/create-issue-invoices) to include one. When enabled, this link is automatically included on invoice documents sent by Paddle. Checkout payment links are returned in transaction responses as `checkout.url`. They're made up of your default payment link with a `_ptxn` query parameter appended. The value of the query parameter is the transaction ID. For example: ``` https://aeroedit.com/pay?_ptxn=txn_01h2b0qpjc0xt8k5aw6nsdec4p ``` In this example: | Description | URL component | |--------------------------------------------|----------------------------------| | **Default payment link** | `https://aeroedit.com/pay` | | **Query parameter key** | `?_ptxn=` | | **Query parameter value** (transaction ID) | `txn_01h2b0qpjc0xt8k5aw6nsdec4p` | You can pass any of your other approved websites as `checkout.url` when [creating or updating a transaction](/build/transactions/create-transaction) to override the default checkout link, if you want to point to another page. ### Payment method update links For compliance, subscription emails from Paddle include a link to let customers update their payment method and cancel their subscription. Links are also returned when working with [subscription entities](/api-reference/subscriptions) against `subscription.management_urls`. The link to [update a payment method](/build/subscriptions/update-payment-details) redirects to your default payment URL to open a checkout to update a payment method. ## Before you begin You'll need to add your website to **Paddle > Checkout > Website approval**, then wait for approval. ## Build your default payment link page Your default payment link should be a page for an approved website that [includes Paddle.js](/paddle-js/about/include-paddlejs). It might be your checkout page, or you might create a separate page specifically for it. If your page calls [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) on load with a list of `items` or a `transactionId`, this takes priority over the query parameter. By default, Paddle.js opens an overlay checkout for the passed transaction. You can [set default checkout settings](/build/checkout/set-up-checkout-default-settings) by passing them to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize). Paddle.js uses default settings when opening a checkout payment link. This example sets default checkout settings for all checkouts opened on a page. When using a checkout payment link, Paddle.js opens an inline checkout with these settings. ```javascript Paddle.Initialize({ token: 'live_7d279f61a3499fed520f7cd8c08', // replace with a client-side token checkout: { settings: { displayMode: "inline", theme: "light", locale: "en", frameTarget: "checkout-container", frameInitialHeight: "450", frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;" } } }); ``` This example sets default checkout settings for all checkouts opened on a page. When using a checkout payment link, Paddle.js opens an overlay checkout with these settings. Paddle.js opens an overlay checkout by default, but this is a good way to [pass additional settings](/build/checkout/set-up-checkout-default-settings) like `locale` and `theme`. ```javascript Paddle.Initialize({ token: 'live_7d279f61a3499fed520f7cd8c08', // replace with a client-side token checkout: { settings: { displayMode: "overlay", theme: "light", locale: "en" } } }); ``` ## Set your default payment link You can set your default payment link in the Paddle dashboard. You must set a default payment link — even if you only sell by invoice or only sell one-time products. Your [sandbox](/sdks/sandbox) and live systems are separate. You should set a default payment link for both systems. They don't have to be the same. 1. Go to **Paddle > Checkout > Checkout settings**. 2. Enter your website homepage under the **Default payment link** heading. If you don't have one, enter `https://localhost/`. 3. Click Save when you're done. ![Checkout settings screen showing the Paddle dashboard. The default payment URL is set to https://example.com/](/src/assets/images/tmp-build/default-payment-link-20250127.svg) --- # Setup checklist URL: https://developer.paddle.com/build/set-up-checklist Everything you need to do to set up Paddle and build a successful integration. Welcome to Paddle! This guide walks through all the steps that you need to get started with Paddle, build an integration, and prepare for go-live. The steps in this guide apply to your **sandbox account**, unless otherwise stated. We walk through setting up your live account in our [go-live checklist](/build/go-live-checklist). ## Overview 1. [**Sign up for Paddle**](#sign-up-for-paddle) Create your Paddle accounts and invite your team. 2. [**Verify your live account**](#verify-your-live-account) Provide some information to verify your domain, your business, and your identity. 3. [**Complete initial configuration**](#complete-initial-configuration) Configure key settings like your balance currency and whether prices include tax, and create API keys. 4. [**Add products to your product catalog**](#add-products-to-your-product-catalog) Add subscription plans, recurring addons, one-off charges, and other items to Paddle. 5. [**Integrate with your frontend**](#integrate-with-your-frontend) Integrate Paddle with your frontend using Paddle.js and Paddle Checkout. 6. [**Create notification destinations**](#create-notification-destinations) Use webhooks to get notified when events happen in your Paddle system. 7. [**Handle subscription lifecycle events**](#handle-subscription-lifecycle-events) Build workflows to handle subscription lifecycle events, like signing up, pausing, or canceling. 8. [**Test and go live**](#test-and-go-live) Test your integration fully, then start the transition to your live account. ## Sign up for Paddle When you're ready to get started with Paddle, sign up for Paddle accounts. You can sign up for two kinds of account: - [Sandbox](/sdks/sandbox) — for testing and evaluation - Live — for selling to customers We recommend signing up for both kinds of account. You generally build your integration with your sandbox account, then transition your integration to a live account when you're ready to start selling. Your [sandbox account](/sdks/sandbox) is for evaluation and testing. All transactions are tests, meaning they're simulated and any money isn't real. [Sign up for a sandbox account](https://sandbox-vendors.paddle.com/signup), then confirm your email address. Your live account is where customers can make purchases. Transactions are real, meaning payment methods are charged and you earn real money. [Sign up for a live account](https://vendors.paddle.com/signup), then confirm your email address. Add your team members to Paddle to start collaborating on setup, integration, and the daily operation of your subscription management solution. Go to **Paddle > Business account > Team members**, then add accounts for your team members. Do this for both your sandbox and live accounts. ## Verify your live account Account verification is where we ask for some information about who you are and what you're selling through Paddle to make sure that we can work together. It helps keep the Paddle platform safe for everyone. It only takes a few moments to complete, and our verification team are available to help throughout. You only need to do this for your **live account**. It can sometimes take a few days to complete verification, so we recommend starting the process in live before you start integrating with sandbox. Domain verification makes sure you own the domains where you use Paddle Checkout, and that the products sold meet the [Paddle Acceptable Use Policy](https://paddle.com/support/aup/). Add your domains to **Paddle > Checkout > Request domain approval**. Wait for approval. To learn more, see [Domain review on the Paddle help center](https://www.paddle.com/help/start/account-verification/what-is-domain-verification) Business verification checks the business information that you provided when you signed up for Paddle. This happens automatically in most cases, but sometimes we might need more information from you. Look out for emails from our verification team and reply if you receive them. To learn more, see [Business identification on the Paddle help center](https://www.paddle.com/help/start/account-verification/what-is-business-verification) Identity verification is a check on anyone significant at a business, like anyone who owns more than 25% of a company. If you signed up as an individual, we'll ask you to verify your own identity. Look out for an email from our verification team, then complete identify verification. To learn more, see [Identity verification on the Paddle help center](https://www.paddle.com/help/start/account-verification/what-is-identity-verification) ## Complete initial configuration Before you start building an integration, configure key settings for your sandbox account. You can always change these settings later. Create API keys to interact with the Paddle API. We recommend creating keys and familiarizing yourself with authentication before getting started. Go to **Paddle > Developer tools > Authentication** to create API keys. Send a request to the `/event-types` endpoint to test your authentication. To learn more, see [API keys](/api-reference/about/authentication) and [Authentication](/api-reference/about/authentication). Your default payment link is a quick way to open [Paddle Checkout](/concepts/sell/self-serve-checkout) for a transaction. It should be a page that [includes Paddle.js](/paddle-js/about/include-paddlejs), typically your checkout page. Go to **Paddle > Checkout > Checkout settings**, expand **Default payment link**, then add a default payment link. You can set this to `localhost` or another test domain while working with sandbox. Change this later once you've built a checkout. To learn more, see [Set your default payment link](/build/transactions/default-payment-link) Choose the payment methods you want to use with Paddle Checkout. You can turn on payment methods in a couple of clicks — no need to sign up for merchant or partner accounts. Go to **Paddle > Checkout > Checkout settings**, expand **Payment methods**, then check the payment methods you want to offer. Card is always on for checkouts, and bank transfer is always on for invoices. To learn more, see [Payment methods](/concepts/payment-methods) Choose whether prices should be inclusive or exclusive of taxes by default. Typically, prices are exclusive of tax when selling to businesses and in regions with sales taxes. Go to **Paddle > Checkout > Sales tax settings** to set your sales tax setting. Choose the currency that you want to hold earnings in. Paddle automatically converts payments in other currencies to your balance currency, ready for payout. Go to **Paddle > Business account > Currencies** to set your balance currency. We recommend choosing a currency that matches your bank account. ## Add products to your product catalog Your product catalog includes subscription plans, recurring addons, and one-off items. Products describe the items that customers can purchase. They have related prices that describe how they're charged. Add products and prices to Paddle for your subscription plans, recurring addons, and one-off items. Go to **Paddle > Catalog > Products** to start adding products and prices. To learn more, see [Create products and prices](/build/products/create-products-prices) Discounts let you reduce the amount a customer has to pay by a percentage or fixed amount. They can be one-time or recurring, and apply to an entire transaction or just items that you choose. Go to **Paddle > Catalog > Discounts** to start adding discounts. To learn more, see [Create products and prices](/build/products/offer-discounts-promotions-coupons) Taxable categories determine what kinds of items you offer. They make sure that the correct amount of tax is calculated. Standard Digital Goods is available by default, but you should request approval for other taxable categories if you require them. Go to **Paddle > Catalog > Taxable categories** to request approval for other taxable categories. To learn more, see [Taxable categories on the Paddle help center](https://www.paddle.com/help/start/intro-to-paddle/why-do-i-need-to-select-'taxable-categories'-for-my-products) ## Integrate with your frontend Integrate Paddle with your frontend using Paddle.js and Paddle Checkout. Paddle Checkout handles securely capturing card details or payment using another payment method when customers sign up, change their payment details, or want to pay invoices by card or other payment method. Use Paddle.js to integrate [Paddle Checkout](/concepts/sell/self-serve-checkout) with your website or app. You can build an overlay checkout with a few lines of code, or create an integrated experience using inline checkout. Use Paddle.js to build a checkout. To learn more, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) If you like, you can redirect to a success page or build custom logic that runs when checkout completes. Build a success page and add it to your checkout page, or use Paddle.js events to build custom logic. To learn more, see [Handle checkout success](/build/checkout/handle-success-post-checkout) Your default payment link is a quick way to open Paddle Checkout for a transaction. You can set this to the checkout page that you built, or create another page that includes Paddle.js. If you want your default payment link page to be different from your checkout page, build a new page that [includes Paddle.js](/paddle-js/about/include-paddlejs). Go to **Paddle > Checkout > Checkout settings**, expand **Default payment link**, then update your default payment link. To learn more, see [Set your default payment link](/build/transactions/default-payment-link) Pricing pages show prospects the products that you offer and how much they cost. Paddle lets you build dynamic preview pages using Paddle.js and the Paddle API. Use Paddle.js and the Paddle API to build a pricing page. To learn more, see [Build a pricing page](/build/checkout/build-pricing-page) ## Create notification destinations Notifications let you get notified when key events happen in Paddle. They can be delivered as webhooks or emails. You'll typically use webhooks to keep your app in sync with Paddle. When something happens in Paddle, like a new customer signs up or a subscription is cancelled, Paddle can send you a webhook so you can build logic to handle this in your app. Create notification destinations to tell Paddle where to deliver webhook notifications and which events you want to receive notifications for. Go to **Paddle Developer tools > Notifications** to start creating notification destinations. To learn more, see [Work with notification destinations](/webhooks/about/notification-destinations) Paddle only sends webhooks from certain IP addresses. For security, we recommend that you allowlist Paddle IP addresses and reject responses from others. Allowlist Paddle IP addresses on your webhook server. To learn more, see [Webhooks](/webhooks) ## Handle subscription lifecycle events Build workflows to handle subscription lifecycle events, like when customers sign up, upgrade or downgrade, or pause or cancel their subscription. You should provision your app for each event to make sure a customer has the right level of access. Paddle automatically creates a new subscription for recurring items where a checkout completes or an invoice is issued. Provision your app when a new subscription is created in Paddle. To learn more, see [Handle provisioning and fulfillment](/build/subscriptions/provision-access-webhooks) You should provide a way for customers to cancel their subscription when they no longer want to your user your software. Paddle stops billing them indefinitely. Build a workflow to let customers cancel their subscription using the Paddle API. Provision your app so that customers don't have access once canceled. To learn more, see [Cancel a subscription](/build/subscriptions/cancel-subscriptions) You can offer a pause subscription function to let customers take a break and come back later. Paddle stops billing until their subscription resumes. Build a workflow to let customers pause and resume their subscription using the Paddle API. Provision your app so that customers have limited access while paused. To learn more, see [Pause a subscription](/build/subscriptions/pause-subscriptions) Customers need a way to change the payment method that they use to pay for subscription renewals and charges. This is important where subscriptions are past due. Build a workflow to let customers update their payment details using Paddle.js and the Paddle API. To learn more, see [Update payment details](/build/subscriptions/update-payment-details) If you offer multiple plans, you should provide a way for customers to upgrade or downgrade their subscription. You might also sell addons, like additional users or modules, that you should let customers add or remove. Build a workflow to let customers upgrade or downgrade, and add or remove any recurring items. To learn more, see [Upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade) or [add or remove items](/build/subscriptions/add-remove-products-prices-addons) ## Test and go live Test your integration fully, then start the transition to your live account. Before you go live, test subscription scenarios to check that your integration works. Run through all the subscription lifecycle events yourself to test that they work. You're ready to move from your sandbox account to your live account. Follow our go-live checklist to walk through setting up your live account and switching your integration to work with live. To learn more, see [Go-live checklist](/build/go-live-checklist) --- # Work with credit balances URL: https://developer.paddle.com/build/customers/get-customer-credit-balances See how much credit a customer has to use. Credit balances are automatically used to pay for future transactions or reduce the amount due on issued invoices. When Paddle creates credits for prorated changes to a subscription, credit may be added to a credit balance for a customer. You can check credit balances for a customer to see how much credit they have and how much they've previously used. Credit balances are automatically used to pay for future transactions or reduce the amount due on issued invoices. ## How it works Credit balances are stored against [customer entities](/api-reference/customers). They hold information about how much credit a customer has available. Where customers are billed in [multiple currencies](/concepts/sell/supported-currencies) and receive a credit, Paddle creates a credit balance for each currency. Credit balances can only be applied to transactions in the same currency. Credits in Paddle are always related to existing transactions. [They adjust an amount](/build/transactions/create-transaction-adjustments) that's been paid, or an amount that's due on an issued invoice. They're not promotional credits, which are credits given to customers for things like referral schemes or promotions. ### Credits are for prorated subscription changes When customers make prorated changes to a subscription, it might result in a credit. This is common in downgrade scenarios, where customers [swap to a less expensive plan](/build/subscriptions/replace-products-prices-upgrade-downgrade) or [remove items](/build/subscriptions/add-remove-products-prices-addons) midway through their billing cycle. When a prorated credit fully pays for a subscription change and there's an amount remaining, Paddle creates a credit balance for a customer to hold what's left. You can check `details.totals.credit_to_balance` against [the related transaction](/api-reference/transactions) for a subscription change to see how much was added to a credit balance. This is included on invoices sent to customers from Paddle, too. ### Credits are automatically applied Paddle automatically uses credit balances to pay for future transactions. Each credit balance has three totals: - **Balance:** total available to use. - **Reserved:** total temporarily reserved for `billed` transactions. - **Used:** total amount of credit used. In most cases, credit moves from the `balance` total to the `used` total when applied to a transaction. This is the case when a credit balance fully pays a transaction, or when the remaining balance of a transaction is successfully collected immediately. However, when a transaction is `billed`, like when [working with an issued invoice](/build/invoices/create-issue-invoices), the credit applied to that transaction moves from the `balance` total to the `reserved` total. It's not available for other transactions at this point, but it's not yet considered used. When a `billed` transaction is fully paid and marked as `completed`, then the credit moves from `reserved` to `used`. If a `billed` [transaction is `canceled`](/build/invoices/cancel-invoices), `reserved` credit returns to `balance`. ## Before you begin Credit balances are for customers, so you'll need to [get a customer](/api-reference/customers/get-customer) to work with credit balances for them. Paddle creates a credit balance for a customer when they first have a credit. An empty `data` array is returned for customers who don't have any credit balances. ## Check credit balances for a customer You can work with credit balances for a customer using the API. Send a request to the `/customers/{customer_id}/credit-balances` endpoint to list credit balances for a customer. If successful, Paddle responds with a list of credit balances for a customer. An empty `data` array is returned where a customer has no credit balances. In this example, a customer has a credit balance for `USD`. In this example, a customer has no credit balances. ## Apply a credit balance to a transaction Paddle automatically uses credit balances for a customer to pay or part-pay for transactions. You can't work with credits directly. Paddle creates a credit balance for each currency. Credit balances for a currency are only applied to transactions for that currency. You can check `details.totals` against [a transaction](/api-reference/transactions) get a breakdown of any credit applied to a transaction. In particular, `credit` details the total credit applied to a transaction and `grand_total` lets you know the amount due after credits but before any payments. ## Create or add to a credit balance Paddle automatically creates credits when customers make prorated changes to a subscription, and the changes result in a prorated credit that isn't fully used up on the related transaction for the subscription change. You can't add to a credit balance yourself. You can [create a credit adjustment for a transaction](/build/transactions/create-transaction-adjustments) to reduce the amount due to pay on an issued invoice. Paddle doesn't add the credited amount to a credit balance because it's immediately applied to the invoice. --- # Bill for non-catalog items URL: https://developer.paddle.com/build/transactions/bill-create-custom-items-prices-products Charge for an item without adding it to your product catalog by passing price or product attributes when working with a transaction or a subscription. As well as creating transactions for items in [your product catalog](/build/products/create-products-prices), you can create transactions for non-catalog items. This is useful for one-off or bespoke items that are specific to that transaction. For example, you may agree a custom price with an enterprise customer. You may also like to bill for non-catalog items if you work with products where the price changes often, or where you need to manage your product catalog outside of Paddle. For example, games companies typically manage their product catalog centrally because they need to work with app stores. ## How it works [Transactions](/api-reference/transactions) calculate and capture revenue in Paddle. To bill for an item, you add it to a transaction. You can do this in two ways: [Create products and prices in Paddle](/build/products/create-products-prices), then [pass prices IDs to transactions](/build/transactions/create-transaction) or [Paddle.js](/build/checkout/pass-update-checkout-items) to bill for them. - Manage items using the product catalog in Paddle. - Items can be reused across transactions easily. - Useful for companies who sell a set of digital products at the same price points. - SaaS companies who sell subscription plans and addons. Prices may [vary by country](/build/products/offer-localized-pricing), but items remain the same. - Companies who sell a selection of digital products or software licenses where the items remain the same. Pass price and product attributes directly to a [transaction when creating or updating](/build/transactions/create-transaction) to bill for them. - Manage items using your own product database. - Items are specific to a transaction. - Useful for companies with lots of items, or where item prices may change a lot. - Games companies who maintain a large catalog of items and may show different prices to different user segments. - eBook retailers, where publishers set prices and they may change daily. ### How do non-catalog items relate to catalog items? A [complete product](/build/products/create-products-prices) in Paddle is made up of a [product entity](/api-reference/products) that describes the item, and a [related price entity](/api-reference/prices) that describes how much and how often a product is billed. You can add non-catalog items to a transaction where: - **Only the price is custom.** This is great where the products you offer stay the same, but you might offer bespoke pricing from time to time. Your non-catalog price relates an existing catalog product entity in Paddle, sharing the same product name, image, and tax category. - **Both the price and the product are custom.** Where you manage your product catalog outside of Paddle, you can create entirely custom products. Your item uses a non-catalog price and a non-catalog product. When you [create or update a transaction](/build/transactions/create-transaction) with non-catalog items, Paddle creates a price entity and (optionally) a related product entity. They have a [Paddle ID](/api-reference/about/paddle-ids) as normal, meaning you can use the [get a product](/api-reference/products/get-product) or [get a price](/api-reference/prices/get-price) operations to work with them, but they're not added to your product catalog. This means they're not returned by default when [listing products](/api-reference/products/list-products) or [prices](/api-reference/prices/list-prices), and they're not shown in the Paddle dashboard. Non-catalog price and product entities have a `type` of `custom`, so you can differentiate between entities in your catalog. ### Subscriptions This guide walks through adding non-catalog items to transactions, but you can also: - [Bill one-time non-catalog items to a subscription](/build/subscriptions/bill-add-one-time-charge) - [Update a subscription to add recurring non-catalog items](/build/subscriptions/add-remove-products-prices-addons) You can configure non-catalog items for a subscriptions in the same way as transactions. ## Before you begin To create a transaction, you'll need to first [set your default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link** and get it approved. ## Bill for a non-catalog price for an existing product You can add a non-catalog price for an existing product in your catalog to a transaction. In this case, the product a customer is purchasing is the same, but you have a specific price for it. Bill for a non-catalog price using the API in two steps: 1. **Preview transaction** Preview tax and localized pricing before creating the transaction. 2. **Create transaction** Send a request to create the transaction with your non-catalog item. In your request, build an `items` array with `price` objects and `quantity` for each item. Relate your custom price to an existing catalog product using `product_id`. You can also include existing catalog items using `price_id` and `quantity`. Include `customer_id` and `address_id` (and optionally `business_id`) to create a `ready` transaction. ### Preview transaction Send a request to the `/transactions/preview` endpoint to preview the transaction. ### Create transaction Send a request to the `/transactions` endpoint to create the transaction. The created transaction is `draft`. [Pass it to a checkout](/build/transactions/pass-transaction-checkout) to capture customer and address information. ## Bill for a non-catalog price and a non-catalog-product You can add a non-catalog price for a non-catalog product in your catalog to a transaction. This is useful if you manage your product catalog outside of Paddle, or you want to sell something entirely bespoke. Bill for a non-catalog price and product using the API in two steps: 1. **Preview transaction** Preview tax and localized pricing before creating the transaction. 2. **Create transaction** Send a request to create the transaction with your non-catalog price and product. In your request, build an `items` array with `price` objects (each including a nested `product` object) and `quantity` for each item. You can also include existing catalog items using `price_id` and `quantity`. Include `customer_id` and `address_id` (and optionally `business_id`) to create a `ready` transaction. ### Preview transaction Send a request to the `/transactions/preview` endpoint to preview the transaction. ### Create transaction Send a request to the `/transactions` endpoint to create the transaction. The created transaction is `draft`. [Pass it to a checkout](/build/transactions/pass-transaction-checkout) to capture customer and address information. Paddle automatically creates a subscription for recurring items — use webhooks to [provision your app](/build/subscriptions/provision-access-webhooks). ## Update a non-catalog price or product Non-catalog products and prices are created for specific transactions. They're not considered part of your product catalog. You shouldn't ordinarily need to update them. Non-catalog products and prices have Paddle IDs, so you can update them using the [update a product](/api-reference/products/update-product) or [update a price](/api-reference/prices/update-price) operations if needed. For example, you might correct a spelling error in a `name` or `description` — especially where an item is recurring. To learn more, see [Create products and prices](/build/products/create-products-prices) ## Add a non-catalog item to your catalog If you find yourself adding similar non-catalog prices or products to transactions, you might like to add a custom item you've previously worked with to your product catalog. We recommend that you [create a new product or price](/build/products/create-products-prices) in your catalog where you're adding an item to your standard offering. You can also get an existing custom price or product using its ID, then change the type to `standard`. Set `type` to `standard` to add the item to your product catalog. The `type` field exists against both product and price entities. --- # Build URL: https://developer.paddle.com/build Get instructions on how to model your subscription prices, connect and customize a checkout, create invoices, and work with subscriptions. --- # Build inline checkout URL: https://developer.paddle.com/build/checkout/build-branded-inline-checkout Get a step-by-step overview of how to build a complete inline checkout — including initializing Paddle.js, passing settings and items, updating on-page information, and next steps. The checkout is where customers make purchases. For SaaS businesses, it's the process where customers enter their details and payment information, and confirm that they'd like to sign up for a subscription with you. You can use [Paddle.js](/paddle-js) to integrate an inline checkout into your app. [Inline checkout](/concepts/sell/branded-integrated-inline-checkout) lets you embed a checkout and display information about items and totals in your own interface, creating checkout experiences that are fully integrated with your app. Explore the code for this tutorial and test right away using our inline checkout pen. ## What are we building? In this tutorial, we'll build a page that embeds a multi-page inline checkout for three items in our product catalog. We'll display information about items and totals in a table on the page, add a way for customers to switch to annual plan during checkout, then we'll extend it by passing customer information. ![Illustration showing an inline checkout implementation. The Paddle Checkout frame is on the left and is on the payment page. It shows buttons for Apple Pay and PayPal, followed by a card payment form. On the right is an items list and totals. The grand total is $3600 billed monthly and is at the top.](/src/assets/images/tmp-build/checkout-inline-checkout-demo-20240618.svg) We'll learn how to: - Include and set up Paddle.js using a client-side token - Pass settings to Paddle.js to embed an inline checkout in our page - Pass items to inline checkout using `Paddle.Checkout.open()` - Display and update information about items and totals using checkout events - Take a test payment - Update items for an opened checkout using `Paddle.Checkout.updateCheckout()` If you like, you can [view on CodePen](https://codepen.io/heymcgovern/pen/yLrogZd) and follow along. ## Before you begin ### Choose a checkout implementation This tutorial walks through creating an [inline checkout](/concepts/sell/branded-integrated-inline-checkout). You can also create [overlay checkouts](/concepts/sell/overlay-checkout), which let you launch a checkout in just a few lines of code. We recommend [building an overlay checkout](/build/checkout/build-overlay-checkout) if you're new to Paddle. Inline checkouts use the same JavaScript methods as overlay checkouts, so you can switch to an inline checkout later. ![Illustration showing an overlay checkout. The items summary is simplified, showing gray boxes to illustrate items and totals. The right-hand side of the overlay checkout shows the payment form, with buttons for Apple Pay, PayPal, and the card details entry form.](/src/assets/images/tmp-build/checkout-overlay-checkout-overview-20230824.svg) Integrate Paddle in just a few lines of code. Launches an overlay to capture payment. Quick to set up: add Paddle.js to your site and call `Paddle.Checkout.open()` or use `data-*` HTML attributes to launch the checkout. Checkout opens as a modal overlay on top of your page, focusing the user on payment. Customize colors, logo, and branding in the Paddle dashboard. Structure and experience is managed by Paddle. ![Illustration showing an inline checkout. The inline checkout frame shows the payment form, and is in crop-marks on the left to show where it's been embedded. The items list is on the right with a total at the top.](/src/assets/images/tmp-build/checkout-inline-checkout-overview-20230824.svg) Get complete control of the checkout experience. Captures payment directly in your app. Embed Paddle Checkout directly into your page layout. Use a container element and set `displayMode` to `inline`. Checkout form is part of your page flow—lets you create fully integrated or branded payment experiences. Full control over surrounding layout and styling; you can design the context and surrounding UX. To learn more about the differences between overlay and inline checkouts, see [Paddle Checkout](/concepts/sell/self-serve-checkout) ### Create products and prices Paddle Checkout works with products and prices to say what a customer is purchasing, so you'll need to [create a product and at least one related price](/build/checkout/build-branded-inline-checkout) to pass to your checkout. ### Set your default payment link You'll also need to: - [Set your default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link**. - Get your default payment link domain approved, if you're working with the live environment. ## Overview Add an inline checkout to your website or app in five steps: 1. [**Include and initialize Paddle.js**](#include-and-initialize-paddlejs) Add Paddle.js to your app or website, so you can securely capture payment information and build subscription billing experiences. 2. [**Embed and pass checkout settings and items**](#embed-and-pass-checkout-settings) Pass settings to determine how your checkout opens and how it works, then pass items to say what your checkout is for. 3. [**Show and update on-page information**](#show-and-update-on-page-information) Inline checkout handles capturing payment securely. Display an items list and totals on your page, and update using event callbacks. 4. [**Take a test payment**](#take-a-test-payment) Make sure that your checkout loads successfully, then take a test payment. 5. [**Update your checkout**](#update-items-on-the-checkout) Dynamically update items and other information for your opened checkout. ## Include and initialize Paddle.js [Paddle.js](/paddle-js) is a lightweight JavaScript library that lets you build rich, integrated subscription billing experiences using Paddle. We can use Paddle.js to securely work with products and prices in our Paddle system, as well as opening checkouts and capturing payment information. ### Include Paddle.js script Start with a blank webpage, or an existing page on your website. Then, [include Paddle.js](/paddle-js/about/include-paddlejs) by adding this script to the ``: ```html ``` ### Set environment We recommend [signing up for a sandbox account](https://sandbox-login.paddle.com/signup?utm_source=dx&utm_medium=dev-docs) to test and build your integration, then switching to a live account later when you're ready to go live. If you're testing with the [sandbox](/sdks/sandbox), call [`Paddle.Environment.set()`](/paddle-js/methods/paddle-environment-set) and set your environment to `sandbox`: ```html ``` ### Pass a client-side token Next, go to **Paddle > Developer tools > Authentication** and create a client-side token. [Client-side tokens](/paddle-js/about/client-side-tokens) let you interact with the Paddle platform in frontend code, like webpages or mobile apps. They have limited access to the data in your system, so they're safe to publish. In your page, call [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) and pass your client-side token as `token`. For best performance, do this just after calling `Paddle.Environment.set()`, like this: ```html ``` ## Embed and pass checkout settings Next, we'll set an element on our page as a container for Paddle Checkout and set up Paddle.js for inline checkout. Inline checkout works by embedding a frame that contains Paddle Checkout into your website or app. The Paddle Checkout frame handles securely capturing payment information, letting you display information about items and totals elsewhere on the page. ### Create checkout container Create an empty `
` for the Paddle Checkout frame and give it a unique `class`, for example `checkout-container`: ```html
``` ### Pass settings Now, we'll pass the class of this empty `
` to tell Paddle.js where to embed the checkout frame. We'll also [pass checkout settings](/build/checkout/set-up-checkout-default-settings) to tell Paddle.js to load an inline checkout and say how our inline checkout should work. Update your [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) method call so that it includes `checkout.settings`. These settings are applied to all checkouts opened on this page. In our sample, we pass these settings to `checkout.settings`: | Parameter | Description | | |----------------------|-----------------------------------------------------------------------------|----------------------------------------------------| | `displayMode` | Determines whether Paddle.js should open an inline or overlay checkout. | We set to `inline`. | | `frameTarget` | Sets the element where Paddle Checkout should be loaded. | We passed our `checkout-container` class name. | | `frameInitialHeight` | Sets the initial height of the dev element where Paddle Checkout is loaded. | We set this to `450`, which is our recommendation. | | `frameStyle` | CSS properties to apply to the checkout container. | We passed some simple CSS styles here. | ```html ``` We've covered the required settings for an inline checkout, but you can also pass `locale`, `theme`, and other settings that control how Paddle Checkout works. For more information, see [Pass checkout settings](/build/checkout/set-up-checkout-default-settings) ### Pass items Checkouts must be for one or more items. If we were to try to open a checkout so far, Paddle.js would throw an error. To pass items, we can use the [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) method. In our sample, we've created a function called `openCheckout()` to open a checkout. Here's how it works: 1. We create a variable called `monthItemsList` and pass an array of objects, where each object contains a `priceId` and `quantity`. In our case, there are two prices that recur monthly and a single one-time price. 2. We create a function called `openCheckout()` that takes a parameter called `items`. 3. In our `openCheckout()` function, we call [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open), passing the value of `items` as the items list for the checkout. ```html ``` ### Set openCheckout() to run on page load Right now, we've written a function to open a checkout, but we haven't set it to run. We can add `onLoad` to our `` tag to run our `openCheckout()` function immediately after the page has loaded, passing in our `monthItemsList` variable as a parameter: ```html ``` ### Test your work Save your page, then open it in your browser. Paddle Checkout should load in place of the checkout container `
` element we created earlier. You'll notice that the Paddle Checkout frame doesn't include any information about what the checkout is for. That's our next step. ![Screenshot showing an inline checkout. It's on the first page of checkout, with fields for email and country. It doesn't include an items list or totals.](/src/assets/images/tmp-build/inline-checkout-test-container-20240404.png) ## Show and update on-page information The inline checkout frame doesn't include a breakdown of items or totals. It's designed to handle capturing customer and payment information, giving you the flexibility to show items and totals in your frontend in a way that fits with our design. To do this, we can use an event callback function. [Paddle.js emits events throughout the checkout process](/paddle-js/events) when key things happen. An event callback function is some code that we run when a specific event occurs. For example, when checkout is first loaded, Paddle.js emits a [`checkout.loaded`](/paddle-js/events/checkout-loaded) event that contains information about the items and totals on a checkout. We can build an event callback function to update our items and totals table with data contained in the event. It's important that customers know who they're buying from, what they're buying, and how much they're paying. To build an inline checkout that's compliant and optimized for conversion, your implementation must include: 1. If recurring, how often it recurs and the total to pay on renewal. If a trial, how long the trial lasts. 2. A description of what's being purchased. 3. Transaction totals, including subtotal, total tax, and grand total. Be sure to include the currency too. 4. The full inline checkout frame, including the checkout footer that has information about Paddle, our terms of sale, and our privacy policy. 5. A link to your refund policy, if it differs from the Paddle.com standard refund policy. ### Add tables to hold items and totals First, we need to add some HTML for a couple of tables to hold items and totals information. You might use something more visual when building an app or website, but tables work for our tutorial. Add this to the `` of your page. In this sample, there are two tables for our items and our totals: - Our items table has a header row and a body row that's got some zero values in. We'll add a row for each item on our checkout later. - Our totals table has a header column for the totals we'd like to show to our customer. There's `id`s set on the `` elements that should contain totals. We'll use these IDs to replace the contents of these elements with totals later. ```html

Items

Product name Price name Quantity Total
0 00.00

Totals

One-time charges 0.00
Recurring charges 0.00
Discount 0.00
Taxes 0.00
Total today 0.00
``` Add this to the `` of your page. It applies some styling to the HTML so that the `
` elements are arranged in two columns. For this sample, we also include [`MVP.css`](https://andybrewer.github.io/mvp/), which applies light styling to HTML elements. You don't need to do this if you're updating an existing page in your app or website that already has its own styling. ```html ``` ### Update items table Next, we'll build an event callback function to take data about our items from events and display it in our items table. In our sample, we created a function called `updateTable()` that takes a parameter called `event`. Then, we pass the event payload to our function as `event`. Here's how it works: 1. First, we exclude events that don't return a `name` field and print the data payload of events to the console. This is useful for us to see which events are emitted and how they look while we're testing. 2. We create a variable called `items` and set this to `event.data.items` in our event payload. We'll use this variable to populate our items table. 3. We call another function as part of this event callback function: `updateItemsTable()`, where we pass`items` as a parameter. 4. We create the `updateItemsTable()` function that we called in our event callback, setting it up to accept a parameter called `items`. It finds and selects our items table body (`.items-table tbody`), clears out any rows, then iterates through each item in the `items` array we passed. 5. When iterating through each item, we call another function called `createTableRow()`. We define this underneath, and it accepts `productName`, `priceName`, `quantity`, and `total` — a parameter for each of the columns in our items table. 6. `createTableRow()` returns an HTML table row element with our product name, price name, quantity, and total. This newly created row is appended to our table body in our `updateItemsTable()` function. 7. We update our [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) method, passing `updateTable` as the `eventCallback`. This means this function is run every time an event is emitted by Paddle.js. ```html ``` ### Update totals table Let's update our event callback function so that it displays totals from our events in our totals table. In our sample, we updated our `updateTable()` function so that it calls another function, `updateSummaryTable()`, to update our totals table. Here's how it works: 1. We create some additional variables called `totals` and `recurringTotals`, setting these to values in our event payload. 2. We add a call to another function as part of this event callback function: `updateSummaryTable()`, where we pass `totals` and `recurringTotals` as parameters. 3. We create the `updateSummaryTable()` function that we called in our event callback, setting it up to accept parameters called `totals` and `recurringTotals`. 4. `updateSummaryTable()` gets cells in our totals table using the IDs that we gave them earlier, then replaces the contents with values from the `totals` and `recurringTotals` arrays that we passed in as parameters. We calculate the one-time total by subtracting the subtotal of recurring items from the subtotal. For simplicity, we use the built-in `.toFixed()` JavaScript method to format values to two decimal places in our sample. Paddle [supports over 30 currencies](/concepts/sell/supported-currencies), some of which use a different number of decimal places. Consider using a currency library like [currency.js](https://currency.js.org/) to format currencies correctly. ```html ``` ## Take a test payment We're now ready to test. Save your page, then open it in your browser. Paddle.js should open an inline checkout for the items that we passed. You should see items and totals in the tables we created. If you're using a sandbox account, you can take a test payment using [our test card details](/concepts/payment-methods/card): An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` Any name Any valid date in the future. `100` ![Short animation showing launching an inline checkout, entering contact information, and entering test payment details.](/src/assets/images/tmp-build/inline-checkout-test-payment-20240404.gif) If the checkout doesn't appear, or you get a message saying "Something went wrong," you can open your browser console to see any specific error messages from Paddle.js to help you troubleshoot. Use `⌘ Command` + `⌥ Option` + `J` (Mac) or `Ctrl` + `⇧ Shift` + `J` (Windows) to quickly open your browser console in Google Chrome. #### Common problems Check that: - You added a default payment link to your checkout under **Paddle > Checkout > Checkout settings > Default payment link**, and that this matches the domain where you're testing. You can use `localhost` if you're testing locally on sandbox. - You included Paddle.js correctly. If you're moving from Paddle Classic, the CDN URL has changed. - Your client-side token is correct and passed to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize). - You set the correct environment. - The Paddle IDs for price entities that you passed are correct. Sandbox and live systems are separate, so make sure you're passing price IDs for the environment that you're working in. - Your event callback function doesn't have any problems. ## Update items on the checkout What if we want to update our checkout now it's opened? For example, we might want to let customers adjust the quantity of items or upsell them addons. Paddle.js includes the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method to let us dynamically update items, customer information, and discount on a checkout. For this tutorial, we'll add a button that we can click to switch to annual plan. When customers click this button, we'll swap monthly items on the checkout for annual plan items. ### Define list of prices [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) has an `items` parameter. When we pass an items list, Paddle.js replaces the items on the checkout with the new items list we passed. First, we'll define a new variable called `yearItemsList`. Like `monthItemsList`, we'll pass an array of objects, where each object contains a `priceId` and `quantity`. In this case, there are two prices that recur yearly and a single one-time price. Keep in mind that the entire items list is replaced — any omitted items are removed from the checkout entirely. ```html ``` ### Update items Next, we'll build a function to replace items on our checkout In our sample, we created a function called `switchPlan()`. Here's how it works: 1. We create a variable called `isMonthly` and set it to `true`. This variable tracks whether the current plan is monthly or yearly, and it's set to `true` initially because we pass `monthItemsList` to our checkout when the page loads. 2. We create a function called `switchPlan()`, then set a variable called `updatedItems` to our `yearItemList` array if `isMonthly` is `true`, and `monthItemList` if `isMonthly` is `false`. 3. We call the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method, passing `updatedItems` as the `items` parameter. This is our yearly items list when the plan is monthly, and our monthly items list when the plan is yearly. 4. We toggle the value of `isMonthly`. If it were `true`, it's set to `false`; if it were `false`, it's set to `true`. This means that next time `switchPlan()` is called, it'll switch plans correctly. ```html ``` ### Add a button to swap plan Finally, add a button to our HTML to call our `switchPlan()` function. ```html Switch plan ``` ### Test your work Save your page, then open it in your browser. Paddle Checkout should load as before. When you click the switch plan button, Paddle.js swaps items on your checkout from monthly to annual, and vice versa. ![Short animation showing updating items on an inline checkout. When the switch plan button is clicked, items and totals update.](/src/assets/images/tmp-build/inline-checkout-switch-plan-20240404.gif) ## Next steps That's it. Now you've built a checkout, you might like to extend Paddle Checkout by presenting other fields to your checkout, automatically applying a discount, passing optional checkout settings, or building a success workflow. ### Add other fields to your checkout Events emitted by Paddle.js contain information about the items and totals on a checkout. We present fields in `data.items[]`, `data.product`, `data.totals`, and `data.recurring_totals` in our sample. You might like to include other data from the event on your page. ### Automatically apply a discount Extend your checkout by passing a discount. When our checkout is launched, Paddle automatically applies the discount (where it's valid). Learn how to pass customer or business info (like email, name, or address) to Paddle Checkout. Set up and manage discount codes or time-limited promotions for your products. ### Pass checkout settings We covered passing the required settings for inline checkout, but there are a bunch of other settings you can pass that give you more control over how opened checkouts work. For example, you can set the language that Paddle Checkout uses, hide the option to add a discount, or restrict payment methods shown to customers. See all the settings you can pass to configure your inline checkout. Full API documentation for opening and customizing checkouts. ### Build a success workflow When customers complete checkout, Paddle Checkout has a final screen that lets customers know that their purchase was successful. If you like, you can redirect customers to your own page or use JavaScript event callbacks to build a more advanced success workflow. Redirect customers or add logic to handle post-checkout workflows. See all of the events you can listen to for customizing your checkout experience. --- # Configure Cancellation Flows URL: https://developer.paddle.com/build/retain/configure-cancellation-flows-surveys Build a cancellation process that saves customers by presenting them with dynamic salvage attempts, as well as capturing cancellation insights for your team. You can use [Cancellation Flows](/concepts/retain/cancellation-flows-surveys), part of [Paddle Retain](/concepts/retain), to build custom curated off-boarding experiences that are designed to prevent customers from churning. Cancellation Flows presents customers with a simple survey that suggests dynamic salvage attempts, like pausing a subscription or switching plans, as well as gathering useful insights around why they want to cancel. As a last resort, you can offer customers [a discount](/build/products/offer-discounts-promotions-coupons) to incentivize them to stick around. ## How it works [Paddle Retain](/concepts/retain) combines world-class subscription expertise with algorithms that use billions of data points to automatically reduce churn. Paddle Billing is fully integrated with Retain, meaning it automatically handles dunning and retention for you. [Cancellation Flows](https://www.profitwell.com/cancellation-flows) are a part of Paddle Retain, helping you save customers from canceling and gathering cancellation insights. They ask customers why they're canceling, as well as what they found valuable about your app, then presents curated salvage attempts. If you use Paddle Billing, Cancellation Flows automatically takes action on the related subscription for you. Cancellation Flows are built-in to the [customer portal](/concepts/sell/customer-portal), or you can build your own workflow in your frontend using Paddle.js. ## Before you begin - **Set up Paddle Retain** If you haven't already, connect Paddle Retain to your billing platform and [set up Paddle Retain](/build/retain). - **Make sure you've installed Paddle.js** Paddle.js must be installed and verified as installed on a public page on your site. [Follow the instructions during setup](/build/retain), click **Edit** under **Paddle.js is not installed**, or click **Install** under **Paddle.js is not installed in web app**. ## Set up Cancellation Flows ### Go to Retain If you set up Retain [for Paddle Billing](/build/retain), you can access and configure Cancellation Flows in the Paddle Billing dashboard. 1. Go to **Paddle > Retain** in your dashboard. 2. Click Cancellation Flows. 3. If setting up for the first time, click Get started. If you’ve already configured Cancellation Flows, click Edit. ### Add branding Any changes you make aren't saved until you reach the end of the setup flow. This includes when you edit an existing configuration. Branding lets you customize the look and feel of Cancellation Flows, so they fit the style of your app. You can change the font family, and colors for text and components. 1. Click the field under **Brand color** and use the color picker to select the color for selected states and progress bars. 2. Choose the font family for the text from the dropdown under **Font**. 3. Click the field under **Text color** and use the color picker to set the color for text. 4. Click the field under **Selected text color** and use the color picker to set the color for text in selected states. 5. View a preview of the first screen of the flow with your style selections on the right-hand side. 6. Click Continue to continue to the next step. ### Add responses for cancellation reasons On the first screen of the flow, a customer is asked why they're canceling. You can add up to 5 responses to this question that are displayed as options the customer can select. 1. Enter the text for each response under the **Response** fields. 2. Click Add response to add a new response field. 3. To remove a response, click the icon next to it. 4. Check the preview of the first screen of the flow with your added responses on the right-hand side. 5. Click Continue to move to the next step. ### Add responses for cancellation insights On the second screen of the flow, a customer is asked what they think your app or company is doing well. You can add up to 5 responses to this question that are displayed as options the customer can select. These response options are also used in the next step to offer a salvage attempt to the customer. 1. Enter the text for each response in the **Response** fields. 2. Click Add response to add another response field. 3. To remove a response, click the icon next to it. 4. Preview the second screen of the flow with your added responses on the right-hand side. 5. Click Continue to move to the next step. ### Map responses to salvage attempts On the third screen of the flow, a customer is offered an alternative option to canceling based on their responses to the previous question. For each response, you can choose which salvage attempt to offer. There are five types: | Type | Description | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Contact support via email | Encourage customers to email your support team. | | Book a meeting with support | Encourage customers to schedule a meeting with your team using [Calendly](https://calendly.com/). | | Pause plan | Encourage customers to pause their subscription, so they can come back in the future. | | Plan switch | Encourage customers to switch to a different product or price, retaining them with an offer that's more suited or affordable. | | No attempt | Don't offer any salvage attempts. Customers are directed to the next step. | #### Contact support via email 1. Select the response you want to offer this salvage attempt for from the dropdown boxes titled **When a customer selects**. 2. Select **Contact support via email** from the dropdown under **Offer**. 3. Customize another response's salvage attempt, or click Continue to progress to the next step. #### Book a meeting with support 1. Select the response you want to offer this salvage attempt for from the dropdown boxes titled **When a customer selects**. 2. Select **Book a meeting with support** from the dropdown under **Offer**. 3. Enter the text for the copy that precedes "can we chat a bit more about this?" under **Header copy**. 4. Customize another response's salvage attempt, or click Continue to progress to the next step. #### Pause plan The number of months to pause the subscription for is automatically set to the number of months remaining on the subscription. 1. Select the response you want to offer this salvage attempt for from the dropdown boxes titled **When a customer selects**. 2. Select **Pause plan** from the dropdown under **Offer**. 3. Enter the text for the copy that precedes "how about pausing your subscription for [x] months?" under **Header copy**. 4. Customize another response's salvage attempt, or click Continue to progress to the next step. #### Plan switch 1. Select the response you want to offer this salvage attempt for from the dropdown boxes titled **When a customer selects**. 2. Select **Plan switch** from the dropdown under **Offer**. 3. Enter the text for the copy that precedes "how about switching to [x]?" under **Header copy**. 4. Customize another response's salvage attempt, or click Continue to progress to the next step. #### No attempt 1. Select the response you want to offer this salvage attempt for from the dropdown boxes titled **When a customer selects**. 2. Select **No attempt** from the dropdown under **Offer**. 3. Customize another response's salvage attempt, or click Continue to progress to the next step. ### Configure salvage attempts Once you've mapped responses to salvage attempts, you must configure the salvage attempts you selected. You can't customize salvage attempts on a per-response basis. Your configuration applies to all responses that offer the same salvage attempt. #### Contact support via email 1. Click on the **Contact support via email** dropdown box. 2. Enter the email address to direct customers to under **Email for customer cancellation updates**. 3. Customize another salvage attempt, or click Continue to progress to the next step. #### Book a meeting with support 1. Click on the **Book a meeting with support** dropdown box. 2. Copy your [Calendly scheduling link](https://help.calendly.com/hc/en-us/articles/223193448-How-to-share-your-scheduling-link) on the Calendly platform. 3. Enter your Calendly scheduling link under **Calendly link**. 4. Customize another salvage attempt, or click Continue to progress to the next step. #### Pause plan 1. Click on the **Pause plan** dropdown box. 2. Select the unit of time to pause the subscription for from the dropdown under **For**. 3. Enter the number of time units to pause the subscription for in the field under **For**. 4. Customize another salvage attempt, or click Continue to progress to the next step. #### Plan switch 1. Click on the **Plan switch** dropdown box. 2. Select the plan that customers would be switched from using the dropdown under **If customer is on**. 3. Select the plan that customers are offered to switch to using the dropdown under **Offer**. 4. Enter any additional details you want to display with the offer under **Information to display**. 5. Click Add product to set up another plan switch pairing. 6. Click the icon next to the **Salvage plan** dropdown box to remove an existing pairing. 7. Customize another salvage attempt, or click Continue to progress to the next step. ### Customize the final discount offer [Discounts](/api-reference/discounts) let you reduce the amount a customer pays on subsequent transactions for their subscription. You can optionally offer a one-time, percentage-based discount to encourage customers who reject the salvage attempts to stay. You can add up to 32 discounts to offer. Customers are offered the discount that matches the billing period of the subscription they are attempting to cancel. If there isn't a discount with a matching billing period, no discount is offered. If you choose to not offer a discount, or no discount can be offered, the customer is sent directly to the final page after they reject the salvage attempts. You can also set the percentage amount to discount by, and the number of billing periods the discount applies for. #### Offer a final discount 1. Toggle **Offer a discount** on to enable the final discount offer. 2. Select the billing period the discount and subscription must match for the discount to be offered from the dropdown under **Billing period**. 3. If you selected **Every number of days, months, or years**, select the time unit from the dropdown and enter the number of time units. 4. Enter the percentage amount to discount by in the field under **Percentage off**. 5. Enter the number of billing periods the discount applies for in the field under **Discount length**. 6. Click New discount to add a new discount to match against. 7. Click to remove an existing discount. 8. Click Continue to progress to the next step. #### Turn off the final discount offer 1. Toggle **Offer a discount** off to disable the final discount offer. 2. Click Continue to finish the flow setup and save your changes. ### Customize the final feedback page If the customer rejects the discount, or if no discount was offered, the customer is sent to the final feedback page to give any final feedback before confirming their cancellation. You can customize the copy on this page to encourage customers to provide feedback and fit your brand. 1. Enter the text for the page's header copy under **Header copy**. 2. Enter the text for the copy following the header under **Body copy**. 3. Click Continue to finish the flow setup and save your changes. 1. Grab a copy of the [Paddle Retain Cancellation Flows configuration](https://assets.paddle.com/assets/retain/paddle-retain-cancel-flow-setup-form.xlsx) template and fill it in. If you use Stripe, grab a copy of the [Paddle Retain Cancellation Flows configuration for Stripe](https://docs.google.com/spreadsheets/d/1K09DDHEpxCk5PAnEsVfLN6t7o99ZU5rQEknyCqMddIA/edit#gid=413128940) template and fill it in instead. 2. Send it to us at [sellers@paddle.com](mailto:sellers@paddle.com?subject=RETAIN%20Cancellation%20Flows). ## Preview Cancellation Flows Once Cancellation Flows are set up, you can preview the flow customers see when they try to cancel a subscription. The flow is interactive, so you can view each page and check how it behaves. 1. Go to **Paddle > Retain > Cancellation Flows**. 2. Click the button in the **Cancellation flow** card. 3. Click to preview the flow. ## Integrate Cancellation Flows into your frontend If you handle cancellations using the [customer portal](/concepts/sell/customer-portal), you don't need to do anything. The customer portal automatically launches Cancellation Flows when customers try to cancel a subscription. If you've built your own cancellation logic outside the customer portal, you need to: 1. Call the [`Paddle.Retain.initCancellationFlow()`](/paddle-js/methods/paddle-retain-initcancellationflow) method when a customer tries to cancel a subscription on your website or app, passing the Paddle ID of [the subscription entity](/api-reference/subscriptions) that the customer wants to cancel. 2. Attach a callback to build workflows around [the result](/paddle-js/methods/paddle-retain-initcancellationflow). 3. Retain automatically handles pausing, applying a discount, switching plans, or canceling the related subscription in Paddle Billing. When those [events](/webhooks) occur in Paddle, [handle provisioning in your app](/build/subscriptions/provision-access-webhooks) as normal. ### Example ```html ``` 1. Update the cancellation logic in your web app to call the `profitwell` method, passing `init_cancellation_flow` and an object containing the ID of the subscription in your billing platform that you want to cancel. 2. Use the `.then()` method to attach a callback to the result, then build logic to [handle the result](/paddle-js/methods/paddle-retain-initcancellationflow). You should cancel a subscription when the customer chooses to cancel or when the cancellation flow encounters an error. 3. Retain automatically handles pausing, applying a discount, or switching plans in your billing platform. Handle provisioning in your app as normal. Retain doesn't cancel subscriptions in your billing platform, unless you're using Paddle Billing. You must [build logic to handle this](/build/subscriptions/cancel-subscriptions). You should run this logic when a cancellation flow encounters an error, too. ### Example ```html ``` ## Simulate Cancellation Flows Once you've set up Cancellation Flows, you can simulate one to check how it looks to customers. 1. Go to a page where you've [installed Paddle.js for Retain](/paddle-js/about/include-paddlejs). 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Enter Paddle.Retain.demo({feature: 'cancellationFlow'}). 1. Go to a page where you've [installed the ProfitWell.js snippets](/build/retain). 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Enter profitwell('cq_demo', 'cancellation_flow'). ## Subscribe to notification emails You can subscribe to receive notification emails based on the outcome of a cancellation flow for monitoring and reporting purposes. You choose which emails you'd like to receive by toggling the available options. - **Abort Cancellation** Notify when a customer starts to cancel but is successfully retained and doesn't go through with it. - **Cancellation** Notify when a customer completes a cancellation despite going through the flow. - **Pause Plan** Notify when a customer accepts a pause offer instead of cancelling. - **Switch Plan** Notify when a customer accepts a plan switch as an alternative to cancelling. - **Salvage Offer** Notify when a customer accepts a discount or other salvage offer. 1. Go to **Paddle > Retain > Cancellation Flows**. 2. In the **Notification email settings** section, click Edit. 3. Enter the email address where you want to receive notifications under **Notification email**. 4. Toggle the notification options that you'd like to receive emails for. 5. Click Save to save your changes. ## Subscribe to receive reports You can subscribe to receive a weekly CSV report of Cancellation Flows activity. The report is an activity log with one row for every time a cancellation flow is initialized. Use it to analyze cancellation patterns, measure your save rate, and identify which salvage attempts are most effective. Certain fields in the report map to those in the return object of [`Paddle.Retain.initCancellationFlow()`](/paddle-js/methods/paddle-retain-initcancellationflow). 1. Go to **Paddle > Retain > Cancellation Flows**. 2. Click Edit in the **Weekly report email** section. 3. Enter the email address you want to receive the report at. 4. Add more email addresses by clicking Add email. 5. Click Save. --- # Create and manage discounts URL: https://developer.paddle.com/build/products/offer-discounts-promotions-coupons Attract new customers and entice existing ones to upgrade using discounts. They're sometimes called promotions or coupons. Discounts let you reduce the amount that a customer has to pay. You may run discounts as part of promotions, or as a way to incentivize customers to upgrade or buy more. ## How it works Discounts are applied to the total value of the checkout or transaction. The type of discount determines how the total is impacted. ![Illustration of an example transaction with a discount applied to the total as a percentage.](/src/assets/images/tmp-build/discount-percentage-20250620.svg) A percentage taken from the full value of the checkout or transaction. If the total is $100 and the discount is 10%, the customer pays $90. ![Illustration of an example transaction with a discount applied to the total as a flat amount.](/src/assets/images/tmp-build/discount-flat-20250620.svg) A set amount that's subtracted from the full value of the checkout or transaction. If the total is $100 and the discount is $20, the customer pays $80. ![Illustration of an example transaction with a discount applied to each unit.](/src/assets/images/tmp-build/discount-flat-per-unit-20250620.svg) A set amount that's subtracted from each quantity of an item in the checkout or transaction. If there are 10 items at $10 each and the discount is $5, the customer pays $50. ### Redemptions You can discount a transaction in two ways: - **Customer applying a discount code** During checkout, the customer can enter a discount code. You can set whether discounts can be applied at checkout or whether they're turned off when you [initialize Paddle.js](/paddle-js/about/include-paddlejs) or [open the checkout](/paddle-js/methods/paddle-checkout-open). - **Applying a discount to a transaction directly** You can open a checkout with a discount already applied, or add a discount to any transaction that hasn't yet been billed. You can't apply a discount to a transaction that's already been billed. You should use an adjustment to [refund or credit a transaction](/build/transactions/create-transaction-adjustments) instead. Paddle counts when discounts are redeemed so you can keep track of how many times a discount has been used. A redemption counts when a transaction is completed, or on the initial application against a subscription. If a discount is present on a transaction created for subscription renewals, midcycle changes, and one-time charges, this doesn't count as a redemption. ### Rules and limits Discounts in Paddle are flexible and powerful. You can set specific rules and restrictions on how they're applied to a transaction or across multiple transactions to give you the freedom to run promotions and discounts in a way that works for you. #### Recurring discounts By default, discounts are one-time. This means they're only applied on the first transaction or checkout that you add them to. You can create recurring discounts which apply to the current transaction and future transactions where a customer has a [subscription](/api-reference/subscriptions). Discounts can recur forever, or for a number of billing periods you specify. When recurring discounts start applying to transactions depends on if a subscription has a [free trial period](/build/trials/update-trials): - **With a free trial** The discount only applies once the trial period ends. For example, a recurring discount set for 4 billing periods applies to the first paid billing period after the trial and the next 3 renewals. - **Without a free trial** The discount applies immediately. For example, a recurring discount set for 4 billing periods applies to the initial transaction and the next 3 renewals. In both cases, the discount applies to any subscription changes within those periods. #### Usage limits You can prevent discounts from being valid after certain conditions have been met. - **Time-based** Once a set date has passed, the discount can no longer be applied to checkouts or transactions. - **Amount-based** Once a discount has been redeemed a certain amount of times, the discount can no longer be applied to checkouts or transactions. Amount-based limits are a total limit for the discount, rather than a per-customer limit. Expired and fully redeemed discounts can't be redeemed against transactions or checkouts, but can be applied when updating subscriptions. #### Item restrictions By default, a discount applies to all items on a transaction. You may want a discount to only be applied to specific items at checkout. For example, only annual plans, or specific regional price points. To do this, you can provide a list of: - **Products** Restricts the discount to apply to all prices for that product. - **Prices** Restricts the discount to only apply to that specific price for a product. The discount applies only to the individual item it's restricted to. You can't limit a discount to apply to the full total only when set items are present. #### Checkout restrictions Customers can apply discounts to checkouts using a discount code. You can assign a discount code, or can have Paddle automatically generate one for you. However, you may not want customers to be able to apply a discount at checkout. This is useful when you want to control who has access to a discount, like with non-transferable custom discounts only valid for particular customers. To achieve this, you can set a discount to be turned off at checkout. If discounts aren't enabled for use at checkout, Paddle won't automatically generate a code. The discount can only be applied by passing the discount to the checkout or transaction directly using its `id`. You can also use the `showAddDiscounts` parameter to determine whether Paddle Checkout presents customers with the option to enter a discount. See [Pass checkout settings](/build/checkout/set-up-checkout-default-settings) #### Currency restrictions Discounts for a flat amount or per-seat amount are currency-specific. They have a currency and can only be applied to transactions in the same currency. For example, you can't apply a flat amount type discount (`type: flat`) with a currency of `USD` (`currency_code: USD`) to a transaction with the currency of `GBP` (`currency_code: GBP`). [Non-catalog discounts](#non-catalog-discounts) use the transaction's currency. A new transaction must have a `currency_code` if the discount being applied is for a flat amount or per-seat amount. ### Discount groups You can [create groups](#create-a-discount-group) to assign discounts under. Discounts could be categorized by usage or team, like: - Marketing and new customer prospects that would be used by the marketing team. - Retention or loyalty that would be used by customer service or operational teams. - Seasonal promotions or campaigns like Black Friday or Cyber Monday. This makes your discounts more easily identifiable and easier to manage as your catalog grows or as discounts become redundant. ### Non-catalog discounts When you [create a discount](#create-a-discount), it gets added to your discount catalog. These discounts can be used against any transaction, and at checkout by default. However, you may want to apply a discount to a single transaction which isn't recorded in your catalog. For broadly distributed and repeatable discounts. When you want to track, report, and attribute performance to the same discount. When customers need to be able to add discounts at checkout. For example, you may run a public seasonal promotion like for Black Friday, or allow all users in a specific segment to use a discount like non-profits or new customers. For one-off, highly targeted, or private offers. When a single customer should have access to a discount. When you have many custom discounts of varying prices and amounts. For example, you may agree on a discounted price for a specific customer's next transaction to prevent churn, or incentivize them to complete a renewal. Non-catalog discounts can only be applied directly to a transaction and can't be added by customers using [Paddle Checkout](/concepts/sell/self-serve-checkout). They don't show in the Paddle dashboard or when [listing discounts through the API](/api-reference/discounts/list-discounts). You can also turn on [checkout recovery](/build/checkout/checkout-recovery) to offer discounts to recover abandoned checkouts. These discounts are non-catalog, non-transferable, and only applicable to the transaction that's recovered. ## Before you begin To open a checkout with a discount, you'll need to first: - [Set your default payment link](/build/transactions/default-payment-link) under **Paddle > Checkout > Checkout settings > Default payment link**. - Get your default payment link domain approved, if you're working with the live environment. ## Create a discount Create discounts to add them to your catalog for usage against checkouts and transactions. 1. Go to **Paddle > Catalog > Discounts**. 2. Click New discount 3. Enter the details for your new discount. 4. Toggle **Recurring discount** and select from the dropdown if you want the discount to apply across subsequent transactions. 5. Select **Set an expiration date for the discount** if you want to set a date and time the discount is no longer applicable. 6. Select **Limit the number of times this discount can be redeemed** if you want to set an amount of redemptions before the discount is no longer applicable. 7. Toggle **Checkout discount code** to set a discount code that customers can apply at checkout. 8. Toggle **Limit discount to selected products** if you want to select products or prices which the discount should only be applicable to. 9. Click Save ![Illustration of the screen to create discounts in the dashboard, showing the core elements of the discount and a toggle for recurring discounts.](/src/assets/images/tmp-build/discount-create-screen-one-20250620.svg) ![Illustration of the screen to create discounts in the dashboard, showing the core elements of the discount and a toggle for recurring discounts.](/src/assets/images/tmp-build/discount-create-screen-two-20250620.svg) Send a request to the `/discounts` endpoint to create a discount. In your request, include `type`, `amount`, and `description`. - For `flat` or `flat_per_seat` types, also include `currency_code`. - Optionally set `code` for customers to use at checkout. If `enabled_for_checkout` is `true` and no `code` is provided, Paddle generates one. - Set `recur: true` to apply the discount to future billing periods; optionally limit recurrences with `maximum_recurring_intervals`. - Set `expires_at` to expire after a datetime, or `usage_limit` to expire after a number of redemptions. - Use `restrict_to` with an array of product or price IDs to limit which items the discount applies to. This example creates a new flat discount called "New Customers." It has a code of `NEWCUST` which can be applied at checkout to get $5 off every purchase. `currency_code` is required as the `type` is `flat`. If successful, Paddle responds with a copy of the new discount entity. Paddle creates an `id` starting with `dsc_` for you. This example creates a new discount called "All orders (10% off)." It recurs for a maximum of 4 billing periods after the first transaction. If successful, Paddle responds with a copy of the new discount entity. Paddle creates an `id` starting with `dsc_` for you. This example creates a new discount called "Limited July 2026 Promotion." It's set to expire at the end of July with a maximum of 1000 redemptions before it's invalid. `currency_code` is required as the `type` is `flat_per_seat`. If successful, Paddle responds with a copy of the new discount entity. Paddle creates an `id` starting with `dsc_` for you. This example creates a new discount called "Annual renewals (20% off)." It's restricted to only be applicable to transactions for annual prices across multiple products, and not available for customers to insert at checkout. If successful, Paddle responds with a copy of the new discount entity. Paddle creates an `id` starting with `dsc_` for you. ## Apply a discount Customers can apply discounts to a checkout by using a discount code during their transaction. You can also automatically apply a discount to a checkout when you open it in your frontend, or directly to a transaction using the API. You can apply non-catalog discounts directly to transactions through the API. Non-catalog discounts are intended as custom, one-off discounts which aren't part of your catalog. They can't be applied at checkout by customers, or applied automatically to a checkout when opened. `enabled_for_checkout` must be `true` against [the discount entity](/api-reference/discounts) to apply it to a checkout. Non-catalog discounts always have `enabled_for_checkout` set to `false`. You can pass either a `discountCode` or `discountId` when opening a Paddle Checkout using Paddle.js. Pass parameters to the `Paddle.Checkout.open()` method to prefill those values on a checkout. ```js Paddle.Checkout.open({ settings: { theme: "light", locale: "en" }, discountId: "dsc_01gp0ynsntfpyw2spd2md1wqx1", items: [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 }, { priceId: 'pri_01gm82kny0ad1tk358gxmsq87m', quantity: 1 }, { priceId: 'pri_01gm82v81g69n9hdb0v9sw6j40', quantity: 1 } ] }); ``` To learn more, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) You can update properties on an open checkout using the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method. You can pass either a `discountCode` or `discountId` when opening a Paddle Checkout using Paddle.js. Add data attributes to your checkout launcher to prefill those values on a checkout. ```html Buy now ``` To learn more, see [HTML data attributes](/paddle-js/about/html-data-attributes) Send a request to the `/transactions` endpoint to create a transaction. In your request, include either a `discount_id` to apply a discount from your catalog or a `discount` object with `type`, `amount`, `description`. Optionally include `recur`, `maximum_recurring_intervals`, and `restrict_to` for a [non-catalog discount](#non-catalog-discounts). For non-catalog flat discounts, include `currency_code` on the transaction. For `flat` or `flat_per_seat` catalog discounts, the discount's currency must match the transaction's `currency_code`. This request creates a new transaction with a discount from your catalog applied using the `id` of the discount. If successful, Paddle responds with a copy of the updated transaction entity with the `id` of the discount as `discount_id`. This request creates a new transaction with a non-catalog discount applied called "Custom loyalty discount", giving 10% off the next 6 billing periods. A `currency_code` is included on the transaction as the discount type is `flat`. If successful, Paddle responds with a copy of the updated transaction entity with the `id` of the discount as `discount_id`. Non-catalog discounts still have a discount entity created for them so you can see their details when fetching them through the API. They have a `mode` of `custom`. ## See how many times a discount has been redeemed You may want to understand how many times a discount has been redeemed to understand its performance, track how many usages it has left, or expose it to customers to promote a sense of urgency against a limit to the discount. You can see this against a discount entity when you fetch or list discounts as `times_used`. If a discount has a limit, you can compare this to the value of `usage_limit` to calculate and show how many redemptions are left. This example is a discount entity with a usage limit for how many times this discount can be redeemed as `usage_limit`. It shows how many times the discount has been redeemed as `times_used`. ## Create a discount group Discounts can be grouped together to make them easier to manage. For example, you might create a group for your Black Friday discounts, and then add all your Black Friday discounts to the group. Create a discount group the API in two steps: 1. **Create a discount group** Build and send a request to create your discount group. 2. **Add discounts to your group** Update discounts to add them to the group you just created. ### Create a discount group Send a request to the `/discount-groups` endpoint to a create a discount group. In your request, include a `name` for the discount group. Make it descriptive, short, and memorable — this isn't shown to customers. This example creates a discount group called "Black Friday 2024." If successful, Paddle responds with a copy of the new discount group entity. Take the `id` of the discount group if you want to [add a discount to it](#add-a-discount-to-a-discount-group). ### Add a discount to a discount group Send a request to the `/discounts/{discount_id}` endpoint to add an existing discount to a discount group, or send a request to the `/discounts` endpoint to create a new discount. Provide the `discount_group_id` you just created in the payload, with the value of `id` of the discount group. This example updates a discount called "All orders (10% off)" to add it to the "Black Friday 2024" discount group. If successful, Paddle responds with a copy of the created or updated discount entity, containing the `discount_group_id` field. ```json { "discount_group_id": "dsg_01js2gqehzccfkywgx1jk2mtsp" } ``` --- # Go-live checklist URL: https://developer.paddle.com/build/go-live-checklist Everything you need to do to transition to live when you've set up Paddle in sandbox. Ready to start selling with Paddle? This guide walks through all the steps that you need to move from [sandbox](/sdks/sandbox) to live. By the end, you'll be ready to take your first real payment. Going live involves swapping API keys in your existing integration, as well as recreating some data in your live account from sandbox. It's a good chance to review entities that you created to make sure you're only porting across good data. The steps in this guide apply to your **live account**. It assumes that you've set up and tested Paddle in your sandbox account. We walk through setting up sandbox in our [setup checklist](/build/set-up-checklist). ## Overview 1. [**Complete initial configuration**](#complete-initial-configuration) Port over key settings like your balance currency, whether prices include tax, and payment methods you support from your sandbox account. 2. [**Add products to your catalog**](#add-products-to-your-product-catalog) Create subscription plans, recurring addons, one-off charges, and other items in your live account. 3. [**Update your integration**](#update-your-integration) Make some changes to your sandbox integration so that it points to your live account. 4. [**Create notification destinations**](#create-notification-destinations) Create new notification destinations to get notified when events happen in your live account, and handle signature verification. 5. [**Post-integration**](#post-integration) When you're live, sign up for updates about new features or changes to the Paddle API and developer tools. ## Complete initial configuration Configure key settings for your live account. You set most of these in your sandbox account when testing, so mirror these settings in your live account. ### Mirror from sandbox Your default payment link is a quick way to open [Paddle Checkout](/concepts/sell/self-serve-checkout) for a transaction. It should be a page that [includes Paddle.js](/paddle-js/about/include-paddlejs), typically your checkout page. Go to **Paddle > Checkout > Checkout settings**, expand **Default payment link**, then add a default payment link. For your live account, you must use a real website (not `localhost`) that's passed [domain verification](https://www.paddle.com/help/start/account-verification/what-is-domain-verification). To learn more, see [Set your default payment link](/build/transactions/default-payment-link) Choose the payment methods you want to use with Paddle Checkout. You can turn on payment methods in a couple of clicks — no need to sign up for merchant or partner accounts. Go to **Paddle > Checkout > Checkout settings**, expand **Payment methods**, then check the payment methods you want to offer. Card is always on for checkout, and bank transfer is always on for invoices. To learn more, see [Payment methods](/concepts/payment-methods) Choose whether prices should be inclusive or exclusive of taxes by default. Typically, prices are exclusive of tax when selling to businesses and in regions with sales taxes. Go to **Paddle > Checkout > Sales tax settings** to set your sales tax setting. Choose the currency that you want to hold earnings in. Paddle automatically converts payments in other currencies to your balance currency, ready for payout. Go to **Paddle > Business account > Currencies** to set your balance currency. We recommend choosing a currency that matches your bank account. ### New steps Paddle integrates with Retain to handle dunning and payment recovery. Retain automatically retries failed payments, sends reminders, and gives customers frictionless ways to retry payment. Go to **Paddle > Retain** to set dunning and recovery settings. To learn more, see [Set up Retain](/build/retain) Paddle initiates payouts on the first of each month when your balance meets the minimum threshold. You can get paid by Paddle by bank transfer, PayPal, or Payoneer. Go to **Paddle > Business account > Payouts > Payout settings** to enter your payout details and set your minimum threshold. To learn more, see [Payouts on the Paddle help center](https://www.paddle.com/help/manage/get-paid/when-and-how-do-i-get-paid) ## Add products to your product catalog Your product catalog contains the items purchased by customers. This includes subscription plans, recurring addons, and one-off items. To start selling, recreate your sandbox product catalog in your live account. You should only create products that you plan to offer. We don't recommend copying test products to your live account. You can use the [Paddle API](/api-reference) to create [products](/api-reference/products), [prices](/api-reference/prices), and [discounts](/api-reference/discounts). If you have a large product catalog, getting entities using the API in sandbox, manipulating responses to match create request bodies, then creating entities using the API in live might speed up the process. ### Copy from sandbox Products describe the items customers purchase. They have related prices that describe how they're charged. Add products and prices to Paddle for subscription plans, recurring addons, and one-off items. Go to **Paddle > Catalog > Products** to start adding products and prices. To learn more, see [Create products and prices](/build/products/create-products-prices) Discounts let you reduce the amount a customer has to pay by a percentage or fixed amount. They can be one-time or recurring, and apply to an entire transaction or just items that you choose. Go to **Paddle > Catalog > Discounts** to start adding discounts. To learn more, see [Create products and prices](/build/products/offer-discounts-promotions-coupons) Taxable categories determine what kinds of items you offer. They make sure that the correct amount of tax is calculated. Standard Digital Goods is available by default, but you should request approval for other taxable categories if you require them. Go to **Paddle > Catalog > Taxable categories** to request approval for other taxable categories. To learn more, see [Taxable categories on the Paddle help center](https://www.paddle.com/help/start/intro-to-paddle/why-do-i-need-to-select-'taxable-categories'-for-my-products) ## Update your integration Update your integration with Paddle so that it points to your live account rather than your sandbox account. ### New steps The Paddle API has different base URLs for sandbox and live accounts. Update your base URLs so Paddle knows which environment you're working with. Replace `sandbox-api.paddle.com` with `api.paddle.com` in API endpoint URLs. [API keys](/api-reference/about/authentication) are separate for your sandbox and live accounts. [Create new API keys](/api-reference/about/authentication) for your live account, then swap [sandbox keys for live ones](/api-reference/about/authentication) in your code. Go to **Paddle > Developer tools > Authentication** to create API keys. Send a request to the `/event-types` endpoint to test your authentication. To learn more, see [Manage API keys](/api-reference/about/authentication) and [Authentication](/api-reference/about/authentication). [Client-side tokens](/paddle-js/about/client-side-tokens) are separate for your sandbox and live accounts. You use a client-side token when including, initializing, and authenticating Paddle.js in your frontend. [Create a new client-side token](/paddle-js/about/client-side-tokens) and replace the value in your frontend. Go to **Paddle > Developer tools > Authentication** to create a new client-side token. Swap the value for the `token` parameter passed to `Paddle.Initialize()` in your frontend. To learn more, see [Create client-side tokens](/paddle-js/about/client-side-tokens) and [Include Paddle.js](/paddle-js/about/include-paddlejs). Paddle.js integrates with Retain, so you don't have to include a separate Retain script. Client-side tokens for live accounts authenticate with both Paddle Billing and Paddle Retain, too. When transitioning to live, make sure you pass the `pwCustomer` parameter to `Paddle.Initialize()` when including Paddle.js so that Retain works correctly. Update `Paddle.Initialize()` calls to include `pwCustomer`, passing either the Paddle ID or email address of a customer. To learn more, see [Initialize Paddle.js and Retain](/paddle-js/about/include-paddlejs) Sandbox integrations use the `Paddle.Environment.set()` method to tell Paddle.js that you're working with a sandbox account. Remove this so Paddle.js defaults to the live environment. Remove `Paddle.Environment.set("sandbox")` from your frontend code. To learn more, see [`Paddle.Environment.set()`](/paddle-js/methods/paddle-environment-set) As your sandbox and live accounts are separate, all products, prices, discounts, and other entities in Paddle have different Paddle IDs. Price and discount IDs are used with Paddle.js to open or update items on a checkout. Replace sandbox Paddle IDs with live Paddle IDs. Replace price and discount IDs in your integration with price and discount IDs for the newly created entities in your live account. Price and discount IDs are typically used in [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open), [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize), and [`Paddle.Checkout.updateItems()`](/paddle-js/methods/paddle-checkout-updateitems) methods. You might have passed them in [HTML data attributes](/paddle-js/about/html-data-attributes), too. You may have used Paddle IDs for price, discount, or entities in your sandbox in logic that handles subscription lifecycle events like upgrades or downgrades. Replace sandbox Paddle IDs with live Paddle IDs. Replace price and discount IDs in your code that handles subscription lifecycle events with price and discount IDs for the newly created entities in your live account. You might have used them as [part of pricing pages](/build/checkout/build-pricing-page), as well as [upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade) workflows. ## Create notification destinations Notifications let you get notified when key events happen in Paddle. They can be delivered as webhooks or emails. You'll typically use webhooks to keep your app in sync with Paddle. To receive webhooks for live events, create new notification destinations in your live account. We recommend using separate webhook destination URLs for your sandbox and live accounts. ### Copy from sandbox Create notification destinations to tell Paddle where to deliver webhook notifications and which events you want to receive notifications for. Go to **Paddle Developer tools > Notifications** to create notification destinations. To learn more, see [Work with notification destinations](/webhooks/about/notification-destinations) ### New steps Paddle sends webhooks from different IP addresses for sandbox and live environments. For security, we recommend that you allowlist Paddle IP addresses and reject responses from others. Allowlist Paddle IP addresses on your webhook server. To learn more, see [Webhooks](/webhooks) All webhooks sent by Paddle include a signature that you can use to check that they were genuinely sent by Paddle. We recommend that you verify webhook signatures to make sure that they weren't tampered with in-transit. Build logic to handle webhook signature verification. To learn more, see [Verify webhook signatures](/webhooks/about/signature-verification) ## Post-integration Once you've completed the preceding steps, you're live — congratulations. We recommend keeping up-to-date with changes to the Paddle platform. We document all changes to our API and developer tools on our developer changelog. We recommend all developers check our changelog regularly or sign up for emails to get updates about new features and changes. Check our developer changelog and use the email form to sign up for updates. To learn more, see [Developer changelog](/changelog) We post details of any incidents impacting the Paddle platform in real-time on our status page. We recommend subscribing to updates by email, webhook, RSS, or Slack. Check our status page and subscribe to updates. To learn more, see [Paddle status](https://paddlestatus.com/) Your API keys grant access to the data in your Paddle system. It's good practice to [rotate your API keys](/api-reference/about/rotate-api-keys) periodically and [revoke keys](/api-reference/about/authentication) that you're no longer using. Go to **Paddle > Developer tools > Authentication** to create new API keys. Swap API keys for your new keys, then revoke old keys. To learn more, see [API keys](/api-reference/about/authentication) and [Rotate API keys](/api-reference/about/rotate-api-keys). --- # Upgrade or downgrade subscriptions URL: https://developer.paddle.com/build/subscriptions/replace-products-prices-upgrade-downgrade Upgrade or downgrade subscriptions by replacing items on a subscription. Depending on your billing model, you might let customers upgrade or downgrade their plan. This could mean: - Customers want to change their base plan, for example going from Pro to Enterprise - Customers want to change their billing frequency, for example going from monthly to annual Adding or removing users, modules, one-off fees, or other addons typically involves adding or removing items. For example, you might offer a module called "Advanced Reporting" across all plans. To learn more, see [Add or remove items](/build/subscriptions/add-remove-products-prices-addons) ## How it works In Paddle, there's no rigid hierarchy of products. This means there's no formal distinction between products that you consider base plans and products that you consider addons. When customers upgrade or downgrade, they're technically replacing items on their subscription. For example, if a customer moves from Pro to Enterprise, you'd remove the Pro product and replace it with the Enterprise one. You can do this in one API call, or on one screen in the Paddle dashboard. Customers might also say that they're upgrading or downgrading when they change their billing frequency. For example, a customer might say that they'd like to "upgrade to the annual plan." In this case, you'd swap all monthly prices for annual prices. When you make changes to items, you can determine how Paddle should bill for those changes. This is called [proration](/concepts/subscriptions/proration). Paddle's subscription billing engine calculates proration to the minute, allowing for precise billing. ## Before you begin To upgrade or downgrade a subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `active` to get a list of active subscriptions. ## Change base plan Change the base plan when customers want to move from one plan to another. For example, from Pro to Enterprise. You can choose any [proration billing mode](/concepts/subscriptions/proration). 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to change. 2. Find the subscription under the Subscriptions heading and click it. 3. From the subscription overview page, choose Edit subscription 4. Make changes to the items list. 5. To prorate charges, toggle **Charge for what's remaining of the current billing period?** on, then choose how you want to handle proration. 6. Click Continue to review, then Save to proceed. Change the base plan using the API in three steps: 1. **Extract existing items** Get the existing items on the subscription to extract the `price.id` and `quantity` for each item. 2. **Preview change** Preview how much the customer is going to be charged on a regular basis. Present this to customers if you offer an in-app workflow to upgrade or downgrade. 3. **Update subscription** Send a request to update the subscription with the changes you previewed. ### Extract existing items Send a request to the `/subscriptions/{subscription_id}` endpoint to get the existing items on the subscription. Extract the `price.id` and `quantity` for each item in the `items` array — you'll need these to build your update request. ### Preview change Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. In your request, build an `items` array. Remove the price ID for the existing base plan, then add existing items that you want to keep, plus the price ID for the new base plan. Include `proration_billing_mode` to tell Paddle how to bill for the change. For `prorated_immediately` or `full_immediately` on automatically-collected subscriptions, Paddle tries to collect right away. Optionally, include `on_payment_failure` to control payment failure behavior (defaults to `prevent_change`). Previews include `immediate_transaction`, `next_transaction`, and `recurring_transaction_details`. You should present this to customers if you offer an in-app workflow to upgrade or downgrade. ### Update subscription Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, include the same body as the preview request. Paddle updates the subscription. ## Change billing frequency Change the billing frequency when customers want to change from paying in one interval to another. For example, from monthly to annually. When you change billing frequency, you may need to replace multiple items — including addons — so they all have the same frequency. For example, if customers can purchase a Premium Support addon for $200 a month, replace this with a price for Premium Support that bills yearly when moving to an annual base plan. You can use `prorated_immediately`, `full_immediately`, and `do_not_bill` when [changing billing frequency](/concepts/subscriptions/proration). 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to change. 2. Find the subscription under the Subscriptions heading and click it. 3. From the subscription overview page, choose Edit subscription 4. Make changes to the items list. 5. To prorate charges, toggle **Charge for what's remaining of the current billing period?** on, then choose how you want to handle proration. 6. Click Continue to review, then Save to proceed. Change billing frequency using the API in three steps: 1. **Extract existing items** Get the existing items on the subscription to extract the `price.id` and `quantity` for each item. 2. **Preview change** Preview how much the customer is going to be charged on a regular basis. Present this to customers if you offer an in-app workflow to upgrade or downgrade. 3. **Update subscription** Send a request to update the subscription with the changes you previewed. ### Extract existing items Send a request to the `/subscriptions/{subscription_id}` endpoint to get the existing items on the subscription. Extract the `price.id` and `quantity` for each item in the `items` array — you'll need these to build your update request. ### Preview change Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. In your request, build an `items` array. Remove the price ID for the existing base plan and any recurring addons, then add existing items that you want to keep (like one-time charges), plus the price ID for the new base plan and any new addons. Include `proration_billing_mode` to tell Paddle how to bill for the change. Only `prorated_immediately`, `full_immediately`, and `do_not_bill` are allowed [when changing billing frequency](/concepts/subscriptions/proration). Optionally, include `on_payment_failure` to control payment failure behavior (defaults to `prevent_change`). Previews include `immediate_transaction`, `next_transaction`, and `recurring_transaction_details`. You should present this to customers if you offer an in-app workflow to upgrade or downgrade. ### Update subscription Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, include the same body as the preview request. Paddle updates the subscription. --- # Use customer portal links in your app URL: https://developer.paddle.com/build/customers/integrate-customer-portal Add customer portal links to your app to hand off core billing workflows to Paddle, letting customers manage subscriptions, payments, and account information. [The customer portal](/concepts/sell/customer-portal) gives customers a centralized place to manage purchases made from your Paddle account. You can link to the customer portal to add core subscription management, billing information, and payment history to your app. ![Illustration of a customer portal showing a subscription page. The total is $120, which is shown at the top in a large font. The screen shows an items list with two products and an itemized breakdown of the quantity, tax, and total. It shows a totals section with subtotal, discount total, and tax, and grand total. On the left hand side are details of the subscription, including the next billing date, payment method, and past transactions.](/src/assets/images/tmp-build/hero-customer-portal-20240911.svg) ## How it works When integrating Paddle, you need to build workflows to let customers manage their subscriptions, payments, and account information. You can do this in two ways: - **Build your own workflows** You can use the Paddle API to build your own billing management screens. For example, you can use the cancel subscription operation to [build a workflow to let customers cancel](/build/subscriptions/cancel-subscriptions) their subscription. - **Link to the customer portal from your app** You can link to [the customer portal](/concepts/sell/customer-portal), letting Paddle handle billing management. For example, you can link to the cancel subscription page in the customer portal to let customers cancel their subscription. While building your own workflows is great for deep integration, the customer portal is fully hosted by Paddle and includes core billing functionality out-of-the-box, making it quicker to integrate. ### Customer portal sessions You can link directly to the customer portal, where customers can log in using their email address. However, for the best customer experience, we recommend creating a [customer portal session](/api-reference/customer-portals) when linking to a customer portal from your app. Customer portal sessions generate authenticated links that automatically sign a customer in to the customer portal. This makes sense in the context of your app, where customers are already signed in and linking to a screen that asks them to log in again might cause confusion. Authenticated links include a `token` parameter, which Paddle uses to identify the customer and present their information in the portal. These tokens are unique and impossible to guess, restricted to a particular customer, and automatically expire. They can only be generated with the Paddle API using a valid API key. Customer portal sessions are temporary and shouldn't be cached. Create a new customer portal session each time you want to generate authenticated links to the customer portal. ### Deep links As well as linking to the customer portal homepage, you can create links that take customers to specific pages in the portal. For example, you can create links that take customers to the cancellation page for a specific subscription. This means you can add buttons or links to your app for particular workflows, like updating payment details or canceling a subscription. ## Before you begin Customer portal sessions are for [customers](/api-reference/customers), so you'll need the Paddle ID of a customer that you want to generate authenticated links for. You can [list customers using the Paddle API](/api-reference/customers/list-customers) or search for them in the dashboard. ## Generate an authenticated link to the customer portal homepage Create a customer portal session for a customer to generate an authenticated link to the customer portal homepage. Customers can do things like see their past payments and download invoices. Send a request to the `/customers/{customer_id}/portal-sessions` endpoint to create a customer portal session. You don't need to include a request body. Paddle returns an authenticated link to the customer portal homepage in `urls.general.overview`. ## Generate links for subscription management workflows Pass an array of subscriptions when creating a customer portal session for a customer to generate deep links that let customers make changes to their subscriptions. Generate deep links for a customer portal using the API in two steps: 1. **Get subscriptions** List subscriptions for a customer to get the Paddle IDs for them. 2. **Create portal session** Create a customer portal session for a customer, passing the Paddle IDs of the subscriptions you want to create deep links for. ### Get subscriptions Send a request to the `/subscriptions` endpoint to list subscriptions for a customer, then extract the Paddle IDs of the subscriptions you want to create deep links for. ### Create portal session Send a request to the `/customers/{customer_id}/portal-sessions` endpoint to create a customer portal session, passing a `subscription_ids` array with up to 25 subscription IDs. Paddle generates an authenticated `cancel_subscription` and `update_subscription_payment_method` link for each subscription. --- # Bill for one-time charges URL: https://developer.paddle.com/build/subscriptions/bill-add-one-time-charge Bill one-time charges to a subscription for one-off things, like setup or onboarding fees or support incident charges. Charge immediately, or add them to the next renewal. As well as adding recurring items to subscriptions, Paddle lets you bill for one-time charges. One-time charges are sometimes called one-off, ad-hoc, or non-recurring charges or fees. Bill one-time charges to a subscription when: - Customers pay a fee at the start of their subscription for things like setup, implementation, deployment, or activation. - You offer services to subscribed customers, like auditing, data export, data migration, or incident support. - You offer passes to access modules or features, like a 10-day pro pass for customers on a starter plan. - You want to provide an easy way for customers to buy ebooks, access to webinars, or other resources you offer. ## How it works One-time charges for a subscription are items that don't recur. The `billing_cycle` against the [related price entity](/api-reference/prices) is `null`. They're typically charged at the start of a subscription, like setup fees, or mid-cycle for things like incident support or data auditing. You can also use them to offer additional products, like ebooks, access to webinars, or other educational resources. While you could create an entirely new transaction for additional products, billing them to the subscription lets you: - **Automatically-collect using the payment method on file for the subscription** No need to ask a customer for card details again. Use the billing details that you use to collect for subscription payments. - **Add one-time charges to the next renewal** Collect for your one-time charges when you next collect for a subscription, offering an integrated experience. As they're non-recurring, one-time charges aren't added to the `items` list against subscriptions. This list is just for recurring items. [Proration](/concepts/subscriptions/proration) doesn't apply to one-time charges because they're not related to the billing period. Customers are always charged the full amount. ### Related subscription changes Billing for one-time charges is typically used when you want customers to pay for one-off fees or ad-hoc products. Depending on what you're looking to do, you might also like to: - [**Add items for new users, modules, or other recurring addons**](/build/subscriptions/add-remove-products-prices-addons) Adding or removing users, modules, one-off fees, or other addons typically involves adding or removing items. For example, you might offer a module called "Advanced Reporting" that customers can subscribe to. - [**Replace items to upgrade or downgrade a subscription**](/build/subscriptions/replace-products-prices-upgrade-downgrade) Upgrading or downgrading a subscription plan typically involves replacing products. For example, you might replace a "Starter plan" product with a "Premium plan" product to upgrade. ## Before you begin To bill for one-time charges, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `active` to get a list of active subscriptions. You'll also need price IDs for the one-time prices that you want to bill for. One-time charges are prices where the `billing_cycle` is `null`. Use the `recurring` query parameter with the value `false` when [listing prices](/api-reference/prices/list-prices) with the API to return one-time prices. ## Bill for one-time charges Bill for one-time charges using the API in two steps: 1. **Preview charge** Preview the charge before billing. Present this to customers if you offer an in-app workflow. 2. **Create one-time charge** Send a request to bill for the one-time charge. ### Preview charge Send a request to the `/subscriptions/{subscription_id}/charge/preview` endpoint to preview a one-time charge. In your request, build an `items` array of one-time charge price IDs and quantities. Only prices where `billing_cycle` is `null` may be included. You don't need to include existing recurring items. Include `effective_from` to control when Paddle bills: - `immediately` — creates a transaction for the charge right away. - `next_billing_period` — adds the charge to the next renewal transaction. When `effective_from` is `immediately` on an automatically-collected subscription, Paddle tries to collect right away. Optionally include `on_payment_failure` to control what happens if payment fails (defaults to `prevent_change`). ### Create one-time charge Send a request to the `/subscriptions/{subscription_id}/charge` endpoint to bill for the one-time charge. In your request, include the same body as the preview request. One-time charges aren't held against [the subscription entity](/api-reference/subscriptions), so the charges you billed for aren't returned in the response. ## Get one-time charges One-time charges aren't added to the subscription `items` list. This array is only for recurring items. To get details of a one-time charge: - [**Billed next billing period**](#next-billing-period) When billed on the `next_billing_period`, get the subscription you billed the charge to using the API and include `next_transaction`. - [**Billed immediately**](#immediately) When billed `immediately`, list transactions using the API and filter to see transactions for the subscription you billed the charge to. ### Next billing period When you bill for a one-time charge with `effective_from` as `next_billing_period`, Paddle adds it to the next renewal transaction. You can see the one-time charge by [getting the subscription](/api-reference/subscriptions/get-subscription) that you billed the charge to, using the `include` query parameter to return the `next_transaction`. One-time charges are detailed in `next_transaction.details.line_items`, along with any recurring items. One-time charges appear in `next_transaction.details.line_items` alongside recurring items. ### Immediately When you bill for a one-time charge with `effective_from` as `immediately`, Paddle creates a transaction for it right away. You can see the one-time charge by [listing transactions](/api-reference/transactions/list-transactions), filtering to see transactions for the subscription you billed the charge to. The `origin` against the transaction is `subscription_charge`, which you may also filter for. If you've [subscribed to notifications](/webhooks/about/notification-destinations) for transaction events, [`transaction.created`](/webhooks/transactions/transaction-created) occurs when you bill for one-time charges. The notification includes the complete [transaction entity](/api-reference/transactions). You can check `transaction.line_items` to get information about one-time charges. Filter by `subscription_id` and `origin=subscription_charge` to find the transaction Paddle created for the immediate charge. --- # Build your own mobile checkout implementation URL: https://developer.paddle.com/build/mobile-apps/link-out-mobile-app-custom-workflow Get a step-by-step overview of how to implement your own Paddle Checkout implementation as an external purchase 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 link users in the to an external checkout for purchases in iOS apps. You can build your own [Paddle Checkout](/concepts/sell/self-serve-checkout) implementation to quickly and securely collect for payment for digital products outside your app. Customers tap a button in your app to open your checkout implementation, then they're redirected to your app when they complete their purchase. Instead of building your own checkout implementation, you can use [hosted checkouts](/concepts/sell/hosted-checkout-mobile-apps) to let users securely make purchases outside your app. ## What are we building? In this tutorial, we'll use [Paddle.js](/paddle-js) and the [Paddle SDKs](/sdks) to create a custom [checkout implementation](/concepts/sell/self-serve-checkout) as part of an external purchase flow for in-app purchases in iOS apps. We'll walk through handling fulfillment using the [RevenueCat x Paddle integration](https://www.paddle.com/revenuecat-integration-beta) or [webhooks](/webhooks). ![Mobile checkout screen for a lifestyle app. The left side shows a payment page with a blue running shoe logo, price of $250.00 including tax, collapsed order summary section, and payment options including Apple Pay button, Card option (selected), and PayPal option. Card number field shows placeholder text with accepted payment logos (Mastercard, Visa, American Express, and Diners Club). The right side shows a confirmation screen with a blue checkmark icon and text 'Your transaction has been completed successfully.'](/src/assets/images/tmp-build/custom-checkout-flow-20250508.svg) ## What's not covered This tutorial doesn't cover: - **Handling authentication** We assume you already have a way to identify your users, like [Firebase](https://firebase.google.com/) or [Sign in with Apple](https://developer.apple.com/sign-in-with-apple/). - **Native in-app purchases** We'll launch Paddle Checkout in Safari then redirect users back to your app. Like the App Store, Paddle Checkout supports [Apple Pay](/concepts/payment-methods/apple-pay) with no additional setup, plus [other popular payment options](/concepts/payment-methods). - **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](/concepts/sell/customer-portal). We cover that elsewhere in our docs. ## Before you begin ### Sign up for Paddle You'll need a Paddle account to get started. You can sign up for two kinds of account: - [Sandbox](/sdks/sandbox) — for testing and evaluation - Live — for selling to customers For this tutorial, we recommend signing up for a sandbox account. You can transition to a live account later when you've built your integration and you're ready to start selling. If you sign up for a live account, you'll need to complete account verification. This is where we ask for some information from you to make sure that we can work together. ### Prep your iOS development environment As part of our tutorial, we're going to update our app to include a link to the page on your website where you set up Paddle.js. You'll need: - Some knowledge of iOS development, access to your iOS project, and Xcode on macOS. - A [correctly configured URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) so you can redirect users back to your app. You don't need to make changes to your iOS app to set up Paddle.js on your website, so you can come back to this later if you're working with a separate iOS developer. ## Overview Build your own Paddle Checkout implementation to link out for in-app purchases in six steps: 1. [**Map your product catalog**](#map-your-product-catalog) Create products and prices in Paddle that match your in-app purchase options. 2. [**Add Paddle.js to your website**](#add-paddlejs-to-your-website) Include Paddle.js on a page on your website, then handle the post-purchase redirect back to your app. 3. [**Create a transaction in Paddle**](#create-a-transaction-in-paddle) Build logic in your backend to create a customer and a transaction in Paddle, ready for checkout. 4. [**Add a checkout button to your app**](#add-a-checkout-button-to-your-app) Create a button that creates a transaction in Paddle and opens your checkout. 5. [**Handle fulfillment and provisioning using webhooks**](#handle-fulfillment-and-provisioning) Use RevenueCat or process webhooks to fulfill purchases after a customer completes a checkout. 6. [**Take a test payment**](#test-the-complete-flow) Make a test purchase to make sure your purchase flow works correctly. ## 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](/build/products/create-products-prices) 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](/build/products/create-products-prices) using the Paddle dashboard or the API. 1. Go to **Paddle > Catalog > Products**. 2. Click New product 3. Enter details for your new product, then click Save when you're done. 4. Under the **Prices** section on the page for your product, click New price 5. Enter details for your new price. Set the type to **One-time** to create a one-time price. 6. Click Save when you're done. 7. Click the button next to a price in the list, then choose Copy price ID from the menu. Keep this for later. ![Illustration showing the new product drawer in Paddle. It shows fields for product name, tax category, and description](/src/assets/images/tmp-build/dashboard-create-product-20230831.svg) ![Prices list in the Paddle dashboard, with the action menu open and copy ID selected.](/src/assets/images/tmp-build/copy-price-id-20250127.svg) ## Add Paddle.js to your website [Paddle.js](/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`, or `pnpm`. ```bash pnpm add @paddle/paddle-js ``` ```bash yarn add @paddle/paddle-js ``` ```bash npm install @paddle/paddle-js ``` Import Paddle.js, then initialize by calling the [`initializePaddle()`](https://github.com/PaddleHQ/paddle-js-wrapper?tab=readme-ov-file#initialize-paddlejs) function with a configuration object. We'll get a client-side token in the next step. ```typescript import { initializePaddle } from '@paddle/paddle-js'; const paddle = await initializePaddle({ token: 'CLIENT_SIDE_TOKEN' }); ``` You can manually load the Paddle.js script on your website using a script tag. Copy and paste this in the `` section of the page where you want to launch Paddle Checkout. ```html ``` We'll get a client-side token in the next step. ### Get a client-side token [Client-side tokens](/api-reference/about/authentication) are for authenticating with Paddle in your frontend. We need one to securely open Paddle Checkout. 1. Go to **Paddle > Developer tools > Authentication**. 2. Click the **Client-side tokens** tab, then click New client-side token 3. Give your client-side token a name and description, then click Save 4. From the list of client-side tokens, click the button next to the client-side token you just created, then choose Copy token from the menu. 5. Paste your token as `CLIENT_SIDE_TOKEN` in the code you copied. ![Paddle's Authentication dashboard interface showing API management tools. The page header 'Authentication' appears at top left with a 'New client-side token' button at top right. Below is explanatory text: 'Securely manage your secret API keys and client-side tokens. Use API keys to interact with Paddle on the server side, or when working with trusted integrations. Use client-side tokens for working with Paddle.js in your frontend.' Navigation tabs show 'API keys' and 'Client-side tokens' (selected). A table displays existing keys with columns for Name, Token (showing masked values like 'live_******'), Status (showing 'Active' with green indicators), and Last used dates from September 20.](/src/assets/images/tmp-build/client-side-token-create-20250407.svg) ### Build a success workflow After users complete a purchase successfully, we need to redirect users back to our app. To do this, we can use an event callback function. [Paddle.js emits events throughout the checkout process](/paddle-js/events) 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`](/paddle-js/events/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`: ```typescript import { initializePaddle } from '@paddle/paddle-js'; import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js'; const paddle = await initializePaddle({ token: 'CLIENT_SIDE_TOKEN', eventCallback: (event: PaddleEventData) => { if (event.name === CheckoutEventNames.CHECKOUT_COMPLETED) { setTimeout(() => { window.location.href = `myapp://example-redirect?transactionId=${event.data?.id}`; }, 3000); } } }); ``` Replace `myapp://example-redirect` with a custom URL scheme or [universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/) 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. Update the JavaScript snippet in the `` section of the page to include an `eventCallback` function: ```html highlightLines="5-12" ``` Replace `myapp://example-redirect` with a custom URL scheme or [universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/) in your iOS app, but keep the `?transactionId=` 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. For an optimized Apple Pay experience on mobile, pass `variant: 'express'` in your checkout settings to open an [express checkout](/concepts/sell/express-checkout). This must be combined with `displayMode: 'inline'` and requires [domain verification for Apple Pay](/concepts/payment-methods/apple-pay#verify-your-domain-for-apple-pay). ### Set your default payment link Your [default payment link](/build/transactions/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: 1. Go to **Paddle > Checkout > Checkout settings**. 2. Enter the page where you added Paddle.js under the **Default payment link** heading. 3. Click Save when you're done. ![Checkout settings screen showing the Paddle dashboard. The default payment URL is set to https://example.com/](/src/assets/images/tmp-build/default-payment-link-20250127.svg) ## Create a transaction in Paddle [Transactions](/api-reference/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 as a central identifier for this purchase throughout the journey. ### Install Paddle First, add the Paddle SDK to your backend. Paddle has [SDKs for Node.js, Python, PHP, and Go](/sdks). Install using `npm`, `yarn`, or `pnpm`. For example: ```bash pnpm add @paddle/paddle-node-sdk ``` ```bash yarn add @paddle/paddle-node-sdk ``` ```bash npm install @paddle/paddle-node-sdk ``` ### Get an API key [API keys](/api-reference/about/authentication) are for authenticating with Paddle in your backend. We need one to create a transaction. 1. Go to **Paddle > Developer tools > Authentication** 2. Click New API key 3. Give your key a name and description, then set an expiry date. 4. Under permissions, check **Write** for customers and transactions. You can always edit permissions later. 5. Click Save when you're done, then copy the API key. 6. Store this safely in your credential manager or secret store. ![A modal dialog window titled 'New API key' with form fields for creating a Paddle API key. Contains input fields for 'Name' (required) and 'Description' (optional). The 'Expires' section shows default expiration set to '90 days (March 8, 2025)' with a calendar picker and explanatory text about setting expiration dates for security and compliance with a 'Learn more' link. A close button appears in the top-right corner. In the background, a partially visible table shows previously created API keys.](/src/assets/images/tmp-build/api-key-create-2-new-20250407.svg) ### 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 link. ```typescript const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); // Import Paddle SDK const { Paddle } = require('@paddle/paddle-node-sdk'); // Express setup const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); require('dotenv').config(); // Initialize Paddle // Assumes you have a Paddle API key in the .env file const paddle = new Paddle(process.env.PADDLE_API_KEY); app.post("/paddle/create-transaction", async (req, res) => { try { // You will insert code here in the next steps } catch (error) { console.error('Error creating transaction:', error); return res.status(500).json({ error: error.message }); } }); // Start server app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); ``` ### Create a customer If the user is signed in to your app, [create a customer](/build/customers/create-update-customers) in Paddle for them. [Customers](/api-reference/customers) are lightweight entities that hold high-level details, like name and email address. They have related address and business entities. ```typescript const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); // Import Paddle SDK const { Paddle } = require('@paddle/paddle-node-sdk'); // Express setup const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); require('dotenv').config(); // Initialize Paddle const paddle = new Paddle(process.env.PADDLE_API_KEY); app.post("/paddle/create-transaction", async (req, res) => { try { // 1. Fetch or create a Paddle customer const { userId } = req.body; const existingUser = await User.findOne({ where: { id: userId } }); if (!existingUser) { return res.status(404).json({ error: 'User not found' }); } // Check if customer already exists in Paddle if (!existingUser.paddleCustomerId) { const customer = await paddle.customers.create({ email: existingUser.email, name: existingUser.name }); // Update the user with the Paddle customer ID await existingUser.update({ paddleCustomerId: customer.id }); existingUser.paddleCustomerId = customer.id; } } catch (error) { console.error('Error creating transaction:', error); return res.status(500).json({ error: error.message }); } }); // Start server app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); ``` We recommend storing the Paddle 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 fulfillment](#handle-fulfillment-and-provisioning), include an [object of custom data](/api-reference/about/custom-data) containing a unique identifier for RevenueCat. ```typescript const cors = require('cors'); const bodyParser = require('body-parser'); // Import Paddle SDK const { Paddle } = require('@paddle/paddle-node-sdk'); // Express setup const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); require('dotenv').config(); // Initialize Paddle const paddle = new Paddle(process.env.PADDLE_API_KEY); app.post("/paddle/create-transaction", async (req, res) => { try { // 1. Fetch or create a Paddle customer const { userId } = req.body; const existingUser = await User.findOne({ where: { id: userId } }); if (!existingUser) { return res.status(404).json({ error: 'User not found' }); } // Check if customer already exists in Paddle if (!existingUser.paddleCustomerId) { const customer = await paddle.customers.create({ email: existingUser.email, name: existingUser.name }); // Update the user with the Paddle customer ID await existingUser.update({ paddleCustomerId: customer.id }); existingUser.paddleCustomerId = customer.id; } // 2. Create the transaction // (Optional) Grab RevenueCat metadata field ID passed from the iOS app. // You can use any name to pass through. We use revenuecatId. const { revenuecatId } = req.body; // Grab the items from the request body // This is an array of objects with a price_id and quantity for each item the customer is purchasing const { items } = req.body; if (!Array.isArray(items) || items.length === 0) { return res.status(400).json({ error: "Items array is required and can't be empty" }); } // Create the transaction const transaction = await paddle.transactions.create({ customer_id: existingUser.paddleCustomerId, items: items, collection_mode: 'automatic', // Default that means checkout is created custom_data: { rc_user_id: revenuecatId // (Optional) If using Revenuecat, pass the metadata field ID. } }); // Return the checkout link to the iOS app res.json({ checkoutUrl: transaction.checkout.url }); } catch (error) { console.error('Error creating transaction:', error); return res.status(500).json({ error: error.message }); } }); // Start server app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); ``` ## Add a checkout button to your app Now, update your iOS app to add a button that: 1. Checks to see if in-app purchases are allowed on the device 2. Checks to see if a user already purchased the item. 3. Calls your backend endpoint to create a Paddle transaction. 4. Opens the checkout link returned by your endpoint in Safari. ## Handle fulfillment and provisioning When a customer completes a purchase, they'll be redirected back to your app. At this point, you need to handle fulfillment and unlock the features they bought. You can extract the transaction ID from the redirect URL query parameter to match the checkout with the transaction you created earlier and the [`transaction.completed`](/webhooks/transactions/transaction-completed) webhook. If you use the [RevenueCat x Paddle integration](https://www.paddle.com/revenuecat-integration-beta) to handle entitlements, you're all set! Here's how it works: 1. Paddle automatically sends data to RevenueCat about the completed checkout. 2. RevenueCat grants the user an entitlement based on [your product configuration](https://www.revenuecat.com/docs/offerings/products-overview). 3. Use the RevenueCat SDK to [check entitlement status](https://www.revenuecat.com/docs/customers/customer-info) in your iOS app. You can use webhooks to build your own fulfillment workflow. In this example, we'll grant users access when they've purchased our `Lifetime Access` product. #### Build a webhook handler When a customer creates or completes a transaction, Paddle can send a webhook to an endpoint you set up. 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](/build/mobile-apps/link-out-mobile-app-custom-workflow#setup-endpoint-create-transaction). ```typescript app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => { try { // You can verify the webhook signature here // We don't cover this in the tutorial but it's best practice to do so // https://developer.paddle.com/webhooks/signature-verification const payload = JSON.parse(req.body.toString()); const { data, event_type } = payload; const occurredAt = payload.occurred_at; // Listen for vital events from Paddle switch (event_type) { // 1. Record transactions in the database // Handle a new transaction // You can create a Transaction database to store records and associate them to a user case 'transaction.created': // Find the user associated with this transaction const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } }); if (userForTransaction) { await Transaction.create({ transactionId: data.id, userId: userForTransaction.id, subscriptionId: data.subscription_id, status: data.status, amount: data.amount, currencyCode: data.currency_code, occurredAt: occurredAt }); } break; // Handle a completed transaction // If you have a Transaction database, you can update the transaction record case 'transaction.completed': // Find the transaction by its ID const completedTransaction = await Transaction.findOne({ where: { transactionId: data.id } }); if (completedTransaction) { await completedTransaction.update({ status: data.status, subscriptionId: data.subscription_id, invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, billedAt: data.billed_at, updatedAt: data.updated_at }); } break; } res.json({ received: true }); } catch (error) { console.error('Error processing webhook:', error); return res.status(500).json({ error: error.message }); } }); ``` #### Unlock user access When you receive the [`transaction.completed`](/webhooks/transactions/transaction-completed) webhook, you can use the details to handle order fulfillment 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. ```typescript app.post("/paddle/webhooks", express.raw({ type: 'application/json' }), async (req, res) => { try { // You can verify the webhook signature here // We don't cover this in the tutorial but it's best practice to do so // https://developer.paddle.com/webhooks/signature-verification const payload = JSON.parse(req.body.toString()); const { data, event_type } = payload; const occurredAt = payload.occurred_at; // Listen for vital events from Paddle switch (event_type) { // 1. Record transactions in the database // Handle a new transaction // You can create a Transaction database to store records and associate them to a user case 'transaction.created': // Find the user associated with this transaction const userForTransaction = await User.findOne({ where: { paddleCustomerId: data.customer_id } }); if (userForTransaction) { await Transaction.create({ transactionId: data.id, userId: userForTransaction.id, subscriptionId: data.subscription_id, status: data.status, amount: data.amount, currencyCode: data.currency_code, occurredAt: occurredAt }); } break; // Handle a completed transaction // If you have a Transaction database, you can update the transaction record case 'transaction.completed': // Find the transaction by its ID const completedTransaction = await Transaction.findOne({ where: { transactionId: data.id } }); if (completedTransaction) { await completedTransaction.update({ status: data.status, subscriptionId: data.subscription_id, invoiceId: data.invoice_id, invoiceNumber: data.invoice_number, billedAt: data.billed_at, updatedAt: data.updated_at }); // 2. Provision access to your app // Fetch the user associated with this transaction const user = await User.findOne({ where: { id: completedTransaction.userId } }); if (user) { // Fetch the items from the transaction const purchasedItems = data.items || []; // Add what access the user has based on the items they purchased // For this example, we're using access permissions and storing them in the user model on an accessPermissions field // We also map the Paddle product IDs to the access permissions // In a real app, you could use a database table for this mapping // Get existing permissions to see if any first const accessPermissions = user.accessPermissions ? JSON.parse(user.accessPermissions) : {}; // Map product IDs to access permissions // We add an additional product as an example of how you can handle multiple const productToPermission = { 'pro_01h1vjes1y163xfj1rh1tkfb65': 'lifetimeAccess', 'pro_01gsz97mq9pa4fkyy0wqenepkz': 'temporaryAccess' } purchasedItems.forEach(({ price }) => { const permissionKey = productToPermission[price.product_id]; if (permissionKey) accessPermissions[permissionKey] = true; }); // Update the user with their new access permissions await user.update({ accessPermissions: JSON.stringify(accessPermissions), }); } } break; } res.json({ received: true }); } catch (error) { console.error('Error processing webhook:', error); return res.status(500).json({ error: error.message }); } }); ``` #### Create a notification destination To start receiving webhooks, [create a notification destination](/webhooks/about/notification-destinations). This is where you can tell Paddle which events you want to receive and where to deliver them to. 1. Go to **Paddle > Developer tools > Notifications**. 2. Click New destination. 3. Set **Notification type** to **URL** and enter the URL for your webhook handler. 4. Choose the `transaction.completed` event. You can always edit events later. 5. Click Save destination. ![Illustration of the new destination drawer in Paddle. It shows fields for description, type, URL, and version. Under those fields, there's a section called events with a checkbox that says 'select all events'](/src/assets/images/tmp-build/create-webhook-destination-20240912.svg) ## Test the complete flow We're now ready to test the complete purchase flow end-to-end. If you're using a sandbox account, you can take a test payment using [our test card details](/concepts/payment-methods/card): An email address you own Any valid country supported by Paddle Any valid ZIP or postal code `4242 4242 4242 4242` Any name Any valid date in the future. `100` Before you go live, extend your Apple Pay integration by [verifying your domain for Apple Pay](/concepts/payment-methods/apple-pay). This lets you launch the Apple Pay modal directly from your checkout. ## Next steps That's it. Now you've built a purchase workflow that links out to Paddle Checkout, 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. See which payment methods you can offer your customers, including credit cards, PayPal, Apple Pay, more. Track revenue, subscriptions, and other business metrics natively in Paddle. Let customers manage their subscriptions, payments, and account details in a self-service portal. ### 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. Start here for a complete overview of integrating with Paddle and launching your workflow. Offer geo-based prices for your products and subscriptions, automatically in checkout. Listen for events during checkout to enhance the customer experience or trigger actions. ### 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. Pause, resume, or flexibly manage customer subscriptions in your workflow. Offer free trials or update trial periods to give customers more flexibility. Reduce churn and recover payments by integrating Paddle Retain. --- # Configure Term Optimization URL: https://developer.paddle.com/build/retain/configure-term-optimization-automatic-upgrades Increase lifetime value and reduce churn by intelligently identifying customers on a monthly plan who are likely to upgrade to longer term. Retain automatically reaches out by email, in-app, and during payment recovery. [Term Optimization](/concepts/retain/term-optimization), part of [Paddle Retain](/concepts/retain), analyzes customer engagement and other subscription data to determine which customers are likely to upgrade to a plan with a longer term length, like a quarterly or annual plan. It automatically reaches out at the right time with the right messaging — in-app, via email, or during payment recovery — prompting customers to upgrade using a secure payment form. ![Screenshot showing the Retain Control Center page. The Term Optimization tab is open.](/src/assets/images/tmp-build/retain-term-op-control-center-20240109.png) ## How it works [Paddle Retain](/concepts/retain) combines world-class subscription expertise with algorithms that use billions of data points to automatically reduce churn. Paddle Billing is fully integrated with Retain, meaning it automatically handles dunning and retention for you. [Term Optimization](/concepts/retain/term-optimization), part of Paddle Retain, works to automatically upgrade customers to annual plans — no effort required on your part. You can choose how Retain identifies customers to upgrade, as well as how you'd like Retain to contact eligible customers. If you use Paddle Billing, Term Optimization automatically upgrades the related subscription in Paddle Billing for you. There's no need to build logic to handle this. To learn more about Term Optimization, see [Term Optimization](/concepts/retain/term-optimization) ## Before you begin - **Set up Paddle Retain** If you haven't already, connect Paddle Retain to your billing platform and [set up Paddle Retain](/build/retain). - **Make sure you've installed Paddle.js** Paddle.js must be installed and verified as installed on a public page on your site. [Follow the instructions during setup](/build/retain), click **Edit** under **Paddle.js is not installed**, or click **Install** under **Paddle.js is not installed in web app**. ## Set up Term Optimization 1. Grab a copy of the [Paddle Retain Term Optimization configuration](https://docs.google.com/spreadsheets/d/1U_JctATw1EwW67hzVFVs_OCmqNWUJlAOXDbd8Sll6mk/edit?usp=sharing) template. 2. Fill out the spreadsheet by adding the price IDs for your monthly plans in the first column, then the corresponding annual plan price IDs in the second column. You can get price IDs by going to **Paddle > Products**, clicking into a product, then choosing Copy ID from the menu next to a price in the list, or by using [the list prices operation](/api-reference/prices/list-prices). 3. Choose which [Term Optimization strategy](/concepts/retain/term-optimization) you want to use. You can target for (1) less churn, (2) increased revenue, or (3) both. 4. Choose which [outreach methods](/concepts/retain/term-optimization) you want to use. You can send emails, send in-app notifications, and prompt customers to upgrade when updating their payment method as part of payment recovery. 5. Send the completed spreadsheet to us at [sellers@paddle.com](mailto:sellers@paddle.com?subject=RETAIN%20Term%20Optimization). In your email, let us know which Term Optimization strategy and outreach methods you want to use. We'll take care of the rest. 1. Grab a copy of the [Paddle Retain Term Optimization configuration](https://docs.google.com/spreadsheets/d/1U_JctATw1EwW67hzVFVs_OCmqNWUJlAOXDbd8Sll6mk/edit?usp=sharing) template. 2. Fill out the spreadsheet by adding the API IDs for your monthly plans in the first column, then the corresponding annual plan API IDs in the second column. You can get API IDs by going to **Stripe > Products**, clicking into a product, then copying the API ID for a price under the pricing section. 3. Choose which [Term Optimization strategy](/concepts/retain/term-optimization) you want to use. You can target for (1) less churn, (2) increased revenue, or (3) both. 4. Choose which [outreach methods](/concepts/retain/term-optimization) you want to use. You can send emails, send in-app notifications, and prompt customers to upgrade when updating their payment method as part of payment recovery. 5. Send the completed spreadsheet to us at [sellers@paddle.com](mailto:sellers@paddle.com?subject=RETAIN%20Term%20Optimization). In your email, let us know which Term Optimization strategy and outreach methods you want to use. We'll take care of the rest. When mapping monthly plans to annual plans, the annual plan must be less than the monthly plan multiplied by 12. Quarterly plans can't be upgraded to annual plans. ## Simulate Term Optimization notification Once you've set up Term Optimization, you can simulate a notification to check how it looks to customers. 1. Go to a page where you've [installed Paddle.js for Retain](/paddle-js/about/include-paddlejs). 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Enter Paddle.Retain.demo({feature: 'termOptimizationInApp'}). 1. Go to a page where you've [installed the ProfitWell.js snippets](/build/retain). 2. Open your [browser console](https://developer.chrome.com/docs/devtools/console/). 3. Enter profitwell('cq_demo', 'plan_upgrade', 'notification'). --- # Pass a transaction to a checkout URL: https://developer.paddle.com/build/transactions/pass-transaction-checkout Pass an existing transaction to Paddle.js to open a checkout to collect for it. You can do this for automatically and manually-collected transactions. [Transactions](/api-reference/transactions) hold all the information about a customer purchase, including customer details, items, calculated tax and localized pricing, and payments. Paddle creates transactions automatically when checkouts are opened, but you can also [create your own transactions](/build/transactions/create-transaction) and pass them to a checkout. ## How it works All purchases are transactions. You can work with transactions using Paddle.js in two ways: When you open a checkout, [Paddle.js](/paddle-js) automatically creates a transaction for you. 1. Pass items to Paddle.js using [HTML data attributes](/paddle-js/about/html-data-attributes) or [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open). 2. Paddle.js automatically creates a transaction for the items passed. 3. Customers pay using [Paddle Checkout](/concepts/sell/self-serve-checkout). This is a typical self-service workflow, where customers sign up and pay using your website. Learn more: [Pass or update checkout items](/build/checkout/pass-update-checkout-items) You can also [create a transaction manually using the API](/build/transactions/create-transaction), then pass this to Paddle.js. 1. You create a transaction using the API or the Paddle dashboard. 2. Pass transaction to Paddle.js using [HTML data attributes](/paddle-js/about/html-data-attributes) or [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open). 3. Customers pay using [Paddle Checkout](/concepts/sell/self-serve-checkout). This is more typical when working with sales-assisted customers who want to pay an invoice by card. Learn more: [Create a transaction](/build/transactions/create-transaction) You can pass automatically-collected transactions and manually-collected transactions where `billing_details.enable_checkout` is `true` to a checkout. ## Before you begin - To pass a transaction to a checkout, you'll need to first [create a transaction using the API](/build/transactions/create-transaction). - You'll also need to [build an inline](/build/checkout/build-branded-inline-checkout) or [overlay checkout](/build/checkout/build-overlay-checkout) to pass a transaction to. - If you haven't already, you'll need to [add a default payment link](/build/transactions/default-payment-link) to your checkout under **Paddle > Checkout > Checkout settings > Default payment link**, and get it approved. ## Use checkout payment link The simplest way to pass a transaction to a checkout is to use [a checkout payment link](/build/transactions/default-payment-link). Automatically-collected transactions, and manually-collected transactions where `billing_details.enable_checkout` is `true`, include a `checkout.url`. This is made up of your default payment link, with a `_ptxn` query parameter and the transaction ID appended. Provided your default checkout link page [includes Paddle.js](/paddle-js/about/include-paddlejs), it automatically opens a checkout for the transaction passed in the URL. The opened checkout [inherits settings from `checkout.settings` in `Paddle.Initialize()`](/build/checkout/set-up-checkout-default-settings). You can: - Set `allowLogout` to `false` to [prevent customers from changing their details](/build/checkout/set-up-checkout-default-settings) - Mark a transaction as `billed` to [stop customers from editing items on it](/build/checkout/pass-update-checkout-items) Pass an approved domain as `checkout.url` when creating or updating a transaction to override the domain that Paddle uses to generate your checkout payment link. ## Pass a transaction to Paddle.js When building pages with [Paddle.js](/paddle-js), you can pass a transaction ID to a checkout using [HTML data attributes](/paddle-js/about/html-data-attributes) or when you call [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open). You should do this instead of passing an array of `items`. Pass `transactionId` to [the `Paddle.Checkout.open()` method](/paddle-js/methods/paddle-checkout-open) to open a checkout for the passed transaction. ```javascript title="Checkout open method" highlightLines="6" Paddle.Checkout.open({ settings: { theme: "light", locale: "en" }, transactionId: "txn_01h0j589qt1nee24210teqtz57" }); ``` Add `data-transaction-id` as an attribute to your checkout launcher to open a checkout for the passed transaction. ```html title="Checkout launcher element" highlightLines="6" Buy now ``` --- # Pass checkout settings URL: https://developer.paddle.com/build/checkout/set-up-checkout-default-settings Pass settings to Paddle.js to determine how opened checkouts should work. Set default settings for all checkouts on a page to save time. As well as passing items to a checkout, you can pass settings that tell [Paddle.js](/paddle-js) how a checkout should work. If you offer multiple products or plans, you might have more than one checkout button on a page. To save time, you can pass default settings that apply to all checkouts opened when including Paddle.js. ## How it works You can pass settings to Paddle.js to determine how opened checkouts should work. You can do this in three ways: - **Pass data attributes to button that opens checkout.** Use [HTML data attributes](/paddle-js/about/html-data-attributes) on your checkout launcher element. The settings you pass apply only to the opened checkout. - **Apply settings to only the opened checkout.** Pass `settings` to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open). The settings you pass apply only to the opened checkout. - **Apply settings to all checkouts opened on a page.** Pass `checkout.settings` to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize). The settings you pass apply to all checkouts. If you have more than one checkout link on a page and all the checkouts you use have the same settings, we recommend using the `Paddle.Initialize()` method. This sets default settings that apply to all checkouts opened on the page, meaning you don't need to pass the same settings for each checkout. Use `Paddle.Checkout.open()` or HTML data attributes to pass settings for each checkout on a page. HTML data attributes are generally recommended for [overlay checkouts](/build/checkout/build-overlay-checkout), or when working with a CMS that has limited customization options. ## Before you begin - [Include Paddle.js on your page](/paddle-js/about/include-paddlejs) and pass a [client-side token](/paddle-js/about/client-side-tokens). - To open a checkout, you'll need to [create products and prices](/build/products/create-products-prices) and [pass them to a checkout](/build/checkout/pass-update-checkout-items). To get a step-by-step overview of how to build a complete checkout, including passing checkout settings and prefilling properties, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) ## Required settings If you're building [an inline checkout](/build/checkout/build-branded-inline-checkout), some settings are required: Display mode for the checkout. Class name of the element where the checkout should be rendered. Styles to apply to the checkout element. `min-width` must be set to `286px` or above with checkout padding off, `312px` with checkout padding on. Height in pixels of the `
` on load. Do not include `px`. Recommended `450`. `frameStyle` isn't required, but strongly recommended for styling the inline checkout frame. If not set, checkout frames inherit browser or default styles for `iframe` elements. ## Recommended settings ### One-page checkout By default, Paddle presents customers with a multi-page checkout experience. You can present customers with a one-page experience to collect customer information and payment details on the same page. Use `variant` to present `one-page` or `multi-page` checkout experiences. Paddle Checkout defaults to `multi-page` by default. Checkout experience presented to customers. ### Dark mode Use `theme` to style a checkout for `dark` or `light` mode. Paddle Checkout defaults to `light` by default. Theme for the checkout. ### Allow logout If you're presenting an existing customer with options to upgrade, set `allowLogout` to `false`. This hides the option to change the customer on an opened checkout. Whether the user can change their email once on the checkout. When you pass a `customerAuthToken` to Paddle.js to authenticate a customer, or you pass an `upsell` object to Paddle.js to [show an upsell flow](/build/checkout/upsell-checkout), `allowLogout` is ignored and set to `false`. ### Locale Paddle Checkout uses the browser default locale if `locale` isn't passed. If you have a language selector on your website or app, we recommend passing the chosen locale to opened checkout so that it matches. Language for the checkout. If omitted, the browser's default locale is used. ### Hide option to add discount Paddle Checkout includes an "Add discount" option to let customers enter a discount code. If you don't offer discounts, you might like to hide this option. Whether the option to add a discount is displayed at checkout. Requires the "display discount field on the checkout" option enabled in Paddle > Checkout > Checkout settings. ### Hide option to add tax number Paddle Checkout includes an "Add tax number" option to let customers enter a tax number and business information. If you don't work with businesses, you might like to hide this option. Whether the option to add a tax number is displayed at checkout. ## Set default settings during initialization Rather than passing checkout settings each time you create a link or call `Paddle.Checkout.open`, you can set default settings when you include Paddle.js. These settings apply to all checkouts opened on the page. Use the `Paddle.Initialize()` method and pass your checkout defaults to `checkout.settings`. You can do this in the same block where you [include Paddle.js](/paddle-js/about/include-paddlejs) on your page. This example sets up an inline checkout, then sets the checkout `theme` to `dark`, `locale` to Spanish (`es`), and `allowLogout` to `false`: ```html ``` To learn more, see [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) ## Pass settings for each checkout You can pass settings for each checkout that you open on the page when you create a link or call `Paddle.Checkout.open()`. This example sets the checkout `theme` to `dark`, `locale` to Spanish (`es`), and `allowLogout` to `false`. ```javascript Paddle.Checkout.open({ settings: { theme: "dark", locale: "es", allowLogout: false, items: [ { priceId: 'pri_01gs59hve0hrz6nyybj56z04eq', quantity: 1 }, { priceId: 'pri_01gs59p7rcxmzab2dm3gfqq00a', quantity: 1 } ] } }); ``` For a full list of parameters, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) This example sets the checkout `data-theme` to `dark`, `data-locale` to Spanish (`es`), and `data-allow-logout` to `false`. ```html Buy Now ``` For a full list of HTML data attributes, see [HTML data attributes](/paddle-js/about/html-data-attributes) --- # Change billing dates URL: https://developer.paddle.com/build/subscriptions/change-billing-dates Change the billing date of a subscription to change when a customer next pays, and when their subscription renews in the future. Subscriptions renew automatically when their billing period elapses. For example, a subscription might renew every week, month, or year. Change the billing date of a subscription when customers want their subscription to renew on a certain day or time every period, like the first of the month or the first day of the financial year. Working with trialing subscriptions? You can extend a trial or cut a trial short to bill for it right away, see [Extend or activate a trial](/build/trials/extend-activate-change-date-trials) ## How it works Paddle automatically creates subscriptions when customers pay for recurring items using [the checkout](/concepts/sell/self-serve-checkout), or when you [create and issue invoices](/build/invoices/create-issue-invoices) using manually-collected transactions. By default, the billing date for each renewal is based on the date that the subscription was created. For example, if a subscription bills annually then its billing date is every year on the anniversary of its creation. Change the billing date against a subscription to change when the subscription renews. This changes the next billing date of the subscription, and the day and time that it renews in the future. When you change a subscription billing date, you can determine how Paddle should bill for any changes. This is called [proration](/concepts/subscriptions/proration). Paddle's subscription billing engine calculates proration to the minute, allowing for precise billing. If you choose to prorate: - When a customer moves their billing date **later** than their renewal, Paddle calculates the prorated **amount that they owe** and **bills** for it. - When a customer moves their billing date **sooner** than their renewal, Paddle calculates the prorated **amount that they already paid** for and **creates a credit** for it. You can also choose `do_not_bill` to change the billing date without charging or crediting. ## Before you begin To change the billing date of a subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `active` to get a list of active subscriptions. ## Change billing dates 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to change. 2. Find the subscription under the Subscriptions heading and click it. 3. From the subscription overview page, choose Change next billing date 4. Enter the new date, then click Save to proceed. Change billing dates using the API in two steps: 1. **Preview change** Preview how the proration will apply before committing the change. Present this to customers if you offer an in-app workflow. 2. **Update subscription** Send a request to update the subscription with the new billing date. ### Preview change Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. In your request, include `next_billed_at` (RFC 3339 timestamp) with the new billing date and `proration_billing_mode`. For billing date changes, only `prorated_immediately`, `prorated_next_billing_period`, and `do_not_bill` are allowed. Optionally include `on_payment_failure` to control payment failure behavior (defaults to `prevent_change`). Previews include `immediate_transaction`, `next_transaction`, and `recurring_transaction_details`. With `prorated_next_billing_period`, `immediate_transaction` is `null` and proration is charged on the next renewal. ### Update subscription Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, include the same body as the preview request. Paddle updates the subscription. --- # Pass or update checkout items URL: https://developer.paddle.com/build/checkout/pass-update-checkout-items Pass price IDs to Paddle.js to tell Paddle what a checkout is for. Use HTML data attributes or JavaScript properties. As well as [passing settings](/build/checkout/set-up-checkout-default-settings), you must pass an array of prices to a checkout before opening. This tells Paddle what the customer should be billed for. ## How it works When opening a checkout, pass a list of [prices](/api-reference/prices) to it to tell Paddle what the checkout is for. You can pass properties to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) or use [HTML data attributes](/paddle-js/about/html-data-attributes). Recurring items on a checkout must have the same billing interval. For example, you can't have a checkout with some prices that are billed monthly and some products that are billed annually. [Overlay checkout](/build/checkout/build-overlay-checkout) includes options for customers to change item quantities and remove items. If you're using [inline checkout](/build/checkout/build-branded-inline-checkout), you can use the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method to build your own logic to let customers add, remove, and update items. Checkouts must have at least one item. You can't open a checkout without any items. ## Before you begin - [Include Paddle.js on your page](/paddle-js/about/include-paddlejs) and pass a [client-side token](/paddle-js/about/client-side-tokens). - To open a checkout, you'll need to [create products and prices](/build/products/create-products-prices) and [pass them to a checkout](/build/checkout/pass-update-checkout-items). To get a step-by-step overview of how to build a complete checkout, including passing checkout settings and prefilling properties, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) ## Pass items to a checkout Pass a list of `items` when opening a checkout to tell Paddle what this checkout is for. `items` should contain an object for each item, with the `price_id` for [a price entity](/api-reference/prices) and a `quantity`. Pass `items` to the [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) method or use the `data-items` [HTML data attribute](/paddle-js/about/html-data-attributes) on your checkout launcher element to do this. This example passes three prices using `Paddle.Checkout.open()`. The opened checkout is for these items. ```javascript Paddle.Checkout.open({ items: [ { priceId: 'pri_01gsz8x8sawmvhz1pv30nge1ke', quantity: 10 }, { priceId: 'pri_01gsz95g2zrkagg294kpstx54r', quantity: 1 }, { priceId: 'pri_01gsz98e27ak2tyhexptwc58yk', quantity: 1 } ] }); ``` To learn more, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) This example passes three prices using `data-items`. The opened checkout is for these items. Use single quotation marks around the value of `data-items`, and double quotation marks around the property names and string values inside it. ```html Buy Now ``` To learn more, see [HTML data attributes](/paddle-js/about/html-data-attributes) ## Update items on a checkout ### Overlay checkout [Overlay checkout](/concepts/sell/overlay-checkout) includes an items list along with the payment form. Customers can change quantities and remove items, except where there's only one item left on a checkout. They can't add new items. ### Inline checkout The [inline checkout](/concepts/sell/branded-integrated-inline-checkout) frame captures customer and payment information. You can build your own logic to show the items list and interact with it. To update items: 1. Pass an `eventCallback` to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) that listens for the [`checkout.loaded`](/paddle-js/events/checkout-loaded) event to display the on-page items list initially. Items are returned in `data.items` against the event. 2. Build logic to add or remove items and change quantities using the [`Paddle.checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method. You may like to update discount at this point, too. 3. Listen for [`checkout.updated`](/paddle-js/events/checkout-updated) in your `eventCallback` to update the on-page items list. For a complete example of an inline checkout that includes logic to dynamically update checkout items, see [Build an inline checkout](/build/checkout/build-branded-inline-checkout) ## Prevent changes to items on a checkout ### Overlay checkout With [overlay checkout](/concepts/sell/overlay-checkout), there's no built-in option to stop customers from removing or changing quantities of items on a checkout. However, you can: - **Set quantity limits against a price.** You can set minimum and maximum quantities [when creating a price](/build/products/create-products-prices) to prevent customers from adding more or less of a price than they should. For example, a "Premium Support" addon might have a maximum quantity of `1`. Customers aren't able to change the quantity of this item on the checkout. - **Pass a billed transaction to Paddle.js.** [Create a billed transaction](/build/transactions/create-transaction) using the Paddle API, then [pass it to Paddle.js](/build/transactions/pass-transaction-checkout). Billed transactions can't be changed, so customers aren't able to change the quantity of this item on the checkout. - **Mark a transaction as `billed` once `ready`.** Paddle creates a related [transaction](/api-reference/transactions) for checkouts to handle calculations. You can use an `eventCallback` to listen for checkout events, then send a request to the Paddle API to mark a transaction as billed once it has the status of `ready`. The quantity stepper is presented, but the checkout shows an error if customers try to change the quantity. ### Inline checkout When using [inline checkout](/concepts/sell/branded-integrated-inline-checkout), the checkout frame doesn't include a breakdown of items or totals. It handles capturing customer and payment information, letting you show information about what a customer is buying on your page. When designing your inline checkout, we recommend building logic in your frontend if you need to prevent customers from removing or changing quantities --- # Revise customer details on a billed or completed transaction URL: https://developer.paddle.com/build/transactions/revise-transaction-customer-details Revise a transaction to update customer, address, and business information for a transaction that's billed or completed. Sometimes customers might want to update their details after they've [completed a checkout](/concepts/sell/self-serve-checkout), or after you've [issued an invoice](/build/invoices/create-issue-invoices). For example, they might want to add a tax number or fill out their address. You can revise a transaction to update some [customer](/api-reference/customers), [address](/api-reference/addresses), or [business](/api-reference/businesses) details for it. The existing [transaction entity](/api-reference/transactions) remains on your system unchanged for recordkeeping purposes. ## How it works Paddle automatically sends customers an email receipt when a transaction is completed, like when a customer completes checkout or a subscription renews. Email receipts include a PDF invoice that customers can retain for recordkeeping. It's common for customers to want to update their details on the included PDF. For example, [Paddle Checkout](/concepts/sell/self-serve-checkout) only requires that customers enter their country (and in some cases their ZIP code) to keep the checkout journey short. In this case, customers might want to populate the other fields that are part of their address, like their street address and state. As billed and completed transactions are financial records, they can't be deleted or changed directly. Instead, you can revise customer, address, and business information for a transaction. The related customer information for that transaction is updated, but the existing [transaction entity](/api-reference/transactions) remains on your system unchanged for recordkeeping purposes. ### Related entities Completed and billed transactions must have a related [customer](/api-reference/customers) and [address entity](/api-reference/addresses), and may have a related [business entity](/api-reference/businesses). They're linked using the `customer_id`, `address_id`, and `business_id` fields. You can use [the `include` query parameter](/api-reference/about/include-entities) to get the related customer, address, and business entities for a transaction. When a customer, address, or business is set against a transaction, Paddle creates a relationship between the transaction and the related entity at that moment. This means that if you update a customer, address, or business after it's been set against a transaction, those changes aren't reflected when you use the `include` query parameter to get the related entities. You can revise a transaction to update the customer, address, and business entities for a transaction. When you revise a transaction, you're only updating the customer, address, and business information for that particular transaction. The related customer, address, and business entities aren't updated. ### Adjustments [Adjustments](/api-reference/adjustments) are another way you can describe updates to a transaction after it's been billed or completed. However, they're used to describe financial changes, like [refunding or crediting](/build/transactions/create-transaction-adjustments) some or all the items on a transaction. When you revise customer information for a transaction, Paddle may create an adjustment if there are financial changes. For example, if you add a valid tax or VAT number, Paddle automatically creates an adjustment to refund any tax where applicable. Describes customer information updates to a billed or completed transaction. For example, adding extra address details or adding a tax number. Revises customer, address, and business entities for the transaction. Customer receives a revised invoice PDF. Learn more: [Revise a transaction](/build/transactions/revise-transaction-customer-details) Describes financial updates to a billed or completed transaction. For example, refunding or crediting some or all line items for a transaction. Creates a new, separate adjustment entity related to the transaction. Customer receives a credit note PDF. Learn more: [Refund or credit a transaction](/build/transactions/create-transaction-adjustments) In both cases, the existing [transaction entity](/api-reference/transactions) remains on your system unchanged for recordkeeping purposes. You can't revise customer information for a transaction that has an adjustment. ## Before you begin To revise a transaction, you'll need to [get the transaction ID](/api-reference/transactions/list-transactions) for the transaction you want to revise. You can only revise transactions that are `billed` or `completed` and don't already have an adjustment. You can use the `status` query parameter when listing with the value `billed,completed` to get a list of billed and completed transactions. ## Revise a transaction Send a request to the `/transactions/{transaction_id}/revise` endpoint to revise a transaction. In your request, include only the fields you want to revise — you can update customer name, business name and `tax_identifier`, and address details (except country). You can't remove a valid tax or VAT number, only replace it. Transactions can only be revised once. Include all the information you want to update in your request. ### Get a transaction including revised information Send a request to the `/transactions/{transaction_id}` endpoint with `include=customer,address,business` to fetch the revised entities and present them in a billing screen. Paddle automatically sends a copy of the revised invoice to customers. ## Update customer, business, and address entities When you revise customer information for a transaction, only the customer information for this transaction is updated. The related customer, address, and business entities aren't updated. Use [the dashboard or the Paddle API to update the customer, address, and business entities](/build/customers/create-update-customers) so that future transactions use the latest data. --- # Change collection mode for a transaction URL: https://developer.paddle.com/build/transactions/change-collection-mode-transaction Change collection mode to determine whether Paddle tries to collect payment for a transaction automatically using a saved payment method or sends an invoice that must be paid manually. All purchases are [transactions](/api-reference/transactions). They hold all the information about a customer purchase, including customer details, items, calculated tax and localized pricing, and payments. Change collection mode for a transaction to determine whether Paddle tries to collect automatically using a saved payment method, or sends an invoice to a customer that they must pay manually. ## How it works You can create two kinds of [transactions](/api-reference/transactions): - **Automatically-collected transactions** Paddle collects using a saved payment method. If no payment method is saved, customers must enter one using [Paddle Checkout](/concepts/sell/self-serve-checkout). Typically part of a self-service workflow. - **Manually-collected transactions** Paddle collects by sending an invoice that the customer must pay by bank transfer or using Paddle Checkout. Typically part of a [sales-assisted invoicing workflow](/build/invoices/create-issue-invoices). Before a transaction is billed, you can switch between automatic and manual collection modes. This means that you can do things like: - Move higher-dollar deals to manually collected, [sending invoices](/build/invoices/create-issue-invoices) to customers that they can pay by bank transfer. - Automatically collect for smaller amounts for sales-assisted customers, like changes to seats or addons mid-billing cycle. You can change a transaction collection mode for `draft` and `ready` transactions. [Subscriptions](/api-reference/subscriptions) also have a `collection_mode` field. Change the collection mode against a subscription to determine the collection mode for transactions created from that subscription. ## Before you begin To change collection mode for a transaction, you'll need to [get the transaction ID](/api-reference/transactions/list-transactions) for the transaction you want to change. You can only change collection mode for transactions that are `draft` or `ready`. You can use the `status` query parameter when listing with the value `draft,ready` to get a list of draft and ready transactions. You can't change collection mode for a transaction that's `billed` or `completed`: - Billed transactions have been marked as finalized, so they're considered financial records. [Cancel a billed transaction](/build/invoices/create-issue-invoices) to say that it's no longer needed, then create another. - Completed transactions have payments against them, so there's no need to change their collection mode. If you've not yet created a transaction, you can set a collection mode when creating a transaction. To learn more, see: - [Create an automatically-collected transaction](/build/transactions/create-transaction) - [Create and issue an invoice (manually-collected transaction)](/build/invoices/create-issue-invoices) ## Change from automatic to manual Change an automatically-collected transaction to a manually-collected transaction when you want to send an invoice to customers. Collection for payment happens manually, which means customers must pay by bank transfer or Paddle Checkout. Paddle doesn't automatically collect for the balance. You'll need to [mark the transaction as `billed` to issue your invoice](/build/invoices/create-issue-invoices). Paddle assigns an invoice number and sends an invoice document. Send a request to the `/transactions/{transaction_id}` endpoint to update the transaction. In your request, set `collection_mode` to `manual` and include a `billing_details` object. Billing details must include `payment_terms`, but you may omit other fields. Optionally include `enable_checkout` (set to `true` to allow payment via Paddle Checkout; defaults to `false`), `purchase_order_number`, and `additional_information`. This example changes the collection mode for an automatically-billed transaction to `manual`. The `billing_details.enable_checkout` field is omitted, which means that this transaction can't be paid using Paddle Checkout. Only bank transfers are accepted. If successful, Paddle responds with a copy of the updated transaction entity. This example changes the collection mode for an automatically-billed transaction to `manual`. `billing_details.enable_checkout` is included and set to `true`, which means that you can [pass this transaction to a checkout](/build/transactions/pass-transaction-checkout) to collect for it. If successful, Paddle responds with a copy of the updated transaction entity. `checkout.url` is included in the response, which you can send to customers to open a checkout to capture payment details for this transaction. You can also [pass a transaction ID to a checkout](/build/transactions/pass-transaction-checkout) using Paddle.js to collect for it. ## Change from manual to automatic Change a manually-collected transaction to an automatically-collected transaction when you want to collect using a saved payment method. If no payment method is saved, customers must enter one using Paddle Checkout. Send a request to the `/transactions/{transaction_id}` endpoint to update the transaction. In your request, set `collection_mode` to `automatic`. Automatically-collected transactions don't need the `billing_details` object — Paddle automatically sets this to `null` when you change collection mode to automatic. If successful, Paddle responds with a copy of the updated transaction entity. `billing_details` is automatically set to `null`. --- # Prefill checkout properties URL: https://developer.paddle.com/build/checkout/prefill-checkout-properties Prefill checkout fields to save customers time and increase checkout conversion You can prefill properties on a checkout for a smoother checkout experience for customers. You might do this when: - You capture some information about a prospect on the page before they interact with your checkout. - You've built an integration with a CRM solution that passes email or other information as parameters on signup links. - You're working with a logged-in customer, presenting them with upgrade options. ## How it works [Paddle Checkout](/concepts/sell/self-serve-checkout) is optimized for conversion, asking customers for: - Email address - Country - ZIP/postal code, or region ([only in some markets](/concepts/sell/supported-countries-locales)) - Payment details Customers can also add a discount and information about their business, like a VAT or tax number. You can pass data to a checkout to prefill properties, reducing purchase friction for customers and increasing conversion. You can prefill all customer details. You can't prefill payment details, but you can [present saved payment methods](/build/checkout/saved-payment-methods) for returning customers. Prefilling works with both [overlay checkout](/build/checkout/build-overlay-checkout) and [inline checkout](/build/checkout/build-branded-inline-checkout). You can use [HTML data attributes](/paddle-js/about/html-data-attributes) or [JavaScript properties](/paddle-js/methods/paddle-checkout-open). ## Before you begin You'll need to [include Paddle.js on your page](/paddle-js/about/include-paddlejs) and pass a [client-side token](/paddle-js/about/client-side-tokens). To open a checkout, you'll need to [create products and prices](/build/products/create-products-prices) and [pass them to a checkout](/build/checkout/pass-update-checkout-items). To get a step-by-step overview of how to build a complete checkout, including passing checkout settings and prefilling properties, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) ## Pass customer or business information In this example, there's a signup button that includes an email address field. The page has a country selector. You could use HTML attributes or JavaScript properties to prefill this information in checkout: | # | Description | HTML attribute | JavaScript property | |:---:|-------------------|--------------------------------------|--------------------------------| | `1` | Country | `data-customer-address-country-code` | `customer.address.countryCode` | | `2` | Email address | `data-customer-email` | `customer.email` | Pass parameters to the `Paddle.Checkout.open()` method to prefill those values on a checkout. ```javascript title="" highlightLines="[15,17]" var itemsList = [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 }, { priceId: 'pri_01gm82kny0ad1tk358gxmsq87m', quantity: 1 } ]; Paddle.Checkout.open({ items: itemsList, customer: { email: "jo@example.com", address: { countryCode: "US", postalCode: "10021", region: "New York", city: "New York", firstLine: "4050 Jefferson Plaza, 41st Floor" }, business: { name: "ChatApp Inc.", taxIdentifier: "555952383" } } }); ``` For a full list of fields that you can prefill, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) You can update properties on an open checkout using the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method. Add data attributes to your checkout launcher to prefill those values on a checkout. ```html title="Checkout launcher element" highlightLines="5-6" Buy Now ``` For a full list of fields you can prefill, see [HTML data attributes](/paddle-js/about/html-data-attributes) ## Pass customer, address, and business IDs Instead of passing customer email, address, and business information, you can pass an existing customer ID, address ID, or business ID. You might do this if you're working with a logged-in customer who's looking to upgrade or purchase another subscription, or if you have a CRM integration that creates entities in Paddle for prospects. Customer ID, address ID, and business ID replace other customer, address, and business fields. For example, you should pass either customer ID or customer email — not both. Pass parameters to the `Paddle.Checkout.open()` method to prefill those values on a checkout. ```javascript title="" highlightLines="[20,21,22,23,24,25,26,27,28]" var itemsList = [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 }, { priceId: 'pri_01gm82kny0ad1tk358gxmsq87m', quantity: 1 } ]; Paddle.Checkout.open({ settings: { displayMode: "overlay", theme: "light", locale: "en", allowLogout: false }, items: itemsList, customer: { id: "ctm_01gm82kny0ad1tk358gxmsq87m", address: { id: "add_01gm82v81g69n9hdb0v9sw6j40" }, business: { id: "biz_01gnymqsj1etmestb4yhemdavm" } } }); ``` For a full list of fields that you can prefill, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) You can update properties on an open checkout using the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method. Add data attributes to your checkout launcher to prefill those values on a checkout. ```html title="Checkout launcher element" highlightLines="5-7" Buy Now ``` For a full list of fields that you can prefill, see [HTML data attributes](/paddle-js/about/html-data-attributes) ## Apply a discount You can pass the Paddle ID of a discount entity or its discount code to a checkout to automatically apply it — no need for customers to enter a code. `enabled_for_checkout` must be `true` against [the discount entity](/api-reference/discounts) to apply it to a checkout. Pass parameters to the `Paddle.Checkout.open()` method to prefill those values on a checkout. ```javascript title="" highlightLines="[6]" Paddle.Checkout.open({ settings: { theme: "light", locale: "en" }, discountId: "dsc_01gp0ynsntfpyw2spd2md1wqx1", items: [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 }, { priceId: 'pri_01gm82kny0ad1tk358gxmsq87m', quantity: 1 }, { priceId: 'pri_01gm82v81g69n9hdb0v9sw6j40', quantity: 1 } ] }); ``` To learn more, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) You can update properties on an open checkout using the [`Paddle.Checkout.updateCheckout()`](/paddle-js/methods/paddle-checkout-updatecheckout) method. Add data attributes to your checkout launcher to prefill those values on a checkout. ```html title="Checkout launcher element" highlightLines="20" Buy now ``` To learn more, see [HTML data attributes](/paddle-js/about/html-data-attributes) ## Build a one-page checkout ### One-page checkout experience Pass `variant` with the value `one-page` as [a checkout setting](/build/checkout/set-up-checkout-default-settings) to present customers with a one-page checkout experience. Paddle Checkout collects customer information and payment details on the same page. ```javascript title="" highlightLines="[16]" var itemsList = [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 }, { priceId: 'pri_01gm82kny0ad1tk358gxmsq87m', quantity: 1 } ]; Paddle.Checkout.open({ settings: { displayMode: "overlay", variant: "one-page" }, items: itemsList, }); ``` ### Multi-page checkout If you prefer the multi-page checkout experience, Paddle skips the first page of checkout when all required fields are prefilled. This means customers land on a screen where all they need to do is enter their payment details. ![Illustration of an overlay checkout. The payment method form is open, with buttons for Apple Pay and PayPal along with the card details form underneath. The items list shows one item for 'Professional plan', with tax and totals underneath. The total overall is $3600, displayed at the top-left of the checkout.'](/src/assets/images/tmp-build/checkout-overlay-checkout-hero-20230831.svg) To jump to the second page on the multi-page inline checkout, prefill either required properties or Paddle IDs: | Description | HTML attribute | JavaScript property | |--------------------------------------------------------------------------------------|--------------------------------------|--------------------------------| | Country | `data-customer-address-country-code` | `customer.address.countryCode` | | Email address | `data-customer-email` | `customer.email` | | ZIP/postal code ([only where required](/concepts/sell/supported-countries-locales)) | `data-customer-address-postal-code` | `customer.address.postalCode` | | Region ([only for United Arab Emirates](/concepts/sell/supported-countries-locales)) | `data-customer-address-region` | `customer.address.region` | | Description | HTML attribute | JavaScript property | |---------------------------------------------------------------------------------------------------------------|----------------------------|-----------------------| | Customer ID | `data-customer-id` | `customer.id` | | Address ID, with valid ZIP/postal code or region [where required](/concepts/sell/supported-countries-locales) | `data-customer-address-id` | `customer.address.id` | --- # Update payment details URL: https://developer.paddle.com/build/subscriptions/update-payment-details Build a workflow for updating customer payment details for a subscription using the Paddle API and Paddle.js. It's good practice to give customers a way to change the payment method that they use to pay for future subscription renewals and charges. It's especially important where subscriptions are past due, meaning customers have an outstanding payment. Payment methods can be updated for automatically-collected subscriptions that are active or past due. This guide walks through updating details for an existing subscription. To present saved payment methods at checkout for new purchases, see [Present saved payment methods at checkout](/build/checkout/saved-payment-methods) ![Screenshot of an overlay checkout for an update payment method transaction.](/src/assets/images/tmp-build/payment-method-update-20241217.svg) ## How it works When payment fails for an automatically-collected [subscription](/api-reference/subscriptions), the subscription status changes to `past_due`. Paddle works to automatically recover the payment for you by automatically retrying the payment method associated with that subscription, using algorithms to retry payments at the best time for success. [Turn on Payment Recovery](/build/retain), part of Paddle Retain, to get more comprehensive payment recovery and control over the experience, including payment reminders by email, in-app, and by SMS. You can build a workflow to let a customer update their payment details using [Paddle Checkout](/concepts/sell/self-serve-checkout), which handles securely capturing card details or payment using [another payment method](/concepts/payment-methods). To open Paddle Checkout for an existing subscription, you need a transaction for that subscription. You can use [the get a transaction to update payment method operation](/api-reference/subscriptions/get-subscription-update-payment-method-transaction) to get a transaction that you can [pass to Paddle.js](/build/transactions/pass-transaction-checkout) to open a Paddle Checkout for it. The returned [transaction](/api-reference/transactions) depends on the `status` of the related subscription: ![Overlay checkout for an update payment method transaction. The subscription is past due, and the customer is charged for the past due payment.](/src/assets/images/tmp-build/past-due-update-20241217.svg) When the subscription status is `past_due`, the last `past_due` transaction is returned. Displays the items and totals for the overdue transaction, so that customers know they'll be charged when they update their details. Includes an "Update payment method" button letting customers update payment details and pay the overdue amount. ![Overlay checkout for an update payment method transaction. The subscription is active and the customer is charged for $0.](/src/assets/images/tmp-build/zero-dollar-update-20241217.svg) When the subscription status is `active`, Paddle creates a zero-value transaction for the subscription's items. Displays the subscription's items and shows a message indicating the customer is updating their payment details, with no charge due. Includes an "Update payment method" button letting customers update their details without making a payment. When the checkout for the returned transaction completes, Paddle saves the updated payment details and uses them for future renewals and charges. ## Before you begin To update payment details for a subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `active,past_due` to get a list of active and past due subscriptions. To pass a payment method update transaction to a checkout, you'll need [a page that includes Paddle.js](/paddle-js/about/include-paddlejs). This is typically [your default payment link](/build/transactions/default-payment-link). If you haven't already, you'll need to: - Add [a default payment link](/build/transactions/default-payment-link) to your checkout under **Paddle > Checkout > Checkout settings > Default payment link**. - Get your default payment link [domain approved](https://www.paddle.com/help/start/account-verification/what-is-domain-verification) if you're working with the live environment. ## Use the portal The quickest way to let customers update payment details is to link them to [the customer portal](/concepts/sell/customer-portal). The portal is hosted by Paddle and handles the checkout flow for capturing card details (or other payment methods) out of the box, so you don't need to build your own UI with Paddle.js. When customers update their payment method using the portal, Paddle saves the new details and uses them for future renewals and charges. You can link customers to the portal in two ways: - **Customer portal session** Generates an authenticated, deep link straight to the payment method update page for a specific subscription. Customers don't need to sign in to the portal. - **Subscription management URL** A pre-generated link returned on the subscription entity. Quickest to integrate, but customers will need to sign in to the portal using their email address. ### With a customer portal session Generate a customer portal session for the customer and pass the `subscription_ids` of the subscriptions you want to create deep links for. Paddle returns an authenticated `update_subscription_payment_method` link for each subscription that you can present in your app. Use a customer portal session when customers are already signed in to your app, so they don't have to sign in to the portal again. For more information, see [Use customer portal links in your app](/build/customers/integrate-customer-portal). Customer portal sessions are temporary and shouldn't be cached. Create a new customer portal session each time you want to generate authenticated links. ### With a subscription management URL Send a request to the `/subscriptions/{subscription_id}` endpoint to get the subscription, then return `management_urls.update_payment_method` to the customer. When customers click this link, they're taken to the customer portal where they can sign in with their email address and update their payment method. For security, subscription management URLs include a temporary `token` parameter. Don't store these URLs — they expire. The `management_urls` object isn't returned in events for this reason, too. ## Use the API If you want to embed the payment method update flow into your own app rather than handing customers off to the portal, you can build a workflow using [the Paddle API](/api-reference) and [Paddle.js](/paddle-js). Use this approach when you want full control over the look and feel of the update flow, like presenting [an inline checkout](/concepts/sell/branded-integrated-inline-checkout) embedded in your app. Build a payment method update workflow using the API in two steps: 1. **Get a payment method update transaction** Use the Paddle API to get a transaction for updating the payment method. 2. **Pass a transaction to a checkout** Pass the transaction to Paddle.js to open a checkout where the customer enters their new payment details. ### Get a payment method update transaction Send a request to the `/subscriptions/{subscription_id}/update-payment-method-transaction` endpoint to get a transaction for updating the payment method. The transaction Paddle returns depends on the subscription status: When the related subscription is `past_due`, this operation returns the last `past_due` transaction for the subscription. If successful, Paddle returns the last `past_due` transaction for the subscription. When the related subscription is `active`, this operation creates a new zero-value transaction for the subscription. Paddle creates this transaction so you can pass it to Paddle.js to open a checkout to update payment details — no charge is due, and no change is made to the subscription. If successful, Paddle returns the new zero value transaction for this subscription. Its origin is `subscription_payment_method_change`. All items and totals on the transaction are zero. ### Pass a transaction to a checkout Once you have a payment method update transaction, pass it to [Paddle.js](/paddle-js) to open a checkout for it. To do this, you can either: - Use the `checkout.url` field in the transaction response to automatically open a checkout for the transaction using [your default payment link](/build/transactions/default-payment-link). - Extract the `id` and pass to Paddle.js to open a checkout. To learn more, see [Pass a transaction to a checkout](/build/transactions/pass-transaction-checkout) #### Show on-page information [Overlay checkout](/concepts/sell/overlay-checkout) includes items, totals, and a message to let customers know what the checkout is for. [Inline checkout](/concepts/sell/branded-integrated-inline-checkout) doesn't include items or totals. It's designed to capture payment information, letting you embed information about the transaction on your page. You might like to build your own logic to display information about this transaction. Pass an `eventCallback` to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) to listen for the [`checkout.loaded`](/paddle-js/events/checkout-loaded) event, then update on-page elements based on the event emitted. - **For past due subscriptions, `data.status` is `past_due`.** You should include items and totals, and may like to show a message to let customers know this checkout is to pay for a past due payment and update the payment method on file. - **For active subscriptions, `data.totals.subtotal` is `0`.** You should show a message to let customers know this checkout is to update the payment method on file. ## Events For a full list of events that occur when a payment method is updated, see [Payment method update](/build/subscriptions/update-payment-details) --- # Handle checkout success URL: https://developer.paddle.com/build/checkout/handle-success-post-checkout Redirect to a success page or create custom logic that runs when checkout completes. Then, provision your app. Paddle Checkout includes a final screen that lets customers know their purchase was successful. You can redirect to your own page or build a more advanced success workflow using [Paddle.js](/paddle-js). ## How it works When a customer successfully pays for items on a checkout, Paddle Checkout lets them know that their purchase was successful and sends an email with details of the order. If you like, you can redirect to your own success page when checkout completes successfully. You might like to include tips and tricks for getting started, or point customers towards a complementary addon like training or implementation. For more advanced success workflows, you can pass an `eventCallback` for [`checkout.completed`](/paddle-js/events/checkout-completed) to [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize). This lets you run your own JavaScript function when checkout completes. You might use this as part of [an inline checkout](/build/checkout/build-branded-inline-checkout) workflow to redirect to a new page or change elements on the checkout page to show order information. If a checkout is for recurring products, Paddle automatically creates a new [subscription](/api-reference/subscriptions) for the customer for the items that they purchased. You should [provision your app](/build/subscriptions/provision-access-webhooks) at this point to make sure the customer can access the products they paid for. ## Before you begin You'll need to [include Paddle.js on your page](/paddle-js/about/include-paddlejs) and pass a [client-side token](/paddle-js/about/client-side-tokens). To open a checkout, you'll need to [create products and prices](/build/products/create-products-prices) and [pass them to a checkout](/build/checkout/pass-update-checkout-items). To get a step-by-step overview of how to build a complete checkout, including passing checkout settings and prefilling properties, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) ## Write an event callback [Paddle.js emits events](/paddle-js/events) for key actions as a customer moves through checkout. It emits [a `checkout.completed` event](/paddle-js/events/checkout-completed) when a customer successfully pays for items on a checkout. You can pass an `eventCallback` to `Paddle.Initialize()` to call a function when `checkout.completed` is emitted. This example logs the `checkout.completed` event emitted by Paddle.js to console. ```javascript highlightLines="3-7" Paddle.Initialize({ token: 'live_7d279f61a3499fed520f7cd8c08', // replace with a client-side token eventCallback: function(data) { if (data.name == "checkout.completed") { console.log(data); } } }); ``` ## Redirect to a success page You can redirect customers to your own success page by passing a property to a checkout when opening it. Use the `data-success-url` [HTML data attribute](/paddle-js/about/html-data-attributes) on your checkout launcher element, or pass `settings.successUrl` to the [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) or [`Paddle.Initialize()`](/paddle-js/methods/paddle-initialize) methods do this. This example sets the redirect page to `https://paddle.com/thankyou` using `Paddle.Checkout.open()`. Use `Paddle.Initialize()` to [set a default](/build/checkout/set-up-checkout-default-settings) thank you page for all checkouts opened on a page. ```javascript highlightLines="6" Paddle.Checkout.open({ settings: { displayMode: "overlay", theme: "light", locale: "en", successUrl: "https://paddle.com/thankyou" }, items: [ { priceId: 'pri_01gs59hve0hrz6nyybj56z04eq', quantity: 1 }, { priceId: 'pri_01gs59p7rcxmzab2dm3gfqq00a', quantity: 1 } ] }); ``` To learn more, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) This example sets the redirect page to `https://paddle.com/thankyou` using `data-success-url`. ```html highlightLines="7" Buy Now ``` To learn more, see [HTML data attribute](/paddle-js/about/html-data-attributes) ## Provision your app When a customer completes checkout for recurring items, Paddle automatically creates [a related subscription](/api-reference/subscriptions). At this point, you should provision your app. Provisioning is how you grant customers access to your app, as well as determining which features they should have access to. To learn more, see [Handle provisioning and fulfillment](/build/subscriptions/provision-access-webhooks) --- # Pause a subscription URL: https://developer.paddle.com/build/subscriptions/pause-subscriptions Pause subscriptions when customers want to take a break and come back later. Offering the option to pause a subscription can lower voluntary churn and increase customer LTV. Pause subscriptions to stop billing for them temporarily. Paddle doesn't bill for paused subscriptions until they're resumed. You may wish to give customers some level of access to your software while paused to maintain a relationship and encourage them to return. For example, you might let customers download reports or access existing data, but restrict their access to create records. If a customer doesn't want to use your software at all, [cancel instead](/build/subscriptions/cancel-subscriptions). You can't reinstate a canceled subscription. Unlike [cancel](/build/subscriptions/cancel-subscriptions) and [update payment details](/build/subscriptions/update-payment-details), pause isn't supported by [the customer portal](/concepts/sell/customer-portal). Customers can't self-serve a pause — you'll need to build a workflow in your app using the API or pause from the Paddle dashboard. ## How it works ### Pause When you pause a [subscription](/api-reference/subscriptions), its `status` is set to `paused` and Paddle doesn't create any [transactions](/api-reference/transactions) or collect payment for it. You should [provision your app](/build/subscriptions/provision-access-webhooks) so customers don't have access, or have limited access, while paused. When sending a request to pause, you can tell Paddle to: - **Pause at the end of the billing period** Paddle creates a scheduled change to say the subscription should be paused on the next billing date. The subscription remains `active` until the next billing date, when the subscription status changes to `paused`. - **Pause immediately** Paddle pauses the subscription right away. Its status changes to `paused`. To avoid charging for overlapping billing periods, any past due transactions for subscription renewals, where the transaction `origin` is `subscription_recurring`, are automatically canceled. If you've [made changes to a subscription](/build/subscriptions/replace-products-prices-upgrade-downgrade) or [billed for one-time charges](/build/subscriptions/bill-add-one-time-charge) and set them to be charged [on the next billing period](/concepts/subscriptions/proration): - When changes result in a credit, these are automatically forgiven. - When changes result in a charge, these are applied to the transaction created on resume. ### Resume Subscriptions remain paused until they're resumed. Pauses can be open-ended, or you can set a resume date. We recommend giving customers a set of pause duration options in your frontend — for example, 30 days, 60 days, or 90 days. You can resume a paused subscription at any time, even if there's a resume date already set, by sending a request to resume. It's good practice to make it as easy as possible for customers to resume their subscription, so we recommend building a way for self-serve customers to reactivate their account. When sending a request to resume, you can tell Paddle to: - **Start a new billing period** Paddle starts a new billing period for the subscription. The `current_billing_period.starts_at` is set to the date and time that the subscription is resumed, and Paddle creates a transaction to collect for the new billing period immediately. - **Continue the existing billing period** Paddle checks the end date of the existing billing period. If the resume date is within the existing billing period, Paddle continues the existing billing period. The `current_billing_period` dates aren't changed, and there's no immediate charge. ## Before you begin To pause a subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `active` to get a list of active subscriptions. ## Pause a subscription 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to pause. 2. Find the subscription under the **Subscriptions** heading and click it. 3. From the subscription overview page, click Pause subscription 4. Use the **When do you want to pause the subscription?** to choose whether to pause now or at the end of the billing period. 5. To resume a subscription on a particular date, toggle **Automatically resume** on, then set a date and time. Toggle off to pause indefinitely. 6. Click Pause subscription to confirm. 7. If you chose to pause now, click Pause subscription again on the confirmation box. ![Illustration of a customer page with a list of subscriptions.](/src/assets/images/tmp-build/customer-subscriptions-20241114.svg) ![Illustration of the pause subscription drawer, showing a subscription that's set to pause immediately for an indefinite amount of time.](/src/assets/images/tmp-build/pause-subscription-drawer-20241115.svg) Send a request to the `/subscriptions/{subscription_id}/pause` endpoint to pause a subscription. In your request, include `effective_from` to tell Paddle when to pause (`next_billing_period` or `immediately`). Include `resume_at` (RFC 3339 timestamp) to set a scheduled resume date — omit for an indefinite pause. Include `on_resume` to control charging on resume — defaults to `start_new_billing_period` (customer is charged immediately for the new billing period). Paddle creates a scheduled change with an `effective_at` date matching the subscription's `next_billed_at` date. The subscription `status` changes to `paused` right away. Paddle creates a `scheduled_change` to resume with an `effective_at` matching the `resume_at` date you sent. ## Remove a scheduled change You can stop a pause from going through at the end of the billing period by removing the scheduled change. You might also do this to remove a scheduled resume from a paused subscription, so it's paused indefinitely until you resume. 1. Go to **Paddle > Customers**, and find the customer whose subscription you no longer want to pause. 2. Find the subscription under the **Subscriptions** heading and click it. 3. From the subscription overview page, click Don't pause 4. Click Don't pause on the confirmation box. ![Illustration of a subscription page with a banner saying that a subscription is scheduled to pause.](/src/assets/images/tmp-build/paused-subscription-20241115.svg) Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, set `scheduled_change` to `null` to remove the scheduled pause or resume. ## Set or change a resume date You can change when a subscription is set to resume, or set a resume date where a subscription is paused indefinitely. To change the future resume date for an active subscription that's scheduled to pause, [remove the scheduled change](#remove-a-scheduled-change) then [send a request to pause with a resume date](#pause-a-subscription). 1. Go to **Paddle > Customers**, and find the customer whose paused subscription you want to set or change the resume date for. 2. Find the subscription under the **Subscriptions** heading and click it. 3. From the subscription overview page, click Set resume date or Edit resume date 4. Choose a new date and time under the **Automatically resume** toggle. Toggle off to pause indefinitely. 5. Click Save to confirm. ![Illustration of a subscription page with a banner saying that a subscription is scheduled to pause.](/src/assets/images/tmp-build/paused-subscription-scheduled-resume-20241115.svg) Send a request to the `/subscriptions/{subscription_id}/resume` endpoint to set or change the resume date for a paused subscription. In your request, include `effective_from` with either an RFC 3339 timestamp (to resume on a specific date) or `next_billing_period`. Include `on_resume` to control charging on resume — defaults to `start_new_billing_period` (customer is charged immediately for the new billing period). ## Resume a paused subscription Resume a subscription to start billing for it again. You should grant the customer access to your app once resumed. When resumed, Paddle bills for the subscription immediately. The subscription billing date is recalculated based on the resume date. To resume on a particular date, see [Set or change a resume date](#set-or-change-a-resume-date) 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to pause. 2. Find the subscription under the **Subscriptions** heading and click it. 3. From the subscription overview page, click Resume subscription 4. Click Resume subscription on the confirmation box. ![Illustration of a subscription page with a banner saying that a subscription is paused.](/src/assets/images/tmp-build/paused-subscription-resume-20241115.svg) Resume a paused subscription using the API in two steps: 1. **Preview charging** Preview what the customer will be charged when the subscription resumes. 2. **Resume subscription** Send a request to resume the subscription. ### Preview charging Send a request to the `/subscriptions/{subscription_id}` endpoint with `include=recurring_transaction_details` to get a preview of what the customer will be charged on resume. ### Resume subscription Send a request to the `/subscriptions/{subscription_id}/resume` endpoint with an empty request body to resume the subscription right away. Paddle creates a transaction for the new billing period. If automatically collected, Paddle attempts to collect immediately. If collection fails, the subscription becomes `past_due`. ### Update payment method When resumed, Paddle creates a transaction to collect for the new billing period. If automatically collected, Paddle automatically attempts to collect using a payment method on file immediately on resume. This may have expired or no longer be valid, especially when resuming after a long period of time. If collection fails, the subscription and related transaction become `past_due` and [events for subscription past due occur](/build/retain/configure-payment-recovery-dunning). As collection is attempted immediately, if a subscription becomes past due then we recommend [presenting a way for customers to update their payment method](/build/subscriptions/update-payment-details) as part of your resume workflow. ## Events For a full list of events that occur when a subscription is paused or resumed, see [Subscription pause or resume](/build/subscriptions/pause-subscriptions) --- # Refund or credit a transaction URL: https://developer.paddle.com/build/transactions/create-transaction-adjustments Create an adjustment to record a change to a billed or completed transaction, like a refund or credit. Most refunds for live accounts have to be approved by Paddle. If you need to change a billed or completed transaction, you can create an adjustment. Adjustments let you refund or credit a transaction after it's been billed or completed. Adjustments sit alongside transactions. The existing [transaction entity](/api-reference/transactions) remains on your system unchanged for recordkeeping purposes. ![Illustration of a credit note from Paddle](/src/assets/images/tmp-build/credit-notes-hero-20240912.svg) ## How it works Billed and completed transactions are financial records, so they can't be deleted or changed. This is especially important when working with [manually-collected transactions](/build/invoices/create-issue-invoices) because they're considered issued invoices. Paddle assigns them an invoice number and sends them to customers, so any financial adjustments must be correctly recorded. Use adjustments to refund or credit all or part of a transaction and its items. A transaction may have multiple adjustments where you've refunded or credited different items. Paddle automatically sends a credit note to customers as a PDF so they have a record of a refund or credit. You can also download credit notes from the Paddle dashboard or generate a URL to them using the API. ### Refunds Refunds let you return some or the total a transaction amount to customers. The money is returned to the original payment method that the customer used. To keep the platform safe for everyone, most refunds for live accounts require approval from Paddle. They're created with the status of `pending_approval`, before moving to `approved` or `rejected` once reviewed. For live accounts, Paddle automatically approves refunds when: - Your account has been through [Paddle account verification](https://www.paddle.com/help/start/account-verification) and is active. - The refund amount is less than or equal to 400 `USD`, or equivalent [in another currency](/concepts/sell/supported-currencies). - The refund amount is less than your balance. - The customer paid using a [payment method](/concepts/payment-methods) other than [bank transfer](/concepts/payment-methods/wire-transfer). For [sandbox accounts](/sdks/sandbox), Paddle automatically approves all refunds every ten minutes. ### Credits Credits let you give customers some or the total of a transaction amount as a credit. You can create credits for manually-collected transactions (invoices) to reduce the amount due on issued invoices. For example, if you [create and issue an invoice](/build/invoices/create-issue-invoices) then want to remove an item, you can create an adjustment for the item you want to remove. Paddle automatically applies the credit to the issued invoice, reducing the amount the customer owes. When you credit the full value of a transaction, it's marked as `completed`. It's no longer due. Credits don't need approval from Paddle. Credits in Paddle are always related to existing transactions. [They adjust an amount](/build/transactions/create-transaction-adjustments) that's been paid, or an amount that's due on an issued invoice. They're not promotional credits, which are credits given to customers for things like referral schemes or promotions. ### Chargebacks When paying by [card](/concepts/payment-methods/card) and some other kinds of [payment methods](/concepts/payment-methods), customers may dispute a charge with their payment method issuer. Issuers investigate disputes and may choose to reverse a charge. This is called a chargeback. Paddle automatically creates adjustments for chargeback events for you: - For some kinds of chargebacks, we get an early warning and create an adjustment with the type `chargeback_warning` for the disputed amount. The amount is refunded. - For chargebacks where we don't get an early warning, we create an adjustment with the type `chargeback` for the disputed amount. The amount is refunded. - The Paddle team contests chargebacks for you. Where a chargeback is contested successfully, Paddle creates an adjustment with the type `chargeback_reverse` to return the amount held. ### Revise customer information [Revising customer information for a transaction](/build/transactions/revise-transaction-customer-details) is another way you can describe updates to a transaction after it's been billed or completed. However, revising a transaction is for updating customer, address, and business information against a transaction. When you revise customer information for a transaction, Paddle may create an adjustment if there are financial changes. For example, if you add a valid tax or VAT number, Paddle automatically creates an adjustment to refund any tax where applicable. Describes customer information updates to a billed or completed transaction. For example, adding extra address details or adding a tax number. Revises customer, address, and business entities for the transaction. Customer receives a revised invoice PDF. Learn more: [Revise a transaction](/build/transactions/revise-transaction-customer-details) Describes financial updates to a billed or completed transaction. For example, refunding or crediting some or all line items for a transaction. Creates a new, separate adjustment entity related to the transaction. Customer receives a credit note PDF. Learn more: [Refund or credit a transaction](/build/transactions/create-transaction-adjustments) In both cases, the existing [transaction entity](/api-reference/transactions) remains on your system unchanged for recordkeeping purposes. ## Before you begin To create an adjustment, you'll need to [get the transaction ID](/api-reference/transactions/list-transactions) for the transaction you want to refund or credit. You can use the `status` query parameter when listing with the value `completed` to get a list of completed transactions for refunds, or `billed,past_due` to get billed and past due transactions for credits. ## Create a refund Create an adjustment with the `action` of `refund` to return some or the total a transaction amount to customers. 1. Go to **Paddle > Transactions**. 2. Use the search box and the **Filter by** drop-down to find the transaction you want to create a credit for. 3. Click into the transaction, then click Refund 4. Choose a reason for the refund from the drop-down. To pass your own value, use the API instead. 5. Review the items list and enter the amount to refund for each item in the **Refund amount** box, then click Continue 6. Review your refund, then click Request refund to confirm. ![Illustration of the new refund drawer in the Paddle dashboard.](/src/assets/images/tmp-build/new-refund-20241108.svg) Create a refund using the API in three steps: 1. **Get a transaction and extract items** Get the transaction you want to refund and extract line item IDs for partial refunds. 2. **Create adjustment** Send a request to create the refund adjustment. 3. **Handle refund status change** If you present refunds to customers in your app, handle refund approval or rejection. You can't create an adjustment for a transaction while it has an existing adjustment that's pending approval. Wait for the adjustment to become approved or rejected, then create your adjustment. ### Get a transaction and extract items Send a request to the `/transactions/{transaction_id}` endpoint to get the transaction you want to refund. Adjustments are for [transaction items](/api-reference/transactions). You can refund the total for a transaction, or just some of its line items. If you only want to refund some of the transaction items, extract the line item details from the transaction first. Transactions must be `completed` to create a refund for them. You can get completed transactions using [the list transactions operation](/api-reference/transactions/list-transactions), passing `completed` as a value to the `status` query parameter. For each item in `transaction.details.line_items[]`, extract `id` and `totals.total` — you'll need these when building a partial refund request. ### Create adjustment Send a request to the `/adjustments` endpoint to create the refund adjustment. In your request, include: - `type`: `full` to refund the complete transaction total, or `partial` to refund specific items. - `transaction_id`: Paddle ID for the transaction to refund. - `action: refund` to return the amount to the original payment method. - `reason`: why you're refunding — shown in the Paddle dashboard and kept for recordkeeping. For `partial` refunds, include an `items` array with `item_id` and `type` per item. For each item, use `type: full` to refund the whole item, or `type: partial` with an `amount` for a partial item refund. By default, `items[].amount` is inclusive of tax. Include `tax_mode: external` to say that amounts are exclusive of tax. ### Handle refund status change Most refunds for live accounts are created with the `status` of `pending_approval` until reviewed by Paddle, but [some are automatically approved](#refunds). For [sandbox accounts](/sdks/sandbox), Paddle automatically approves refunds every ten minutes. From `pending_approval`, adjustments move to either: - `approved` Refund approved by Paddle. The amount is refunded to the original payment method that the customer used. This may take a few days to process. - `rejected` Refund rejected by the Paddle team. Contact the Paddle seller support team if you'd like to understand more about why a refund was rejected. The [`adjustment.updated`](/webhooks/adjustments/adjustment-updated) event occurs when the status of an adjustment changes. Subscribe to this event to get notified when adjustments are approved or rejected. If you've built a billing information page in your app, you might like to update the status of the refund on this page. ## Create a credit Create an adjustment with the `action` of `credit` to give customers some or the total of a manually-collected transaction (invoice) amount as a credit. Create a credit using the API in two steps: 1. **Get a transaction and extract items** Get the transaction you want to credit and extract line item IDs for partial credits. 2. **Create adjustment** Send a request to create the credit adjustment. You can only create credits for manually-collected transactions (invoices). You can't create credits for automatically-collected transactions. ### Get a transaction and extract items Send a request to the `/transactions/{transaction_id}` endpoint to get the transaction you want to credit. Adjustments are for [transaction items](/api-reference/transactions). You can credit the total for a transaction, or just some of its line items. If you only want to credit some of the transaction items, extract the line item details from the transaction first. Transactions must be `billed` or `past_due` to create a credit for them. You can get billed and past due transactions using [the list transactions operation](/api-reference/transactions/list-transactions), passing `billed,past_due` as a value to the `status` query parameter. For each item in `transaction.details.line_items[]`, extract `id` and `totals.total` — you'll need these when building a partial credit request. ### Create adjustment Send a request to the `/adjustments` endpoint to create the credit adjustment. In your request, include: - `type`: `full` to credit the complete transaction total, or `partial` to credit specific items. - `transaction_id`: Paddle ID for the transaction to credit. - `action: credit` to give the amount as a credit to the customer. - `reason`: why you're crediting this amount. This is shown in the Paddle dashboard and kept for recordkeeping. For `partial` credits, include an `items` array with `item_id` and `type` per item. For each item, use `type: full` to credit the whole item, or `type: partial` with an `amount` for a partial item credit. ## Generate a credit note document Generate a credit note document as a PDF to give to a customer as a record of a refund or credit. 1. Go to **Paddle > Transactions**. 2. Use the search box and the **Filter by** drop-down to find a transaction with an adjustment that you'd like to download a credit note for. 3. Click into the transaction, then scroll to the credit section of the transaction page. 4. Click View credit note to open a PDF of the credit note. ![Illustration of a transaction in Paddle. It shows the credits section card. There's a button that says view credit note.](/src/assets/images/tmp-build/create-credit-note-20241108.svg) Send a request to the `/adjustments/{adjustment_id}/credit-note` endpoint to generate a URL to a credit note PDF for an adjustment. --- # Cancel a subscription URL: https://developer.paddle.com/build/subscriptions/cancel-subscriptions Cancel subscriptions when a customer no longer wants to use your software. Customers must sign up again if they wish to use your software in the future. Cancel subscriptions to stop billing for them permanently. Paddle stops billing customers indefinitely. You should restrict access to your app when a customer has canceled their subscription. If a customer just wants to stop using your software temporarily, [pause a subscription instead](/build/subscriptions/pause-subscriptions). Paddle supports subscriptions with multiple products. Customers might say they want to cancel when they want to remove items for an addon or users. To learn more, see [Add or remove products on a subscription](/build/subscriptions/add-remove-products-prices-addons) ## How it works When you cancel a [subscription](/api-reference/subscriptions), its `status` is set to `canceled`. Paddle stops billing for it, meaning no further [transactions](/api-reference/transactions) are created for the subscription. You should [provision your app](/build/subscriptions/bill-add-one-time-charge) so customers don't have access when canceled. For compliance reasons, subscription-related emails sent from Paddle to customers include a link to cancel. This is handled by Paddle — you don't need to build your own logic for this. When customers cancel using the link in the email from Paddle, their subscription remains active until the end of the current billing period. You can also cancel a subscription using the API. When sending a request to cancel, you can tell Paddle to: - **Cancel at the end of the billing period** Paddle creates a scheduled change to say the subscription should be canceled on the next billing date. The subscription remains `active` until the next billing date, when the subscription status changes to `canceled`. - **Cancel immediately** Paddle cancels the subscription right away. Its status changes to `canceled`. If you've [made changes to a subscription](/build/subscriptions/replace-products-prices-upgrade-downgrade) or [billed for one-time charges](/build/subscriptions/bill-add-one-time-charge) and set them to be charged [on the next billing period](/concepts/subscriptions/proration), these are automatically forgiven. Canceled subscriptions can't be reinstated. Create a new subscription for customers who have canceled if they want to return. ## Before you begin To cancel a subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `active,paused` to get a list of active and paused subscriptions. ## Use the portal The quickest way to let customers cancel a subscription is to link them to [the customer portal](/concepts/sell/customer-portal). The portal is hosted by Paddle and includes a cancellation flow out of the box, so you don't need to build your own UI. When customers cancel using the portal, Paddle creates a scheduled change against the subscription to cancel at the end of the current billing period. The subscription remains `active` until the next billing date, at which point its status changes to `canceled`. You can link customers to the portal in two ways: - **Customer portal session** Generates an authenticated, deep link straight to the cancellation page for a specific subscription. Customers don't need to sign in to the portal. - **Subscription management URL** A pre-generated link returned on the subscription entity. Quickest to integrate, but customers will need to sign in to the portal using their email address. ### With a customer portal session Generate a customer portal session for the customer and pass the `subscription_ids` of the subscriptions you want to create deep links for. Paddle returns an authenticated `cancel_subscription` link for each subscription that you can present in your app. Use a customer portal session when customers are already signed in to your app, so they don't have to sign in to the portal again. For more information, see [Use customer portal links in your app](/build/customers/integrate-customer-portal). Customer portal sessions are temporary and shouldn't be cached. Create a new customer portal session each time you want to generate authenticated links. ### With a subscription management URL Send a request to the `/subscriptions/{subscription_id}` endpoint to get the subscription, then return `management_urls.cancel` to the customer. When customers click this link, they're taken to the customer portal where they can sign in with their email address and confirm the cancellation. For security, subscription management URLs include a temporary `token` parameter. Don't store these URLs — they expire. The `management_urls` object isn't returned in events for this reason, too. ## Cancel a subscription 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to cancel. 2. Find the subscription under the Subscriptions heading and click it. 3. From the subscription overview page, choose Cancel subscription 4. Review, then choose Cancel subscription to confirm. Send a request to the `/subscriptions/{subscription_id}/cancel` endpoint to cancel a subscription. In your request, include `effective_from` to tell Paddle when to cancel the subscription — `next_billing_period` (default) or `immediately`. You can also send an empty request body to cancel on the next billing period. Paddle creates a scheduled change with an `effective_at` date matching the subscription's `next_billed_at` date. `next_billed_at` is set to `null`. The subscription `status` changes to `canceled` right away. ## Remove a scheduled change You can stop a cancellation from going through at the end of the billing period by removing the scheduled change. 1. Go to **Paddle > Customers**, and find the customer whose subscription you want to remove a scheduled cancel against. 2. Find the subscription under the Subscriptions heading and click it. 3. From the subscription overview page, choose Don't cancel Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, set `scheduled_change` to `null` to remove the scheduled cancellation. ## Reinstate a cancelled subscription Canceled subscriptions can't be reinstated. Create a new subscription for customers who have canceled if they want to return. To streamline this process, create a transaction with the same items and other information as on the previous subscription: 1. **Get the previous subscription** [List subscriptions](/api-reference/subscriptions/list-subscriptions) using the `customer_id` query parameter, passing the Paddle ID for the customer as the value. This returns a list of all subscriptions for this customer. Get the subscription that they previously canceled. 2. **Extract price IDs and quantities** Extract the price ID from the `price` object and the `quantity` for each item in the `items` array against the canceled subscription entity. You may also like to extract the `currency_code`, `address_id`, and `business_id` if they're going to be the same. 3. **Build a request to create a transaction** [Build a request](/build/transactions/create-transaction) with an `items` array with an object for each price that contains a price ID and a quantity, along with the customer ID and any other information you extracted from the canceled subscription. 4. **Create a transaction** Send a request to the [`/transactions` endpoint](/api-reference/transactions/create-transaction) to create a transaction. Paddle returns a new transaction for the customer, items, and other details you passed. 5. **Pass to Paddle Checkout** Collect payment and create a new subscription by getting the `checkout.url` against the created transaction and returning it to the customer, or [pass a transaction ID to Paddle.js](/build/transactions/pass-transaction-checkout) to open a checkout for the transaction you created. ## Events For a full list of events that occur when a subscription is canceled, see [Subscription cancellation](/build/subscriptions/cancel-subscriptions) --- # Present saved payment methods URL: https://developer.paddle.com/build/checkout/saved-payment-methods Let customers securely save payment methods when making a purchase and present saved payment methods to customers when they return. You can manage saved payment methods using the API. Paddle lets customers save their payment methods when purchasing one-time items and subscriptions using [Paddle Checkout](/concepts/sell/self-serve-checkout). Once saved, you can securely present returning customers with their saved payment methods in the future. This creates a friction-free checkout experience by removing the need to manually enter payment details each time. This guide walks through presenting customers with their saved payment methods when they're making a new purchase. To update payment details for an existing subscription, see [Update payment details](/build/subscriptions/update-payment-details) ![Illustration of a sample inline checkout. It has two one-time items on the right: a sack of gems and a health boost powerup. The total is $5.48. On the left, there two saved payment methods with radio buttons next to them. One is a Visa card ending 4242 and another is a Mastercard ending 1234. There are buttons to pay by card or pay another way.](/src/assets/images/tmp-build/illustration-checkout-saved-payment-methods-20240726.svg) ## How it works Customers can purchase one-time items and recurring subscriptions using [Paddle Checkout](/concepts/sell/self-serve-checkout) and choose to save their payment method when purchasing. Once saved, you can securely present customers with their saved payment methods when making purchases in the future by passing a customer authentication token to [Paddle.js](/paddle-js). ### Customer journey When purchasing one-time items and subscriptions, customers may check a box to save their payment method when completing payment. ![Illustration showing an inline checkout. Sample card details are filled out. There is a box that says: save my payment details for next time. This box is checked.](/src/assets/images/tmp-build/saved-payment-methods-step-1-20240726.svg) When customers come back in the future, you can securely present them with their saved payment methods to make checkout quick and easy. You can only present saved payment methods when using one-page checkout. ![Illustration showing an inline checkout. There are list of saved payment card presented, including a Visa card with the last four showing 4242. There is an option to use another card or pay another way.](/src/assets/images/tmp-build/saved-payment-methods-step-2-20240726.svg) Customers can view and delete their saved payment methods using [the customer portal](/concepts/sell/customer-portal). Paddle automatically includes a unique link in transaction receipt emails. ![Illustration showing a customer portal. There's a gray sidebar, with nav items represented with solid blocks. There are two cards listed on the right side: a Visa card ending 4242, a Mastercard ending 1111, and an American Express card ending 10005. There are buttons to delete the cards.](/src/assets/images/tmp-build/journey-customer-portal-payment-methods-20240911.svg) ### Authentication You can choose to present all compatible payment methods, or present a single payment method at checkout — useful for building workflows that let customers set a preferred payment method for particular purchases. To present customers with their saved payment methods, you must [generate a customer authentication token](/api-reference/customers/generate-customer-authentication-token) and pass it to Paddle.js when opening a checkout. Customer authentication tokens are important for security. They let you authenticate a customer, so Paddle.js can present their payment methods to them. They're unique and impossible to guess, restricted to a particular customer, and only valid for 30 minutes. They can only be generated with the Paddle API using a valid [API key](/api-reference/about/authentication). To avoid exposing your API key and other sensitive data, don't make requests to the Paddle API directly from your frontend. Build functionality into your backend to handle requests and serve just the information you need to your frontend. Entities in the API have an `Access-Control-Allow-Origin` header to block access from browsers. ### Payment method support Paddle creates a [saved payment method entity](/api-reference/payment-methods) for supported payment methods when customers opt to save them for future purchases. Check [the payment methods guides](/concepts/payment-methods) to learn which payment methods support being presented when saved. Keep in mind that some saved payment methods may not be compatible with a checkout when presented. For example, [PayPal](/concepts/payment-methods/paypal) isn't supported for all currencies and regions supported by Paddle. In this case, Paddle Checkout falls back to presenting customers with [all compatible payment options](/concepts/payment-methods). ### Allowed payment options You can pass an array of payment methods to Paddle.js to present only those payment options at checkout. For example, you can open checkouts that only present [PayPal](/concepts/payment-methods/paypal) as a payment option. Saved payment methods for a customer are considered their own payment option, with their own `saved_payment_methods` value. This means that if a customer has a saved payment method for PayPal, it's not presented if you open a checkout that's set to present only PayPal as a payment option. ### Subscriptions When a customer purchases a subscription, Paddle stores their payment method against that subscription to bill for renewals, [upgrades and downgrades](/build/subscriptions/replace-products-prices-upgrade-downgrade), and other charges related to that subscription. However, customers must explicitly opt in to save their payment method for future purchases. Without this opt-in, the payment method won't be saved as a [saved payment method entity](/api-reference/payment-methods) for you to present at checkout. This means a customer could have an active subscription with a stored payment method, but that same payment method won't appear as an option when they make new purchases unless they previously opted in to save it. Payment methods stored against subscriptions always appear in the [customer portal](/concepts/sell/customer-portal) regardless of whether the customer opted to save them for future purchases, letting customers manage their payment methods used for recurring billing. ## Before you begin - **Create products and prices**] Paddle Checkout works with products and prices to say what customers are purchasing, so you'll need to [create a product and at least one related price](/build/checkout/build-branded-inline-checkout) to pass to your checkout. - **Set your default payment link** Set [a payment link under](/build/transactions/default-payment-link) **Paddle > Checkout > Checkout settings > Default payment link**, then get it approved if you're working with the live environment. - **Use one-page checkout** You can only present saved payment methods when using one-page checkout. Multi-step checkouts don't support presenting saved payment methods. To get a step-by-step overview of how to build a complete checkout, including passing checkout settings and prefilling properties, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) ## Let customers save payment methods To give customers the option to save their payment method when purchasing, turn the option on in the Paddle dashboard. This option is off by default. 1. Go to **Paddle > Checkout > Checkout settings**. 2. On the General tab, check the **Allow buyers to opt in to save their payment methods for future purchases** box. 3. Click Save to apply. ![Graphic of the saved payment methods checkbox in the Paddle dashboard. Only the checkbox and label text is present, on a Paddle gradient background.](/src/assets/images/tmp-build/illustration-enable-saved-payment-methods-20240515.svg) ## Present all saved payment methods at checkout Present all compatible saved payment methods for a customer at checkout in three steps: 1. [**Generate a customer authentication token**](#generate-a-customer-authentication-token) Use the Paddle API to generate an authentication token for a customer. 2. [**Pass the customer authentication token to Paddle.js**](#pass-the-customer-authentication-token-to-paddlejs) Pass `customerAuthToken` to Paddle.js when opening a checkout, along with your items or a transaction. 3. [**Present saved payment methods alongside payment options**](#present-payment-methods-alongside-payment-options) Optionally pass `saved_payment_methods` as a value in the `allowedPaymentMethods` list to present saved payment methods when restricting a checkout to particular payment methods. ### Generate a customer authentication token Get the Paddle ID [for the customer](/api-reference/customers) that you want to present payment methods for, then use the API to generate an authentication token. For security, authentication tokens aren't stored and can't be retrieved later. They're valid for 30 minutes. ### Pass the customer authentication token to Paddle.js Take the `customer_auth_token` from the last step and pass it to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) as `customerAuthToken`. [You should pass `items`](/build/checkout/pass-update-checkout-items) or [a `transactionId` when opening a checkout](/build/transactions/pass-transaction-checkout) to tell Paddle what a checkout is for, as normal. When passing a `transactionId`, the customer entity against the transaction must match the customer that the `customerAuthToken` is for. This example opens an inline checkout for a one-time item. `customerAuthToken` is passed to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open), so the customer is presented with any payment methods they previously saved. ```javascript var itemsList = [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 } ]; Paddle.Checkout.open({ items: itemsList, customerAuthToken: "pca_01hwz42rfyaxw721bgkppp66gx_01h282ye3v2d9cmcm8dzpawrd0_otkqbvati3ryh2f6o7zdr6owjsdhkgmm", settings: { displayMode: "inline", frameTarget: "checkout-container", frameInitialHeight: "450", frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;", } }); ``` `allowLogout` is ignored if passed because you've authenticated the customer using the `customerAuthToken` parameter. ### Present saved payment methods alongside payment options You can pass an array of payment method types as a value for the `allowedPaymentMethods` parameter for [checkout settings](/build/checkout/set-up-checkout-default-settings) to determine which [payment options](/concepts/payment-methods) are presented to customers. Saved payment methods for a customer are considered a discrete payment option. This means that if a customer has a saved payment method for [PayPal](/concepts/payment-methods/paypal), it's not presented if you open a checkout that's set to present only PayPal as a payment option. To present a customer with all their saved payment methods when restricting a checkout to certain payment options, include `saved_payment_methods` in your `allowedPaymentMethods` array. This example shows opening a checkout that presents a customer the option to pay with PayPal or a saved payment method. ```javascript var itemsList = [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 } ]; Paddle.Checkout.open({ customerAuthToken: "pca_01hwz42rfyaxw721bgkppp66gx_01h282ye3v2d9cmcm8dzpawrd0_otkqbvati3ryh2f6o7zdr6owjsdhkgmm", settings: { displayMode: "inline", frameTarget: "checkout-container", frameInitialHeight: "450", frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;", allowedPaymentMethods: ["saved_payment_methods", "paypal"], items: itemsList } }); ``` ## Preselect a specific saved payment method Paddle creates [a saved payment method entity](/api-reference/payment-methods) for a customer when they choose to save a payment method. You can pass a saved payment method entity ID to Paddle.js to open a checkout for that payment method. Instead of seeing all their compatible saved payment methods, the customer is only presented with the saved payment method you passed. You can use this to build custom checkout workflows that let customers pick a payment method before launching Paddle Checkout, or to let customers set a preferred payment method for specific purchases. Open a checkout for a saved payment method in three steps: 1. [**Get a saved payment method ID**](#get-a-saved-payment-method-id) Use the Paddle API to get a saved payment method ID for a customer. 2. [**Generate a customer authentication token**](#generate-a-customer-authentication-token) Use the Paddle API to generate an authentication token for a customer. 3. [**Pass the saved payment method ID and customer authentication token to Paddle.js**](#pass-the-saved-payment-method-id-and-customer-authentication-token-to-paddlejs) Pass `savedPaymentMethodId` and `customerAuthToken` to Paddle.js when opening a checkout, along with your items or a transaction. ### Get a saved payment method ID Get the Paddle ID [for the customer](/api-reference/customers) that you want to get a payment method for. You might present this list to a customer on a page in their account settings screen to let them select a preferred payment method. Extract the `id` for the saved payment method you want to present at checkout — we'll pass this to Paddle.js in the next step. ### Generate a customer authentication token Use the API to generate an authentication token for a customer. Extract the `customer_auth_token` and save it for later — we'll pass this to Paddle.js in the next step. For security, authentication tokens aren't stored and can't be retrieved later. They're valid for 30 minutes. ### Pass the saved payment method ID and customer authentication token to Paddle.js Take the `id` for the saved payment method and the `customer_auth_token` from the last steps and pass them to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) as `savedPaymentMethodId` and `customerAuthToken`. [You should pass `items`](/build/checkout/pass-update-checkout-items) or [a `transactionId` when opening a checkout](/build/transactions/pass-transaction-checkout) to tell Paddle what a checkout is for, as normal. When passing a `transactionId`, the customer entity against the transaction must match the customer that the `customerAuthToken` is for. The address entity against the transaction is updated so that it matches the address for the saved payment method passed. This example opens an inline checkout for a one-time item. `customerAuthToken` and `savedPaymentMethodId` are passed to [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open), so the customer is presented with the option to pay with the passed saved payment method when they open checkout. ```javascript var itemsList = [ { priceId: 'pri_01gm81eqze2vmmvhpjg13bfeqg', quantity: 1 } ]; Paddle.Checkout.open({ customerAuthToken: "pca_01hwz42rfyaxw721bgkppp66gx_01h282ye3v2d9cmcm8dzpawrd0_otkqbvati3ryh2f6o7zdr6owjsdhkgmm", savedPaymentMethodId: "paymtd_01hs8zx6x377xfsfrt2bqsevbw", settings: { displayMode: "inline", frameTarget: "checkout-container", frameInitialHeight: "450", frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;", items: itemsList } }); ``` If a passed saved payment method isn't compatible with a checkout, Paddle Checkout presents the customer with other ways to pay. For example, PayPal isn't supported for all currencies and regions supported by Paddle. In this case, customers are presented with options that are compatible. ## Work with payment methods Let customers manage payment methods by: - **Linking to the customer portal** Customers can see and delete their subscription payment methods using [the customer portal](/concepts/sell/customer-portal), meaning you don't need to build any of your own logic. This includes all payment methods associated with their subscriptions, whether they opted to save them for future purchases or not. - **Building an integration using the Paddle API** You can use [the Paddle API](/api-reference/payment-methods) to build billing screens to let customers see and delete their saved payment methods. This only includes payment methods that customers opted to save for future purchases. --- # Work with custom data URL: https://developer.paddle.com/build/transactions/custom-data Include your own custom information when working with checkouts and some entities in the Paddle API. Great for storing metadata or other useful information when working with third-party solutions. Custom data lets you add your own key-value data to subscriptions, transactions, and some other entities in the Paddle API created using Paddle Checkout or the API. You can use custom data for things like: - Capturing UTM source or marketing campaign information - Storing metadata or other useful information for third-party integrations ## How it works You can add custom data to [transactions](/api-reference/transactions) and [subscriptions](/api-reference/transactions) using the API or [Paddle.js](/paddle-js). Both entities have a `custom_data` object against them, which accepts valid key-value JSON data. When working with a checkout, you can pass custom data in a similar way to [prefilling properties](/build/checkout/prefill-checkout-properties). Any custom data is held against the related transaction. Paddle shares custom data between transactions and subscriptions: - Paddle automatically creates a subscription for transactions with recurring prices on them. Where a transaction has custom data against it, that data is copied to the created subscription for your reference. - Where a subscription has custom data against it, that data is copied to any transactions created from it for things like renewals, upgrades and downgrades, and one-time charges. We recommend against nesting custom data. Though there's no limitations on having objects inside the `custom_data` object, you might find this information isn't displayed in the Paddle dashboard correctly. ## Before you begin You'll need to [include Paddle.js on your page](/paddle-js/about/include-paddlejs) and pass a [client-side token](/paddle-js/about/client-side-tokens). To open a checkout, you'll need to [create products and prices](/build/products/create-products-prices) and [pass them to a checkout](/build/checkout/pass-update-checkout-items). ## Pass custom data to checkout Pass custom data to a checkout using an HTML data attribute or JavaScript property. Pass `customData` to the `Paddle.Checkout.open()` method to add custom data to the created transaction. ```javascript title="Checkout open method" highlightLines="15-20" Paddle.Checkout.open({ settings: { theme: "light", }, items: [ { priceId: 'pri_01gs59hve0hrz6nyybj56z04eq', quantity: 1 }, { priceId: 'pri_01gs59p7rcxmzab2dm3gfqq00a', quantity: 1 } ], customData: { "utm_medium": "social", "utm_source": "linkedin", "utm_content": "launch-video", "integration_id": "AA-123" }, customer: { email: "weloveyourproduct@paddle.com", address: { countryCode: "US", } } }); ``` For a full list of data you can pass to a checkout, see [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) Add a `data-custom-data` HTML attribute to your checkout launcher to add custom data to the created transaction. ```html title="Checkout launcher element" highlightLines="5-10" Buy Now ``` For a full list of HTML data attributes, see [HTML data attributes](/paddle-js/about/html-data-attributes) ## Add custom data to a transaction You can add custom data when creating or updating a transaction. You might do this when setting up a transaction to pass to a checkout, or [when working with an invoice (a manually-collected transaction)](/build/invoices/create-issue-invoices). Send a request to the `/transactions` endpoint to create a transaction, or to `/transactions/{transaction_id}` to update an existing one. In your request, include `custom_data` as a JSON object. This example adds UTM information to a new automatically-collected transaction. ## Get custom data Once added to a transaction, you can see custom data: - In the Paddle dashboard against a transaction - Using the API by getting [a transaction entity](/api-reference/transactions) - In webhooks for [transaction events](/webhooks/transactions/transaction-created) If a transaction creates a new subscription, the custom data is copied to the new subscription. You can see it against the subscription in the Paddle dashboard or when working with [a subscription entity](/api-reference/subscriptions) using the API. 1. Go to **Paddle > Customers**, and find the customer who you created a transaction for. 2. Find the transaction under the Transactions heading and click it. 3. From the transaction overview page, click the box that contains the customer email and address. The box expands to show the full address and any custom data against the transaction. Send a request to the `/transactions/{transaction_id}` endpoint to get the transaction. Paddle responds with the complete transaction entity including `custom_data`. ## Update or remove custom data You can update or remove custom data against a transaction or subscription using the API. Transactions are financial records. You can't edit them if they're billed, canceled, or completed. Send a request to the `/transactions/{transaction_id}` endpoint, including `custom_data` as a JSON object. To remove custom data, set `custom_data` to `null`. Send a request to the `/subscriptions/{subscription_id}` endpoint, including `custom_data` as a JSON object. To remove custom data, set `custom_data` to `null`. --- # Brand inline checkout URL: https://developer.paddle.com/build/checkout/brand-customize-inline-checkout Use the Paddle dashboard to brand inline checkout so it fits seamlessly into your app. Inline checkout comes with over 50 styling options to let you create a checkout experience that's fully integrated with your app. You can change colors, borders, shadows, text, and other options directly from your Paddle dashboard — no engineering resource needed. Go to **Paddle > Checkout > Branded inline checkout** to get started. ## Attributes you can change You can change: - Checkout frame padding - Font - Text colors and sizes - Border colors and widths, and how rounded borders are - Hover states for buttons and links - Focus states for buttons and links - Positioning of labels for text boxes - Checkbox colors You can also change styles related to the checkout footer message. This unobtrusive message lets customers know that Paddle is the merchant of record for the transaction. ## What good looks like It's important that customers know who they're buying from, what they're buying, and how much they're paying. To build an inline checkout that's compliant and optimized for conversion, your implementation must include: 1. If recurring, how often it recurs and the total to pay on renewal. If a trial, how long the trial lasts. 2. A description of what's being purchased. 3. Transaction totals, including subtotal, total tax, and grand total. Be sure to include the currency too. 4. The full inline checkout frame, including the checkout footer that has information about Paddle, our terms of sale, and our privacy policy. 5. A link to your refund policy, if it differs from the Paddle.com standard refund policy. ## Before you begin You'll need to [include Paddle.js on your page](/paddle-js/about/include-paddlejs) and pass a [client-side token](/paddle-js/about/client-side-tokens). To open a checkout, you'll need to [create products and prices](/build/products/create-products-prices) and [pass them to a checkout](/build/checkout/pass-update-checkout-items). To get a step-by-step overview of how to build a complete checkout, including passing checkout settings and prefilling properties, see [Build an overlay checkout](/build/checkout/build-overlay-checkout) or [build an inline checkout](/build/checkout/build-branded-inline-checkout) ## General ### Font The default font is [Lato](https://fonts.google.com/specimen/Lato), a popular humanist font that fits into most designs. You can choose from a selection of system fonts instead. If your chosen font can't be loaded on a customer device, Paddle falls back to Helvetica Neue, Helvetica, Arial, then the system default sans-serif. ### Focus state ![Illustration showing how fields in the customize screen relate to fields on a checkout](/src/assets/images/tmp-build/dashboard-brand-checkout-general-20230310.png) The focus state colors determine the border and shadow color when a field is selected. For text boxes, this means a customer has clicked into the field to enter their information. ### Checkout padding Checkout padding is on by default. Turn it off to let your checkout fill the full width of the frame, which may be useful if you have your own padding set on your website. ## Buttons Some buttons are shown as part of checkout, for example: - Pay Now - Subscribe Now - Pay by Card - Change Payment Method - Continue - Cancel Buttons are either primary or secondary: - The primary button is the main thing you want a customer to do, like "Pay Now" or "Continue". - Secondary buttons are for other actions, like "Change Payment Method". ![Illustration showing how fields in the customize screen relate to buttons on a checkout](/src/assets/images/tmp-build/dashboard-brand-checkout-buttons-20230310.png) You can customize button size, text color, borders, and hover states using the options in the Button section. We recommend that you make your primary button appear more prominent than your secondary button to guide customers through checkout. ## Inputs Inputs are fields where customers enter their details. This includes the labels (e.g. "Cardholder name") and input boxes themselves. ![Illustration showing how fields in the customize screen relate to inputs on a checkout](/src/assets/images/tmp-build/dashboard-brand-checkout-inputs-20230310.png) You can choose where labels appear relative to the field that they're describing, or turn them off altogether for a cleaner design. The placeholder fields in Paddle Checkout give customers an idea of what they should input in a field. For compatibility, labels always show at the top-left of input fields on mobile devices. ## Links Some links are shown as part of checkout, for example: - Add Coupon - Add a VAT number ![Illustration showing how fields in the customize screen relate to links on a checkout](/src/assets/images/tmp-build/dashboard-brand-checkout-links-20230310.png) You can change the text size and color of these links. We recommend styling them so they stand out to against other text on your page. ## Messages Every checkout includes a message to let customers know that Paddle is the merchant of record for the transaction. Checkout also shows a message to let customers know that a coupon was applied successfully. ![Illustration showing how fields in the customize screen relate to messages on a checkout](/src/assets/images/tmp-build/dashboard-brand-checkout-messages-20230310.png) You can change the font size, border colors, background colors, and how rounded the containers are. It's important that customers know who they're buying from. When customizing checkout, make sure the checkout footer message is visible and legible to remain legally compliant. --- # Provision access and handle subscription state URL: https://developer.paddle.com/build/subscriptions/provision-access-webhooks Use webhooks to keep your app in sync with Paddle, then decide what access to grant based on subscription state. Provisioning is how you grant customers access to your app, as well as determining which features they should have access to. It's sometimes called fulfillment. For example: - When customers sign up, set them up with an account for your app. - If customers add or remove products for additional modules, give them access to relevant features in your app. - Where subscriptions are paused or canceled, limit or stop access to your app. ## Choose an integration shape There are two patterns. Pick the one that matches how your app needs to make access decisions: - **Lean cache** Store only the fields needed for access decisions via webhooks. For richer billing data, fetch from the Paddle API on demand. Minimal write volume and works well for most apps. - **State mirror** Mirror the full subscription and transaction state into your database. Access checks and billing displays all read locally, so there's no runtime dependency on Paddle's API. Requires a reconciliation job to repair drift if you miss events. ## What to listen for We recommend setting up webhooks for the following events: | Event | Purpose | |---|---| | `subscription.created` | A new subscription exists. Save the customer and subscription IDs against your user, and grant access. | | `subscription.updated` | Something about the subscription has changed, like its status, items, scheduled changes, or billing period. Re-read the cached fields you store. | You don't need to listen for separate events for renewals, upgrades or downgrades, or status changes. `subscription.updated` covers them all. Depending on your setup, you may also want to listen for: - `customer.created` and `customer.updated` if you don't already capture customer details at signup. - `transaction.completed` if you bill one-off charges and need to record them locally. To see exactly which events occur for common scenarios, use the [webhook simulator](/webhooks/simulator/test-webhooks). ## What to store For lean cache, store just enough to identify the customer in Paddle and answer simple access checks without an API call: | Field | What it's for | |---|---| | `customer.id` | The link between your user and Paddle. Save it once, against the user record. | | `subscription.id` | Look up or update the subscription via the API. | | `subscription.status` | Cheap access check without an API call. | | `subscription.items[].price.id` and `subscription.items[].price.product_id` | Map to features in your app. | | `subscription.scheduled_change.effective_at` | Know when a paused or canceled subscription stops giving access. | A minimal schema looks like this: ```sql create table customers ( customer_id text primary key, user_id uuid not null references users(id), email text not null ); create table subscriptions ( subscription_id text primary key, customer_id text not null references customers(customer_id), status text not null, price_id text, product_id text, scheduled_change_at timestamptz, updated_at timestamptz not null default now() ); ``` Use the [customer portal](/concepts/sell/customer-portal) to let customers update their payment details, pause or cancel, and grab invoices. The portal is included with Paddle and handles those flows for you. ## Gate subscription access ### Status Your access decision usually comes down to `subscription.status` and `scheduled_change`: ```mermaid flowchart LR A[trialing] --> B[active] B <--> C[past_due] B --> D[paused] D --> B B --> E[canceled] C --> E ``` | Status | Access | Notes | |---|---|---| | `trialing` | Full | Treat the same as `active`. | | `active` | Full | Full access to your app. | | `past_due` | Full | Show a banner and link to the customer portal so the customer can update their payment method. Paddle Retain automatically retries payment for you. | | `paused` | None or read-only | No transactions are created while paused. | | `canceled` | None | Revoke access. Only revoke after the scheduled change takes effect and the status chances to canceled. | You can't update items on a subscription when it has a pending scheduled change. If you let customers self-serve plan changes, gate that UI when `scheduled_change` is set. ### Entitlements If you have a SaaS app with good-better-best or single option pricing, you can map a `product_id` to a set of features in code. For example, when a user purchases the "Pro" product, they get access to the "Pro" features. If you offer addons or other products that mix and match across plans, store an array of product IDs for each user in your database. ## Common scenarios How to react to the things that actually happen, without reasoning about which event fires when. - **New signup** On `subscription.created`, save the `customer_id` against your user and grant access. - **Upgrade or downgrade** On `subscription.updated`, refresh the cached `price_id` and `product_id`. Update which features the customer has access to. - **Past due** Keep access. Show a banner directing the customer to the [customer portal](/concepts/sell/customer-portal) to update their payment method. - **Pause or resume** On `subscription.updated`, gate access by the new `status`. Resumed subscriptions go straight back to `active` (or `past_due`). - **Cancellation** If `scheduled_change.effective_at` is in the future, revoke access at that time. If the subscription is already `canceled`, revoke now. ## Best practices - Paddle guarantees at-least-once delivery, so you may receive the same event more than once. Dedupe on `event_id`. See [how webhooks work](/webhooks/about/how-webhooks-work). - Events can arrive out of order. Compare `occurred_at` with the value you last stored before applying an update. - Run a periodic reconciliation job that lists subscriptions for active customers via the API and repairs any drift between Paddle and your cache. ## If you can't store anything For performance and scalability, we strongly recommend storing information about subscriptions in a database and using webhooks to keep that information up-to-date. If your app can't persist local state, drive everything from the API using the customer's ID: - [List transactions](/api-reference/transactions/list-transactions) and [list subscriptions](/api-reference/subscriptions/list-subscriptions) accept a `customer_id` query parameter. - [List transactions](/api-reference/transactions/list-transactions) accepts a `subscription_id` parameter to return a subscription's transactions. - [Get a transaction](/api-reference/transactions/get-transaction) and [get a subscription](/api-reference/subscriptions/get-subscription) support `include` parameters to return related entities in one request. --- # Present customers with an upsell checkout URL: https://developer.paddle.com/build/checkout/upsell-checkout Encourage customers to upgrade or purchase additional items after their initial purchase by opening a checkout designed for upsells, removing friction and boosting conversion. Only available for inline checkouts. An [upsell checkout](/concepts/sell/upsell-checkout) is a specialized purchase flow designed to capture additional sales immediately after a customer completes an initial purchase. It's optimized to minimize friction and maximize conversion rates by reusing details and consent acknowledgments from the preceding transaction. ## How it works You can use upsell checkouts after a customer has completed an initial transaction using [Paddle Checkout](/concepts/sell/self-serve-checkout). You pass the transaction ID of the initial transaction to the upsell checkout. An upsell checkout uses the customer, business, and address data from the previous transaction, so customers never have to reenter these details and you don't have to [manually prefill those details](/build/checkout/prefill-checkout-properties). You must open an [inline, one-page checkout](/concepts/sell/branded-integrated-inline-checkout) for the upsell flow to show. If you try to open an [overlay checkout](/build/checkout/build-overlay-checkout) or multi-step variant, it defaults to a standard inline checkout. ### Enable a one-click purchase experience The checkout flow can be streamlined to render a single button for one-click purchasing. It hides elements that were already presented during the initial transaction, like the Merchant of Record disclosure, and automatically selects the same customer payment method. This happens when: - [**The transaction is within the same session**](#same-session) The customer completed the initial transaction within the last 5 minutes. - [**The payment methods can be reused**](#reusing-payment-methods) The customer saved their payment method during the initial purchase and they're authenticated, or they selected Apple Pay or Google Pay. If these don't happen, then the upsell checkout experience presented to customers can vary. Customer data is still reused and the [skip button](#customize-the-upsell-flow) is still shown. ![Illustration of three different upsell checkout flows. The first is a streamlined flow with a skip button that occurs within the same session as the initial purchase. The second is a standard upsell flow with a skip button that occurs outside the same session but with saved payment methods. The third is a standard upsell flow with a skip button that occurs outside the same session and without saved payment methods.](/src/assets/images/tmp-build/upsell-checkout-hero-20250918.svg) #### Same session You can open checkouts for upsells immediately after a transaction completes, or later - for example, from an email campaign or when customers return to your app. Paddle determines whether an upsell occurs within the "same session" by checking if the original transaction was completed within the last 5 minutes. If so, the checkout flow is streamlined to hide elements that were already presented during the initial purchase, like the Merchant of Record disclosure. If not, those elements are shown. When a checkout is opened for an upsell, the session remains fixed. This means the flow won't change for customers during purchase and upsell transactions still process successfully after the 5 minute window has passed. #### Reusing payment methods When the upsell occurs within the [same session](#same-session) and payment methods are reusable, customers can complete their purchase with one click. There's two approaches to enable payment method reuse, depending on the payment method used in the initial transaction: - **Saved payment methods** When you turn on the option to let customers [save their payment methods](/build/checkout/saved-payment-methods), they can opt to save their [card](/concepts/payment-methods/card) or [PayPal](/concepts/payment-methods/paypal) account during checkout. The saved payment method is automatically selected for one-click purchasing if the customer is authenticated with a [customer authentication token](/build/checkout/saved-payment-methods). - **Wallet payment methods** When a customer uses [Apple Pay](/concepts/payment-methods/apple-pay) or [Google Pay](/concepts/payment-methods/google-pay) in the initial transaction, you can [pass that same method](#present-saved-payment-methods) when opening the upsell checkout within `allowedPaymentMethods`. This restricts the checkout to that specific payment method, enabling one-click purchasing without requiring a customer authentication token. For card and PayPal payment methods, customers must opt to save. If consent wasn't given during the initial purchase, customers must reenter their payment details for the upsell. ### Customize the upsell flow You can pass settings to Paddle.js to [configure the upsell experience](#customize-the-upsell-flow). By default, the checkout shows a "No thanks" button so customers can decline the upsell. If they decline, you must [handle what happens](#handle-skipped-and-canceled-upsells) depending on your purchase flow. To hide the skip button, pass `upsell.settings.showSkipButton: false`. Do this when customers can navigate away from the checkout themselves, and you don't want to handle what happens next. Paddle Checkout takes no action when the skip button is clicked. Hide the button or listen for the [`checkout.upsell.canceled`](/paddle-js/events/checkout-upsell-canceled) event to take action yourself. ### Checkout events [Checkout events](/paddle-js/events) include an `upsell` object when the checkout is opened as an upsell flow. The object contains the original transaction ID and a `sameSession` boolean. If `true`, the upsell occurred within the same session as the previous transaction. Paddle.js emits a [`checkout.loaded`](/paddle-js/events/checkout-loaded) event when the checkout is opened. You can use this to track whether an upsell flow was shown to a customer by reading `event.data.upsell`. If `sameSession` is `true`, the streamlined upsell flow was shown. If the checkout isn't for an upsell, or you attempted to open a checkout for an upsell but an upsell-specific flow wasn't shown to the customer, the value of `upsell` is `null`. This can happen because: - The previous transaction isn't `completed`. - You attempt to open an overlay or multi-step checkout for an upsell. ## Before you begin ### Set up an initial checkout Upsells are intended to follow directly after a previously completed transaction at checkout. To open a new checkout for an upsell, you need to have a previous transaction that's `completed`. Build an [inline](/build/checkout/build-branded-inline-checkout) or [overlay](/build/checkout/build-overlay-checkout) checkout to let customers purchase initially. This allows you to [capture the ID of that transaction](#grab-the-previous-transaction-id) and pass it to [open a checkout for the upsell](#open-a-checkout-for-an-upsell) later. While the initial checkout can use an overlay checkout, upsells must use inline checkouts. It's recommended to use an inline checkout throughout for ease of implementation. ### Create products and prices to upsell You'll need to [create any new products and at least one related price](/build/products/create-products-prices) to pass as items to upsell at checkout. ### Turn on saved payment methods For the best upsell experience where customers can purchase in one-click by reusing their previous payment method, you should [turn on saved payment methods](/build/checkout/saved-payment-methods). Without this, customers can only use wallet payment methods like [Apple Pay](/concepts/payment-methods/apple-pay) or [Google Pay](/concepts/payment-methods/google-pay) for one-click purchasing, or would need to enter their payment details again for the upsell transaction. ## Open a checkout for an upsell Open a checkout for an upsell in four steps: 1. [**Grab the previous transaction ID**](#grab-the-previous-transaction-id) Handle checkout completed events for the previous transaction. 2. [**Capture the payment method type**](#capture-the-payment-method-type) Capture the payment method type if the customer used Apple Pay or Google Pay. 3. [**Verify the transaction is completed**](#verify-the-transaction-is-completed) Confirm that the transaction is `completed` before opening the upsell flow. 4. [**Open the checkout with Paddle.js**](#open-the-checkout-with-paddlejs) Pass `upsell` to Paddle.js with the previous transaction ID, along with the items to upsell and any other settings to customize the flow. ### Grab the previous transaction ID Upsell checkouts require a customer to have completed a previous transaction through checkout. You'll need to provide the transaction ID for that previous transaction when opening the new checkout. Since upsell checkouts are intended to be opened [as soon as possible](#same-session) after a customer has completed their purchase, we recommend grabbing the transaction ID on the client-side using Paddle.js. When initializing Paddle.js, you can pass `eventCallback` as a configuration option to run a function when a specific event occurs. Extract the transaction ID from the [`checkout.completed` event](/paddle-js/events/checkout-completed) when a customer completes checkout. Import Paddle.js events if using TypeScript, then update where you initialize Paddle.js to include an `eventCallback` function: ```typescript import { initializePaddle } from '@paddle/paddle-js'; import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js'; // Variable to store the transaction ID for the upsell let previousTransactionId: string | null = null; const paddle = await initializePaddle({ token: 'CLIENT_SIDE_TOKEN', eventCallback: (event: PaddleEventData) => { if (event.name === CheckoutEventNames.CHECKOUT_COMPLETED) { // Grab the transaction ID from the completed checkout previousTransactionId = event.data.transaction_id; } } }); ``` Update where you initialize Paddle.js to include an `eventCallback` function: ```html ``` ### Capture the payment method type If the customer might use Apple Pay or Google Pay in the initial transaction, you should capture the payment method type alongside the transaction ID. This lets you [present the one-click payment experience](#enable-a-one-click-purchase-experience) when [opening the upsell checkout](#present-wallet-payment-methods) by passing the wallet method to `allowedPaymentMethods`. Extract the payment method type from the [`checkout.completed`](/paddle-js/events/checkout-completed) event by reading `event.data.payment.method_details.type`. Look for whether it's `apple-pay` or `google-pay`. Update your `eventCallback` function to also capture the payment method type: ```typescript import { initializePaddle } from '@paddle/paddle-js'; import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js'; // Variables to store data for the upsell let previousTransactionId: string | null = null; let paymentMethodType: string | null = null; const paddle = await initializePaddle({ token: 'CLIENT_SIDE_TOKEN', eventCallback: (event: PaddleEventData) => { if (event.name === CheckoutEventNames.CHECKOUT_COMPLETED) { // Grab the transaction ID and payment method type previousTransactionId = event.data.transaction_id; const methodType = event.data.payment?.method_details?.type; if (methodType === 'apple-pay' || methodType === 'google-pay') { paymentMethodType = methodType; } } } }); ``` Update your `eventCallback` function to also capture the payment method type: ```html ``` ### Verify the transaction is completed When a checkout completes, the related transaction moves to `paid` while completed transaction processing takes place, then `completed`. This typically takes less than a second. For most use cases, you can open the upsell checkout immediately after receiving the `checkout.completed` event. If the transaction isn't in a `completed` state yet, customers see the standard checkout flow instead. If you want to ensure the streamlined upsell flow is shown, you have two options: - **Make a single API verification call** Fetch the transaction [through the Paddle API](/api-reference/transactions/get-transaction) and verify the `status` field is `completed` before opening the upsell checkout. - **Using webhook data (if you store transactions)** If you already listen for `transaction.completed` or `transaction.updated` webhooks to [handle fulfillment](/build/subscriptions/provision-access-webhooks), you can check your stored transaction data to verify completion status. Don't continuously poll the Paddle API to check transaction status. Make at most one verification call, then proceed with opening the checkout regardless of the result. ### Open the checkout with Paddle.js You use the Paddle.js [`Paddle.Checkout.open()`](/paddle-js/methods/paddle-checkout-open) method to open a checkout for an upsell. You can [build an inline checkout](/build/checkout/build-branded-inline-checkout) with all usual [checkout settings](/build/checkout/set-up-checkout-default-settings). The checkout must be inline and one-page. Pass `displayMode` with the value `inline`, and `variant` with the value `one-page` as [a checkout setting](/build/checkout/set-up-checkout-default-settings). Pass either the [items](/build/checkout/pass-update-checkout-items) you want to upsell to the checkout, or the [transaction ID](/build/transactions/pass-transaction-checkout) of an existing transaction that contains the items you want to upsell. You may also want to [create and apply a discount](/build/products/offer-discounts-promotions-coupons) to the upsell to incentivize the customer to purchase the additional items. The `upsell` object tells Paddle.js that this is a checkout for an upsell. You must pass this to the `Paddle.Checkout.open()` method. #### Provide the previous transaction ID Take the `previousTransactionId` variable you stored from the `checkout.completed` event and pass it as `upsell.transactionId`. There's no need to [pass customer, address, or business details](/build/checkout/prefill-checkout-properties) because they're automatically inherited from the previous transaction. If you do choose to pass a `customer_id`, the customer against the previous transaction must match. [`allowLogout`](/build/checkout/set-up-checkout-default-settings) is ignored if passed and defaults to `false` because the upsell is tied to a previous transaction and customer. #### Customize the upsell flow By default, the checkout for upsells shows a "No thanks" skip button. Once clicked, the checkout emits a [`checkout.upsell.canceled`](/paddle-js/events/checkout-upsell-canceled) event. You can hide this by passing `upsell.settings.showSkipButton: false`. This prevents the upsell from being canceled and the checkout from being closed. #### Present saved payment methods To render the [one-click purchase experience](#enable-a-one-click-purchase-experience) or show previous saved payment methods when customers use card or PayPal, you must: - [Turn on saved payment methods](/build/checkout/saved-payment-methods) - Authenticate customers with a [customer authentication token](/api-reference/customers/generate-customer-authentication-token). Generate a [customer authentication token](/build/checkout/saved-payment-methods) using the Paddle API and [pass it to the checkout](/build/checkout/saved-payment-methods) as `customerAuthToken`. If customers can't complete their purchase in one click, check that the [customer has payment methods saved](/api-reference/payment-methods/list-customer-payment-methods) and the checkout is in the [same session](#same-session) using `upsell.sameSession` in [checkout events](/paddle-js/events). #### Present wallet payment methods To render the [one-click purchase experience](#enable-a-one-click-purchase-experience) when customers use Apple Pay or Google Pay, you must pass that specific wallet method to `allowedPaymentMethods`. If you [captured the payment method type](#capture-the-payment-method-type) from the initial checkout, you can pass it directly to `allowedPaymentMethods` as the only value in the array. #### Examples This example shows opening an upsell checkout when the customer used card or PayPal in the initial transaction. The customer authentication token enables access to their saved payment method for one-click purchasing. ```javascript var upsellItems = [ { priceId: 'pri_01h1vjfevh5etwq3rb176h9d9w', quantity: 1 } ]; // previousTransactionId was captured from the initial checkout.completed event Paddle.Checkout.open({ items: upsellItems, customerAuthToken: 'pca_01hwz42rfyaxw721bgkppp66gx_01h282ye3v2d9cmcm8dzpawrd0_otkqbvati3ryh2f6o7zdr6owjsdhkgmm', settings: { displayMode: "inline", frameTarget: "checkout-container", frameInitialHeight: "450", frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;", }, upsell: { transactionId: previousTransactionId, settings: { showSkipButton: false } } }); ``` This example shows opening an upsell checkout when the customer used Apple Pay or Google Pay in the initial transaction. The `allowedPaymentMethods` array is set to the wallet method used in the initial checkout. ```javascript var upsellItems = [ { priceId: 'pri_01h1vjfevh5etwq3rb176h9d9w', quantity: 1 } ]; // paymentMethodType and previousTransactionId were captured from the initial checkout.completed event Paddle.Checkout.open({ items: upsellItems, settings: { displayMode: "inline", frameTarget: "checkout-container", frameInitialHeight: "450", frameStyle: "width: 100%; min-width: 312px; background-color: transparent; border: none;", allowedPaymentMethods: [paymentMethodType] }, upsell: { transactionId: previousTransactionId, settings: { showSkipButton: false } } }); ``` ## Handle skipped and canceled upsells By default, checkouts for upsells [display a "No thanks" skip button](#customize-the-upsell-flow). When a customer clicks the "No thanks" button, Paddle.js emits a [`checkout.upsell.canceled`](/paddle-js/events/checkout-upsell-canceled) event but doesn't perform any action. You need to decide what happens next so customers continue their journey. If you don't want checkouts to display the skip button, pass `upsell.settings.showSkipButton: false`. Common actions include redirecting customers to a different page, [closing the checkout](/paddle-js/methods/paddle-checkout-close), or closing and opening a new checkout with an improved upsell offer. When initializing Paddle.js, you can pass `eventCallback` as a configuration option to run a function when a specific event occurs. This can be used to handle when the [`checkout.upsell.canceled`](/paddle-js/events/checkout-upsell-canceled) event is emitted. This example redirects customers to a dashboard page when they skip the upsell. Import Paddle.js events if using TypeScript, then update where you initialize Paddle.js to include an `eventCallback` function: ```typescript import { initializePaddle } from '@paddle/paddle-js'; import { CheckoutEventNames, PaddleEventData } from '@paddle/paddle-js'; const paddle = await initializePaddle({ token: 'CLIENT_SIDE_TOKEN', eventCallback: (event: PaddleEventData) => { if (event.name === CheckoutEventNames.CHECKOUT_UPSELL_CANCELED) { setTimeout(() => { window.location.href = `https://app.aeroedit.com/settings?upsellSkipped=true`; }, 3000); } } }); ``` Update where you initialize Paddle.js to include an `eventCallback` function: ```html ``` If you close the checkout using `Paddle.Checkout.close()`, be aware that the checkout `iframe` is removed from the DOM. Consider updating your UI alongside this for a more seamless user experience. --- # Work with trials URL: https://developer.paddle.com/build/trials/update-trials Add or remove recurring items, change quantities, and bill for one-time charges for subscriptions in trial. You can also pause trialing subscriptions, too. Trials let customers try your app or service before paying for it. You can update trialing subscriptions to make changes to what a customer is billed for when they transition to a paid plan. You can also bill one-time charges to a subscription for things like data import or setup fees. We recommend letting customers make changes to a trialing subscription as part of a workflow where they confirm and activate their subscription. Change the next billing date against a trialing subscription to extend or cut short the trial period. You can also activate a trialing subscription right away using the API. To learn more, see [Extend or activate a trial](/build/trials/extend-activate-change-date-trials) ## How it works When [customers complete checkout](/build/checkout/pass-update-checkout-items) for recurring items with a trial period, or you [issue an invoice](/build/invoices/create-issue-invoices) for items with a trial period, Paddle creates a subscription with the status `trialing`. You can make changes to trialing subscriptions to pause, add or remove items, change quantities, and bill for one-time charges. This lets you handle: - **Upgrades or downgrades** Replacing items on a subscription to let customers upgrade or downgrade their plan while they're in trial. - **Adding, removing, or changing items** Add or remove additional modules, or update quantities for seats or users before going live. - **Bill for one-time charges** Charge for things like data import or setup fees during a trial period. - **Pause at the end of the trial period** Preserve any work a customer did in your app in trial if they're not ready to go live right away. Since customers aren't yet paying, [proration](/concepts/subscriptions/proration) doesn't apply when making changes to a trialing subscription. You must use `do_not_bill` as the `proration_billing_mode` when sending requests. Only the `items` and `next_billed_at` fields can be updated for a subscription in trial. You can't update other fields against a subscription until it's activated. ## Before you begin To make changes to items on a trialing subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `trialing` to get a list of subscriptions in trial. ## Update items Update items on a trialing subscription to make changes to what a customer is billed for when activated. Replace items on a subscription to [upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade); or [add, remove, or change items](/build/subscriptions/add-remove-products-prices-addons) to change how Paddle bills for recurring products like additional modules or seats. Update items on a trialing subscription using the API in three steps: 1. **Extract existing items** Get the existing items on the subscription to extract the `price.id` and `quantity` for each item. 2. **Preview change** Preview how much the customer is going to be charged on a regular basis. Present this to customers if you offer an in-app workflow to update items. 3. **Update subscription** Send a request to update the subscription with the changes you previewed. For examples and specific guidance, see [Add or remove items](/build/subscriptions/add-remove-products-prices-addons) or [Upgrade or downgrade](/build/subscriptions/replace-products-prices-upgrade-downgrade). ### Extract existing items Send a request to the `/subscriptions/{subscription_id}` endpoint to get the existing items on the subscription. Extract the `price.id` and `quantity` for each item in the `items` array — you'll need these to build your update request. ### Preview change Send a request to the `/subscriptions/{subscription_id}/preview` endpoint to preview the changes you're making to the subscription. In your request, include: - `items`: An array of items you want on the subscription. Each item should include a `price_id` and `quantity`. - `proration_billing_mode`: Set to `do_not_bill`, which is the only allowed value for trialing subscriptions. The response includes: - `next_transaction`: An object with a preview of the next transaction for this subscription. May include charges that aren't yet billed. - `recurring_transaction_details`: An object with a preview of the recurring transaction for this subscription. This is what the customer can expect to be billed when there's no prorated or one-time charges. You should present this to customers if you offer an in-app workflow to update items. ### Update subscription Send a request to the `/subscriptions/{subscription_id}` endpoint to update the subscription. In your request, include the same body as the preview request. Paddle updates the subscription. ## Bill for one-time charges Bill a one-time charge to a trialing subscription for things like onboarding or setup fees. Bill for one-time charges using the API in two steps: 1. **Preview charge** Preview how much the customer is going to be charged for the one-time charge. Present this to customers if you offer a way to bill for one-time charges in your app. 2. **Create one-time charge** Send a request to bill for the one-time charge. For a complete overview with examples, see [Bill for one-time charges](/build/subscriptions/bill-add-one-time-charge). ### Preview charge Send a request to the `/subscriptions/{subscription_id}/charge/preview` endpoint to preview the one-time charge. In your request, include: - `items`: An array of one-time items you want to bill for. Each item should include a `price_id` and `quantity`. You don't need to include existing recurring items. - `effective_from`: Set to `immediately` to create a transaction right away, or `next_billing_period` to add charges to the next renewal transaction. The response includes: - `next_transaction`: An object with a preview of the next transaction for this subscription. May include charges that aren't yet billed. - `recurring_transaction_details`: An object with a preview of the recurring transaction for this subscription. This is what the customer can expect to be billed when there's no prorated or one-time charges. You should present this to customers if you offer a way to bill for one-time charges in your app. ### Create one-time charge Send a request to the `/subscriptions/{subscription_id}/charge` endpoint to bill for the one-time charge. In your request, include the same body as the preview request. Paddle bills for the one-time charge. ## Pause at the end of the trial period Pause a trialing subscription at the end of the trial period to preserve any work a customer did in your app in trial if they're not ready to go live right away. Paddle bills for the subscription on resume. Send a request to the `/subscriptions/{subscription_id}/pause` endpoint to pause the subscription. For more information and examples, see [Pause a subscription](/build/subscriptions/pause-subscriptions). --- # Extend or activate a trial URL: https://developer.paddle.com/build/trials/extend-activate-change-date-trials Extend a trial to give customers longer to evaluate, or cut a trial short to transition a customer to paying. Trials let customers try your app or service before paying for it. You can extend trials to give customers more time to evaluate your app, or activate a subscription before a trial is up to transition a customer to paying. Making it as easy as possible for trialing customers to transition to a paid plan is a simple way to reduce your CAC (customer acquisition cost). Change the items list against a subscription to add or remove items, update quantities, and bill for one-time charges. To learn more, see [Work with trials](/build/trials/update-trials) ## How it works When [customers complete checkout](/build/checkout/pass-update-checkout-items) for recurring items with a trial period, Paddle creates a subscription with the status `trialing`. The customer isn't charged right away. Instead, they're charged on the `next_billed_at` date against the subscription. When the `next_billed_at` date elapses, Paddle charges the [payment method](/concepts/payment-methods) on file and the subscription changes to `active`. The `current_billing_period` for trialing subscriptions is based on the trial period. On activation, it's updated so that it's based on the `billing_cycle`. You can extend a trial by changing the `next_billed_at` date against a subscription to a date after this date. You can cut a trial period short by either: - Changing the `next_billed_at` date against a subscription to an earlier date. - Sending a request to the [`/subscriptions/{subscription_id}/activate`](/api-reference/subscriptions/activate-subscription) endpoint to activate a subscription immediately. Both options work for automatically-collected subscriptions. The [activate a trialing subscription operation](/api-reference/subscriptions/activate-subscription) only works for automatically-collected subscriptions, so you should move the billing date for manually-collected subscriptions. Since customers aren't yet paying, [proration](/concepts/subscriptions/proration) doesn't apply when changing billing dates for a trial. You must use `do_not_bill` as the `proration_billing_mode` when sending requests. Only the `items` and `next_billed_at` fields can be updated for a subscription in trial. You can't update other fields against a subscription until it's activated. ## Before you begin To extend or activate a trialing subscription, you'll need to [get the subscription ID](/api-reference/subscriptions/list-subscriptions) for the subscription you want to change. You can use the `status` query parameter when listing with the value `trialing` to get a list of subscriptions in trial. ## Extend or cut short a trial Send a request to the `/subscriptions/{subscription_id}` endpoint to extend a trial. In your request, include: - `next_billed_at` with the new trial end date. Must be at least 30 minutes from now. - `proration_billing_mode` set to `do_not_bill`, which is the only allowed value for trialing subscriptions. Paddle updates the `next_billed_at` date for the subscription and items, and `current_billing_period` and `items[].trial_dates.ends_at`. ## Activate a trialing subscription Only automatically-collected subscriptions can be activated using the activate a subscription operation. For manually-collected subscriptions, follow the steps to [extend or cut short a trial](#extend-or-cut-short-a-trial). Activate a subscription to cut the trial period short and start charging a customer for it. Activate a subscription using the API in two steps: 1. **Preview charging** Preview how much the customer is going to be charged on activation. Present this to customers if you offer in-app activation. 2. **Activate subscription** Send a request to activate a subscription immediately. ### Preview charging Send a request to the `/subscriptions/{subscription_id}` endpoint, using the `include` query parameter with the `next_transaction` and `recurring_transaction_details` values to get a preview of what the customer is going to be charged when you activate the subscription. The response includes: - `next_transaction`: An object with a preview of the next transaction for this subscription. May include charges that aren't yet billed. - `recurring_transaction_details`: An object with a preview of the recurring transaction for this subscription. This is what the customer can expect to be billed when there's no prorated or one-time charges. You should present this to customers if you offer in-app activation. ### Activate subscription Send a request to the `/subscriptions/{subscription_id}/activate` endpoint to activate a subscription immediately. No request body needed. Paddle activates the subscription immediately. --- # Recover abandoned checkouts URL: https://developer.paddle.com/build/checkout/checkout-recovery Checkouts that don't result in a purchase are considered abandoned. Recover these checkouts with automated emails and discounts to incentivize purchases and increase conversion rates. Customers often start purchasing products and leave without completing the transaction. This is known as an incomplete or abandoned checkout. Paddle tracks abandoned transactions and can automatically send emails to customers to incentivize them to return and finish their purchase. This helps you convert more sales and recapture lost revenue so you leave no money on the table. Checkout recovery is enabled by default if it's enabled in Paddle Classic or if you joined Paddle after May 2, 2025. If you signed up for Paddle before this date, you need to manually turn it on. ![Illustration of the two checkout recovery emails that can be sent - one with a discount and one without a discount.](/src/assets/images/tmp-build/checkout-recovery-both-emails-20250407.svg) ## How it works When a customer opens a checkout, Paddle creates a [transaction](/api-reference/transactions). If a customer doesn't make a purchase, the transaction is considered abandoned. Customers can enter their email address during the checkout flow. If they do, Paddle uses this address to send a [recovery email](/concepts/sell/checkout-recovery) 60 minutes after the transaction was created. Only one email is sent per transaction. Emails contain predefined content which our team have optimized to best encourage customers to return. When a customer clicks the link in the email and completes the checkout, the transaction is considered recovered. ### Discounts [Discounts](/api-reference/discounts) let you reduce the amount a customer pays when purchasing. As part of checkout recovery, you can offer a one-time, percentage-based discount to encourage customers to complete their abandoned transactions. These discounts apply to the full value of the transaction and are only tied to the transaction being recovered. This means customers can't apply the discount to other checkouts and transactions. We recommend offering a 10-20% discount as testing has found this range to be most effective. If you choose to offer a discount, Paddle includes it in all checkout recovery emails. When customers click the link in the email, the checkout opens with the discount automatically applied. Paddle doesn't send discounts to customers who: - **Have already applied a discount** If a discount was already applied during the original checkout, the recovery discount isn't included in the email. - **Haven't agreed to Paddle's marketing consent** Customers must agree to receive marketing emails from Paddle to receive a recovery discount. If these conditions aren't met, customers still receive emails but without the discount offer. Discounts can't be stacked. If a customer adds a different discount to the checkout, it replaces the recovery discount. You can update the value of the discount value through the dashboard when [configuring checkout recovery](#configure-checkout-recovery). This can't be updated through the API. ### Migration from Paddle Classic If you're [migrating from Paddle Classic to Paddle Billing](/migrate), checkout recovery is automatically configured based on your existing Paddle Classic setup, including any discounts you've already configured. Unlike Paddle Classic, Paddle Billing doesn't charge an additional fee for recovered transactions. ## Before you begin You'll need to set up either an [overlay checkout](/build/checkout/build-overlay-checkout) or an [inline checkout](/build/checkout/build-branded-inline-checkout). Paddle only sends emails to recover [automatically-collected transactions](/build/transactions/change-collection-mode-transaction) created by a checkout. You don't need to create a discount to offer one. The discount you add when configuring checkout recovery is automatically created and separate from your existing discounts. ## Configure checkout recovery 1. Go to **Paddle > Checkout > Checkout Settings**. 2. Click the **Recovery** tab. 3. Toggle the **Checkout recovery** slider on if it isn't already. 4. To offer a discount, toggle the **Apply a discount** slider on and enter the percentage amount in the field. 5. Click Save when you're done. ![Illustration showing the checkout recovery settings in the dashboard. It shows a toggle slider for checkout recovery, a toggle slider for apply a discount, and a field to insert the discount percentage. It also shows a button to preview the email. ](/src/assets/images/tmp-build/checkout-recovery-configure-20250407.svg) ## Preview an email After you've [enabled checkout recovery](/build/checkout/checkout-recovery#configure-checkout-recovery), you can preview the email that Paddle sends to customers who abandon their checkout. 1. Go to **Paddle > Checkout > Checkout Settings**. 2. Click the **Recovery** tab. 3. Click Preview email ![Illustration showing a opened drawer with the title 'Preview email' and an example email based on the checkout recovery configuration.](/src/assets/images/tmp-build/checkout-recovery-preview-email-20250407.svg) --- # Launch checkouts from iOS apps URL: https://developer.paddle.com/build/mobile-apps Add an external checkout workflow to your iOS app using Paddle Checkout to let customers make purchases outside your app. Use RevenueCat or webhooks to unlock purchases. With recent developments in legislation around the App Store, you can link users in the to an external checkout for purchases in iOS apps. You can use Paddle to build a seamless purchase workflow for your iOS app that lets customers make purchases outside your app. Link out to a checkout that's fully hosted by Paddle to let users make in-app purchases. Deploy a checkout to Vercel, then link out to it to let users make in-app purchases. Build your own checkout implementation for more control over the purchase workflow. --- # Work with custom subdomains URL: https://developer.paddle.com/build/checkout/custom-subdomains Personalize hosted checkout links using custom subdomains to build customer confidence and improve conversion. Custom subdomains help bridge the gap between your app and the checkout flow by putting your company or app name in the hosted checkout link, reducing cart abandonment and building the confidence customers need to complete their purchase. ## How it works You can use [hosted checkouts](/concepts/sell/hosted-checkout-mobile-apps) to let users securely make purchases outside your app — no hosting required. Customers tap a button in your app to open a checkout that's fully hosted by Paddle, then they're redirected to your app when they complete their purchase. By default, hosted checkouts are hosted at `pay.paddle.io`. For a more branded experience, you can add a custom subdomain to your account and use it for hosted checkout. For example, your hosted checkout can be hosted at `aeroedit.paddle.io/pay`. This helps to build customer confidence, improving conversion. Custom subdomains for sandbox accounts are separate, and follow the format `aeroedit.sandbox.paddle.io`, where `aeroedit` is your custom subdomain. Custom subdomains on sandbox don't require approval. Once you add a custom subdomain, you can set it as the default. Your default custom subdomain is used for new hosted checkouts. You can use any custom subdomain for hosted checkouts, not just your default. You can add up to 10 custom subdomains. To keep the Paddle platform safe for everyone, custom subdomains have to be approved by Paddle before you can use them. You'll get an email from the Paddle team to let you know when your subdomain has been approved. ## Add a custom subdomain 1. Go to **Paddle > Checkout > Custom subdomains**. 2. Click New custom subdomain 3. Enter a descriptive name and an optional description to help you identify this custom subdomain. These aren't shown to customers. 4. Enter the subdomain you want to use in the **Subdomain** field. This is the part that goes before `paddle.io`. For example, enter `aeroedit` to use `aeroedit.paddle.io`. 5. Click Save when you're done. 6. Wait for an email from Paddle to say that your custom subdomain has been approved. ![New subdomain modal dialog with Name, Description, and Subdomain form fields, overlaying a custom subdomains management page.](/src/assets/images/tmp-build/custom-subdomains-20250716.svg) ## Set a custom subdomain as default Your default custom subdomain is used for new hosted checkouts that you create. 1. Go to **Paddle > Checkout > Custom subdomains**. 2. Find the subdomain you want to be the default in the list, then click the action menu and choose Set as default ![Custom subdomains table showing aeroedit (approved) and paddle (rejected) entries, with an open action menu displaying Copy URL, Edit subdomain, and Set as default options.](/src/assets/images/tmp-build/custom-subdomains-set-default-20250829.svg) ## Use a custom subdomain for a hosted checkout To use your custom subdomain for a hosted checkout, copy a link using your default custom subdomain or copy the Paddle URL and swap `pay.paddle.io` with your custom subdomain. ### Use your default custom subdomain 1. Go to **Paddle > Checkout > Hosted checkouts**. 2. Find the hosted checkout you want to use with a custom domain, then click the button. 3. Choose Copy custom URL to get a hosted checkout link that uses your default custom subdomain. ![Screenshot of the Paddle dashboard 'Hosted checkouts' list view. The interface displays a table with columns for Name, Description, and Redirect URL. Multiple entries are shown with placeholder content. A context menu is open for one of the items showing four options: Edit (with pencil icon), Copy Paddle URL (with copy icon), Copy Custom URL (with copy icon), and Archive (with trash icon in red).](/src/assets/images/tmp-build/dashboard-copy-hosted-checkout-20250829.svg) ### Use another custom subdomain 1. Go to **Paddle > Checkout > Hosted checkouts**. 2. Find the hosted checkout you want to use with a custom domain, then click the button. 3. Choose Copy Paddle URL to get a hosted checkout link that uses `pay.paddle.io` or `sandbox.pay.paddle.io`. 4. Paste the URL somewhere and replace `pay` with the custom subdomain you want to use and add `/pay` to the path. For example, replace `pay` with `aeroedit` in `pay.paddle.io` if your subdomain is `aeroedit`. ![Screenshot of the Paddle dashboard 'Hosted checkouts' list view. The interface displays a table with columns for Name, Description, and Redirect URL. Multiple entries are shown with placeholder content. A context menu is open for one of the items showing four options: Edit (with pencil icon), Copy Paddle URL (with copy icon), Copy Custom URL (with copy icon), and Archive (with trash icon in red).](/src/assets/images/tmp-build/dashboard-copy-paddle-url-20250903.svg) Your complete URL should look something like this: ```text https://aeroedit.paddle.io/pay/hsc_01jt8s46kx4nv91002z7vy4ecj_1as3scas9cascascasasx23dsa3asd2a ``` --- # Set up Paddle Retain URL: https://developer.paddle.com/build/retain Set up Paddle Retain to start automatically reducing churn and increasing customer lifetime value. Get started in minutes with Paddle Billing, or integrate with other billing platforms. [Paddle Retain](/concepts/retain) combines world-class subscription expertise with algorithms that use billions of datapoints to automatically reduce churn. Paddle Billing is fully integrated with Retain, meaning it automatically handles dunning and retention for you. Set up in minutes with Paddle Billing, or integrate with other billing platforms. ## Connect to your billing platform If you use Paddle Billing, you can set up Paddle Retain to take care of payment recovery for you. Paddle Billing automatically [integrates with Paddle Retain](/paddle-js/about/include-paddlejs) without additional configuration required. Any team member with the admin, technical, or subscription KPIs roles in Paddle Billing can access Retain. Once you've completed setup, you can configure advanced [payment recovery features](/build/retain/configure-payment-recovery-dunning), [cancellation flows](/build/retain/configure-cancellation-flows-surveys), and [term optimization features](/build/retain/configure-term-optimization-automatic-upgrades). ![Illustration showing the Paddle navigation bar while on the Retain screen. Retain is highlighted, with other options for Checkout, Business Account, Developer Tools, Subscription Metrics, Documentation, and Help Center.](/src/assets/images/tmp-build/retain-setup-sidebar-20250701.svg) Retain [integrates with Paddle.js](/paddle-js/about/include-paddlejs), so you don't need to include any additional scripts if you use Paddle Billing. ### For US customers 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. As part of the setup process, choose **Connect to Braintree**. Alternatively, go to [**ProfitWell > Settings > Integrations**](https://www2.profitwell.com/app/account/integrations) and click **Braintree**. 3. Click **Settings**, then click **Allow access**. 4. Follow the instructions to log in to your Braintree account and authorize ProfitWell to read your data. You must be an admin user in Braintree to do this. 5. That's it! Wait for your data to ingest. This may take a few hours. ### For non-US customers The technology that lets us integrate with Braintree by signing in isn't available outside the . You can integrate by creating a read-only user with API access instead. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. Open Braintree, then go to **Settings > Team**, then click **New user**. 3. Create a new role called "ProfitWell Read Only" with rights to: | | | |-------------------------|-------------------------------------------------| | **Transactions** | Download Transactions with Masked Payment Data | | **Customer management** | Download Vault Records with Masked Payment Data | | **Recurring billing** | Download Subscription Records | | **Recurring billing** | View Subscription Plans | Click **Save** when you're done. 4. Still in Braintree, go to **Settings > Manage users**, then click **Add single user**. 5. Create a new user with API access, the "ProfitWell Read Only" role you just created, and access to your merchant account. Click **Save** when you're done. 6. Log out of Braintree, then log in again using the account you just created. 7. Go to **Settings > API**, then click **Generate new API key**. 8. Email [sellers@paddle.com](mailto:sellers@paddle.com?subject=RETAIN%20Payment%20recovery) with the public key, private key, and merchant ID for the API you just created. We'll take care of the rest. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. As part of the setup process, choose **Connect to Chargebee**. Alternatively, go to [**ProfitWell > Settings > Integrations**](https://www2.profitwell.com/app/account/integrations) and click **Chargebee**. 3. Click **Settings** to open the settings screen, then paste your Chargebee site name. This is the first part of your Chargebee URL. For example, if you log in to Chargebee at `yourproduct.chargebee.com` then `yourproduct` is your site name. 4. Open Chargebee in a new tab, go to **Settings > Configure Chargebee > API Keys and Webhooks**, then click on the **API keys** tab. Create a new read only API key with all access, then copy your key. 5. Hop back over to ProfitWell, then paste your read only API key into your Chargebee settings screen. 6. Click **Save** and you're done! 7. Wait for your data to ingest. This may take a few hours. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. As part of the setup process, choose **Connect to Chargify**. Alternatively, go to [**ProfitWell > Settings > Integrations**](https://www2.profitwell.com/app/account/integrations) and click **Chargify**. 3. Click **Settings** to open the settings screen, then paste your Chargify site name. This is the first part of your Chargify URL. For example, if you log in to Chargify at `yourproduct.chargify.com` then `yourproduct` is your site name. 4. Open Chargify in a new tab, go to **Config > Integrations > API Keys**. Create a new API key and copy it. 5. Hop back over to ProfitWell, then paste your API key into your Chargify settings screen. 6. Click **Save** and you're done! 7. Wait for your data to ingest. This may take a few hours. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. As part of the setup process, choose **Connect to ReCharge**. Alternatively, go to [**ProfitWell > Settings > Integrations**](https://www2.profitwell.com/app/account/integrations) and click **ReCharge**. 3. Open ReCharge in a new tab, go to **Apps > API tokens**. Create a new API key with write access for all the options, then copy it. 4. Hop back over to ProfitWell, click **Settings** on the ReCharge screen, then paste your API key. 5. Click **Save** and you're done! 6. Wait for your data to ingest. This may take a few hours. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. As part of the setup process, choose **Connect to Recurly**. Alternatively, go to [**ProfitWell > Settings > Integrations**](https://www2.profitwell.com/app/account/integrations) and click **Recurly**. 3. Click **Settings** to open the settings screen, then paste your Recurly site name. This is the first part of your Recurly URL. For example, if you log in to Recurly at `yourproduct.recurly.com` then `yourproduct` is your site name. 4. Open Recurly in a new tab, go to **Developers > API Credentials**. Click the **Add private API key** button, then create an API key with full access and copy it. 5. Hop back over to ProfitWell, then paste your API key into your Recurly settings screen. 6. Click **Save** and you're done! 7. Wait for your data to ingest. This may take a few hours. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. As part of the setup process, choose **Connect to Stripe**. Alternatively, go to [**ProfitWell > Settings > Integrations**](https://www2.profitwell.com/app/account/integrations) and click **Stripe**. 3. Click **Settings**, then click **Allow access**. 4. Follow the instructions to log in to your Stripe account and authorize ProfitWell to read your data. You must be an admin user in Stripe to do this. 5. That's it! Wait for your data to ingest. This may take a few hours. 1. [Set up Paddle Retain](#complete-setup-steps), if you haven't already. 2. Open Zuora, go to **Administration > Manage user roles**, then click **Add new role** 3. Create a new role called "API user" with API write access only. Leave the UI access box unchecked. Click **Save** when you're done. 4. Still in Zuora, go to **Administration > Manage users**, then click **Add single user**. 5. Create a new user with a work email and login name of `product+company_name@profitwell.com`, where `company_name` is your company name with spaces or symbols replaced with an underscore. Set the role to the new API user role you just created, then click **Save** when you're done. 6. Email [sellers@paddle.com](mailto:sellers@paddle.com?subject=RETAIN%20Payment%20recovery) with your API user account name in Zuora. We'll take care of the rest. ## Complete setup steps ### Go to Retain You can access the setup flow for Retain in the Paddle dashboard. 1. Go to **Paddle > Retain**. 2. Click Get started ![Screenshot of the Retain setup process, at Step 1: Retain overview. The screen introduces the service, stating "Retain significantly reduces your churn" and includes a "Get started" button.](/src/assets/images/tmp-build/retain-setup-intro-20250701.svg) ### Set up emails Emails sent from Paddle Retain are designed to look like they come from you or someone on your team. 1. Enter the name of the sender and the email to send from. 2. Enter a name and title for the signature. 3. Click Continue ![Illustration of the Retain setup process, at Step 2: Personalize your emails. It shows fields for setting the sender's name, email address, and signature name to personalize the recovery emails sent to customers.](/src/assets/images/tmp-build/retain-setup-email-personalize-20250701.svg) ### Verify your Retain emails Retain uses Postmark to securely send emails to customers on your behalf. Verify your email sender by following the link provided in the email. 1. Open your inbox and look for the email from Postmark. 2. Click Confirm Sender Signature in the email to verify. ![Illustration of the Retain setup process, at Step 3: Verify your emails. The user is prompted to check their inbox for a verification email from Postmark to confirm their sender email address.](/src/assets/images/tmp-build/retain-setup-email-verify-20250701.svg) If you have issues with verification, you can **Resend** the email or **Use a different email address**. ### Install JavaScript snippets The Retain snippets power in-app payment recovery notifications and engagement tracking. 1. Follow the instructions to [install Paddle.js](/paddle-js/about/include-paddlejs). 2. Not technical? Email the instructions to your engineering team under **Need help from your engineering team?** 3. Under **Check Paddle.js installation**, enter the URL of the page where you installed Paddle.js and click Check 4. You're done. Click Continue to Retain to see all settings. ![Illustration of the Retain setup process, at Step 4: Configure Paddle.js. The user is instructed to install the Paddle.js script on their website to enable one-click payment recovery.](/src/assets/images/tmp-build/retain-setup-configure-js-20250701.svg) If you have issues when checking, make sure the page is publicly accessible and doesn't have any form of redirect. We recommend a homepage or landing page to start. ### Configure optional settings 1. Make sure emails from Retain reach customers by clicking Verify on **DKIM and Return-Path DNS records** to set up email authentication methods. 2. Upload a logo for emails and notifications by clicking Edit on **Custom logo upload**. 3. Turn on localized outreach messages for multiple regions by toggling on **Localized outreach**. ![Illustration of the final step of the Retain setup, showing a confirmation modal that says "Congratulations! Retain is now live." An illustration of an in-app notification for an expiring credit card is overlaid.](/src/assets/images/tmp-build/retain-setup-live-20250701.svg) ## Configure Paddle Retain interventions Now you've finished setup, configure and turn on: Fully automated dunning, powered by algorithms that use billions of datapoints to reduce churn. Personalized offboarding workflows designed to deflect cancellations and gather valuable product insights. Seamless one-click upgrades for customers identified as ready to move to a longer-term plan. --- # Generate reports URL: https://developer.paddle.com/build/reports Export information from your Paddle account as CSV formatted spreadsheets to better understand your data and reconcile account activity. Reports in Paddle let you export CSV formatted spreadsheets with your account data. You can use them as part of your financial processes, like calculating revenue and analyzing trends. They're often used for working with data in other applications, like accounting or ERP solutions. ## Reports available Information about revenue received, past due invoices, draft and issued invoices, and canceled transactions. The same data as on transactions reports, but broken down by transaction line item. Information about refunds, credits, and chargebacks. The same data as on adjustments reports, but broken down by adjustment line item. Get information about products and prices. Get information about discounts. Get complete transparency into how your payout amounts are calculated, and reconcile remittance reports. Get an overview of your account balance activity. Use payout reconciliation reports instead. ## How it works When you build a report, you can filter to choose the data that you include on it. Reports are always sorted by when they were last updated, and date range filters work using the date an entity was last updated. This is so you always see the most relevant data. For example, invoices created a few months before being issued and paid are included on reports when they're issued and paid. Data on reports may be delayed for up to 24 hours. This means you might not see entities created in the last 24 hours on your reports. It might take a little while to generate a report, depending on the amount of data you include on it. The Reports screen in the Paddle dashboard updates when your report is ready, and you'll get an email to let you know, too. Reports are available to download for 14 days once generated. ### Reports are CSV files Paddle produces reports as CSV (comma-separated values) files. CSV files are text files that use commas to separate cells of data. You can open CSV files with any spreadsheet app, like Microsoft Excel, Google Sheets, and Apple Numbers. Your computer or device may open CSV files in your default text editor. If this happens, open your spreadsheet app and use **File > Open** to open it, or look for an import option. Paddle exports CSV files using `UTF-8/Unicode` character encoding, with a comma as the delimiter. ## Generate a report 1. Go to **Paddle > Reports**. 2. Find the report you want to generate under the **Build reports** tab, then click Build report 3. Use the options to filter the data you want to include on your report. Check the available filter options to learn more about how they work. 4. When you're done, click Generate report 5. Look out for an email from Paddle, then click **Download report** in the email. Each report type has different filter options available. See the individual report pages for details on available filters: - [Transactions](/build/reports/transactions) - [Transaction line items](/build/reports/transaction-line-items) - [Adjustments](/build/reports/adjustments) - [Adjustment line items](/build/reports/adjustment-line-items) - [Products and prices](/build/reports/products-prices) - [Discounts](/build/reports/discounts) - [Payout reconciliation](/build/reports/payout-reconciliation) Use the Paddle API to generate reports programmatically as part of your workflows. Generate reports using the API in three steps: 1. **Create your report** Build a request that includes the kind of report you want to generate and how you want to filter the data. 2. **Check the status of your report** Wait for Paddle to generate the report. 3. **Download the report** Send a request to get the download URL for the report, then download the file. ### Create report Send a request to the `/reports` endpoint to create a report. Include `type` in your request to specify the report kind. Optionally include `filters` to narrow the data. Each filter object needs `name` and `value`, and date filters can use an `operator` for date ranges. If omitted, defaults to data updated in the last 30 days. The report is created as `pending` while Paddle processes your data. `rows` and `expires_at` are `null` until it's `ready`. ### Check report status Reports are created as `pending` initially, which means Paddle is processing them. During this stage, Paddle finds records that match your criteria and generates CSV files of your reports. When Paddle has completed processing your report, its status changes to `ready`. At this point you can download a CSV file with your data. You can check to see if a report is ready by: - Polling the [`/reports/{report_id}`](/api-reference/reports/get-report) endpoint, checking to see if the status is `ready`. - Subscribing to the [`report.updated`](/webhooks/reports/report-updated) webhook. Paddle sends a notification when the status for a report changes to `ready`. ### Download file Send a request to the `/reports/{report_id}/download-url` endpoint to get the download URL for the report. The download URL expires after 72 hours. --- # Adjustment line item reports URL: https://developer.paddle.com/build/reports/adjustment-line-items Generate detailed reports about refunds, credits, and chargebacks, broken down by line item. [Adjustments](/api-reference/adjustments) are records of changes to a transaction, like refunds or credits. They're created automatically by Paddle when making prorated changes to a subscription, or when making changes to an issued invoice. Adjustment line items are the individual items that make up an adjustment. ## When to use - **Analyze refunds and credits by product** Break adjustments down to the individual items so you can see which products are most affected by refunds and credits. - **Audit prorated changes** See line-level detail for adjustments created from subscription updates and downgrades. - **Feed product-level refund analytics** Export itemized adjustment data into BI tools for revenue analysis. ## Report filters When generating adjustment line item reports, you can filter by: - Field - Description --- - **Updated date** - The date an adjustment was last modified. Matches entities modified on or after the start date and before the end date. --- - **Status** - The current status of the adjustment. - Pending approval: Adjustment is pending approval by Paddle. Refunds must be approved by Paddle and are created `pending_approval`. - Approved: Adjustment is approved. Default for credits. Set when Paddle approves a refund that was `pending_approval`. - Rejected: Adjustment has been rejected. Set when Paddle rejects a refund that was `pending_approval`. - Reversed: Adjustment has been reversed. --- - **Type** - Kind of adjustment. - Credit: Credits some or all the related transaction. - Credit reverse: Reversal of a credit for some or all the related transaction. - Refund: Refunds some or all the related transaction. Must be approved by Paddle. - Chargeback: Chargeback for the related transaction. - Chargeback reverse: Reversal of a chargeback for the related transaction. - Chargeback warning: Warning of an upcoming chargeback for the related transaction. ## Report columns Column headings on adjustment line item reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: --- # Adjustment reports URL: https://developer.paddle.com/build/reports/adjustments Generate detailed reports about refunds, credits, and chargebacks. [Adjustments](/api-reference/adjustments) are records of changes to a transaction, like refunds or credits. They're created automatically by Paddle when making prorated changes to a subscription, or when making changes to an issued invoice. ## When to use - **Track refund, credit, and chargeback activity** Monitor adjustment volume and status across your business. - **Identify pending refunds** Surface refunds awaiting Paddle approval. - **Audit financial corrections** Filter by type and status to investigate adjustments tied to specific accounting periods. - **Feed financial systems** Export adjustment data into accounting or BI tools for monthly close. ## Report filters When generating adjustment reports, you can filter by: - Field - Description --- - **Updated date** - The date an adjustment was last modified. Matches entities modified on or after the start date and before the end date. --- - **Status** - The current status of the adjustment. - Pending approval: Adjustment is pending approval by Paddle. Refunds must be approved by Paddle and are created `pending_approval`. - Approved: Adjustment is approved. Default for credits. Set when Paddle approves a refund that was `pending_approval`. - Rejected: Adjustment has been rejected. Set when Paddle rejects a refund that was `pending_approval`. - Reversed: Adjustment has been reversed. --- - **Type** - Kind of adjustment. - Credit: Credits some or all the related transaction. - Credit reverse: Reversal of a credit for some or all the related transaction. - Refund: Refunds some or all the related transaction. Must be approved by Paddle. - Chargeback: Chargeback for the related transaction. - Chargeback reverse: Reversal of a chargeback for the related transaction. - Chargeback warning: Warning of an upcoming chargeback for the related transaction. ## Report columns Column headings on adjustment reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: --- # Discount reports URL: https://developer.paddle.com/build/reports/discounts Generate detailed reports about discounts in your catalog, including their usage, status, and configuration. [Discounts](/api-reference/discounts) let you reduce the amount a customer has to pay for a transaction. Discounts are sometimes called coupons or promo codes. ## When to use - **Audit your discount catalog** Review active, archived, expired, and used discounts in one place. - **Track promotion performance** See redemption counts and how often each discount has been used. - **Investigate specific discount activity** Filter by status and type to surface discounts relevant to a campaign or accounting period. ## Report filters When generating discount reports, you can filter by: - Field - Description --- - **Updated date** - The date a discount was last modified. Matches entities modified on or after the start date and before the end date. --- - **Status** - The current status of the discount. - Active: Discount is active and can be used. - Archived: Discount is archived, so can't be used. - Expired: Discount has expired. Automatically set by Paddle when the `expires_at` date elapses. - Used: Discount has reached the maximum amount of redemptions. Automatically set by Paddle when the `usage_limit` is reached. --- - **Type** - The kind of discount. - Flat: Discounts a transaction by a flat amount, for example `-$100` off the total. - Flat per seat: Discounts a transaction by a flat amount per unit, for example `-$100` each unit. - Percentage: Discounts a transaction by a percentage of the total, for example `10%`. ## Report columns Column headings on discount reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: --- # Payout reconciliation reports URL: https://developer.paddle.com/build/reports/payout-reconciliation Generate detailed reports that connect your Paddle payouts to the underlying transaction movements for accurate financial reconciliation. Payout reconciliation reports help you reconcile payouts to transactions and adjustments to understand how your payout amounts are calculated. A payout is the money Paddle sends you from your sales. If your balance meets the minimum threshold, Paddle creates your payout on the 1st and sends the payment by the 15th. You'll receive funds by the payout method you've set in your settings. ## When to use - **Reconcile payouts to transactions and adjustments** Verify your payout totals against your remittance advice by summing balance movements to calculate how the payout is formed and close the books. - **Audit fees and currency exchange** Review itemized gross, taxes, Paddle, chargeback, and retained fees in both transaction and balance currencies to confirm calculations and investigate variances. - **Analyze revenue and product performance** Break down activity by product, price, country, origin, and billing frequency to see mix shifts, cohort patterns, and subscription dynamics affecting payouts. - **Simplify tax reporting** Split activity by invoice entity, using state and ZIP where applicable, and include tax rates and tax mode to prove local compliance and support accurate indirect tax reporting. - **Automate financial workflows** Generate reports via API to feed into accounting and BI systems so monthly reconciliations, checks, and dashboards run on schedule without manual intervention. ```mermaid flowchart TD A[Receive payout] --> B[Find remittance reference in the dashboard or on your remittance advice] B --> C[Generate payout reconciliation report via the dashboard or API] C --> D[Sum balance_movement_in_balance_currency across all rows] D --> E{Does total equal payout amount
on the remittance advice?} E -->|Yes| F[✓ Reconciled] E -->|No| G[Regenerate the report and re-check the total] G --> H{Still doesn't match?} H -->|No| F H -->|Yes| I[Contact support with your
remittance reference] ``` ## Report filters When generating payout reconciliation reports, you can filter by: | Field | Description | Use case | |-------|-------------|------------| | `remittance_reference` | Filter by a specific payout using its remittance reference. This gives you all transactions that contributed to that particular payout. | Reconciling a specific payout to your bank account. | | `transaction_updated_date` | Filter by the date range when transactions were last updated. Use this to generate reports covering multiple payouts within a time period. | Monthly reconciliation reports or analyzing trends over time. | ## Report columns The report itemizes how gross sales are reduced by taxes, fees, discounts, and adjustments to arrive at your final payout amount. Each deduction has its own column so you can see what impacts your payout: - Fields - Description --- - - `paddle_fee_in_balance_currency` - `paddle_fee_in_transaction_currency` - Processing fees charged by Paddle --- - - `fx_fee_in_balance_currency` - Currency conversion costs for international transactions --- - - `chargeback_fee_in_balance_currency` - `chargeback_fee_in_transaction_currency` - Fees for dispute handling --- - - `retained_fee_in_balance_currency` - `retained_fee_in_transaction_currency` - Amounts kept by Paddle during refunds to cover gateway costs --- - - `tax_in_balance_currency` - `tax_in_transaction_currency` - Customer taxes --- - - `subtotal_in_balance_currency` - `subtotal_in_transaction_currency` - Pre-tax subtotal before any discount was applied --- - - `discount_in_balance_currency` - `discount_in_transaction_currency` - Total discount applied to the transaction --- - - `discount_id_list` - `discount_code_list` - `discount_type_list` - `discount_description_list` - `discount_rate_list` - Comma-separated lists of discount metadata for each transaction --- - - `payout_rebates_and_fees_in_balance_currency` - Manual rebates and SWIFT fees applied to the payout outside the normal transaction flow Each row in the report is one balance movement. The `balance_movement_type` column tells you what kind of row it is, including sales, adjustments, rebates, and SWIFT fees. Every line item that makes up the payout total is represented. Column headings on payout reconciliation reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: ## How to reconcile To reconcile your payout, sum the `balance_movement_in_balance_currency` column across all rows. The total equals the amount shown on your remittance advice document for the payout. Each row's `balance_movement_in_balance_currency` represents the net amount contributed to your payout after all deductions. Each row's balance movement is calculated using the `_in_balance_currency` fields: | Field | Description | |-------|-------------| | `total_gross_in_balance_currency` | The gross amount of the transaction in the balance currency. | | `tax_in_balance_currency` | The tax amount of the transaction in the balance currency. | | `paddle_fee_in_balance_currency` | The Paddle fee amount of the transaction in the balance currency. | | `retained_fee_in_balance_currency` | The retained fee amount of the transaction in the balance currency. | | `fx_fee_in_balance_currency` | The FX fee amount of the transaction in the balance currency. | | `fx_fee_precision_adjustment_in_balance_currency` | The FX fee precision adjustment amount of the transaction in the balance currency. | | `chargeback_fee_in_balance_currency` | The chargeback fee amount of the transaction in the balance currency. | | `payout_rebates_and_fees_in_balance_currency` | Rebate or SWIFT fee amount applied to the payout outside the normal transaction flow. Non-zero only on `rebate` and `swift_fee` rows. | The formula shows how gross sales are reduced by fees and taxes — and adjusted for any rebates and SWIFT fees — to calculate the net amount for each row: ```text total_gross_in_balance_currency - tax_in_balance_currency - paddle_fee_in_balance_currency - retained_fee_in_balance_currency - fx_fee_in_balance_currency - fx_fee_precision_adjustment_in_balance_currency - chargeback_fee_in_balance_currency + payout_rebates_and_fees_in_balance_currency = balance_movement_in_balance_currency ``` Always use the balance currency fields (ending in `_in_balance_currency`) for reconciliation. While transaction currency fields are useful for analyzing individual transactions, they won't roll up to your payout total because of currency conversion, fees, and rounding adjustments. ### How discounts affect the subtotal The `subtotal_*` columns show the pre-tax amount of the transaction *before* any discount was applied. They're derived from `total_gross`, `tax`, and `discount`: ```text total_gross_in_balance_currency - tax_in_balance_currency + discount_in_balance_currency = subtotal_in_balance_currency ``` On adjustment rows, `discount_in_balance_currency` is negative. It represents the reversal of the original discount benefit, so it's correct to add it to the subtotal. ### Discrepancies in totals Every line item that makes up your payout is included as its own row in the report, including manual rebates and SWIFT fees. Summing `balance_movement_in_balance_currency` across all rows should match the amount on your remittance advice exactly. If the totals don't match, the most likely cause is that recent adjustments weren't yet included when you generated the report. Regenerate it and check again. We recommend that you query the report by remittance reference, rather than date range to accurately identify all movements associated with the payout. If you see an unexpected discrepancy after regenerating, [contact support](mailto:support@paddle.com) with your remittance reference. --- # Product and price reports URL: https://developer.paddle.com/build/reports/products-prices Generate detailed reports about your product catalog, including products and prices. [Products](/api-reference/products) and [prices](/api-reference/prices) form your product catalog. They're things that customers buy. ## When to use - **Audit your product catalog** Review all products and prices, including active and archived items. - **Sync catalog with external systems** Export catalog data for accounting, ERP, or BI tools. - **Investigate catalog changes** Filter by updated date to surface recently modified products and prices. ## Report filters When generating product and price reports, you can filter by: - Field - Description --- - **Product updated date** - The date a product was last modified. Matches entities modified on or after the start date and before the end date. --- - **Price updated date** - The date a price was last modified. Matches entities modified on or after the start date and before the end date. --- - **Product status** - The status of the product. - Active: Entity is active and can be used. - Archived: Entity is archived, so can't be used. --- - **Price status** - The status of the price. - Active: Entity is active and can be used. - Archived: Entity is archived, so can't be used. --- - **Price type** - Kind of product or price. - Custom: Non-catalog product or price. Typically created for a specific transaction or subscription. Not returned when listing products or prices or shown in the Paddle dashboard. - Standard: Standard product or price. Can be considered part of your product catalog and reused across transactions and subscriptions easily. --- - **Product type** - Kind of product or price. - Custom: Non-catalog product or price. Typically created for a specific transaction or subscription. Not returned when listing products or prices or shown in the Paddle dashboard. - Standard: Standard product or price. Can be considered part of your product catalog and reused across transactions and subscriptions easily. ## Report columns Column headings on product and price reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: --- # Transaction line item reports URL: https://developer.paddle.com/build/reports/transaction-line-items Generate detailed reports about revenue received, past due invoices, draft and issued invoices, and canceled transactions, broken down by line item. [Transactions](/api-reference/transactions) capture and calculate revenue for both checkouts and invoices. They hold information about a customer purchase. Transaction line items are the individual items that make up a transaction. ## When to use - **Analyze revenue by product or price** Break transactions down to the individual items so you can see which products and prices drive revenue. - **Audit subscription billing** See line-level detail for renewals, one-time charges, and proration. - **Feed product-level analytics** Export itemized transaction data into BI tools for cohort, mix, and product performance analysis. ## Report filters When generating transaction line item reports, you can filter by: - Field - Description --- - **Updated date** - The date a transaction was last modified. Matches entities modified on or after the start date and before the end date. --- - **Status** - The current status of the transaction. - Completed: Transaction is completed. Payment collected successfully and fully processed. - Paid: Transaction is fully paid, but hasn't yet been processed internally. - Draft: Transaction is missing required fields. Typically the first stage of a checkout before customer details are captured. - Ready: Transaction has all the required fields to be marked as `billed` or `completed`. - Billed: Transaction has been updated to `billed`. Billed transactions get an invoice number and are considered a legal record. They can't be changed. Typically used as part of an invoice workflow. - Past due: Transaction is past due. Occurs for automatically-collected transactions when the related subscription is in dunning, and for manually-collected transactions when payment terms have elapsed. - Canceled: Transaction has been updated to `canceled`. If an invoice, it's no longer due. --- - **Origin** - Where or why the transaction was created. - Created via the API: Transaction created via the Paddle API. - Created via the checkout (web): Transaction created automatically by Paddle.js for a checkout. - Scheduled subscription renewal: Transaction created automatically by Paddle as a result of a subscription renewal. - One-time subscription charge: Transaction created automatically by Paddle as a result of a one-time charge for a subscription. - Subscription update: Transaction created automatically by Paddle as a result of an update to a subscription. - Payment method update: Transaction created automatically as part of updating a payment method. May be a zero value transaction. --- - **Currency** - The currency of the transaction. --- - **Collection mode** - How the customer has been asked to pay. - All: Show both modes. - Manual: Payment is collected manually. Customers are sent an invoice with payment terms and can make a payment offline or using a checkout. - Auto: Payment is collected automatically using a checkout initially, then using a payment method on file. ## Report columns Column headings on transaction line item reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: --- # Transaction reports URL: https://developer.paddle.com/build/reports/transactions Generate detailed reports about revenue received, past due invoices, draft and issued invoices, and canceled transactions. [Transactions](/api-reference/transactions) capture and calculate revenue for both checkouts and invoices. They hold information about a customer purchase. ## When to use - **Reconcile revenue and payment status** Verify which transactions completed, are pending, past due, or were canceled across your business. - **Track invoices and dunning** Surface past due invoices, draft transactions, and others that need attention. - **Audit transaction history** Filter by status, origin, currency, or collection mode to investigate specific revenue events. - **Feed financial systems** Export transaction data into accounting or BI tools for monthly close and revenue analysis. ## Report filters When generating transaction reports, you can filter by: - Field - Description --- - **Updated date** - The date a transaction was last modified. Matches entities modified on or after the start date and before the end date. --- - **Status** - The current status of the transaction. - Completed: Transaction is completed. Payment collected successfully and fully processed. - Paid: Transaction is fully paid, but hasn't yet been processed internally. - Draft: Transaction is missing required fields. Typically the first stage of a checkout before customer details are captured. - Ready: Transaction has all the required fields to be marked as `billed` or `completed`. - Billed: Transaction has been updated to `billed`. Billed transactions get an invoice number and are considered a legal record. They can't be changed. Typically used as part of an invoice workflow. - Past due: Transaction is past due. Occurs for automatically-collected transactions when the related subscription is in dunning, and for manually-collected transactions when payment terms have elapsed. - Canceled: Transaction has been updated to `canceled`. If an invoice, it's no longer due. --- - **Origin** - Where or why the transaction was created. - Created via the API: Transaction created via the Paddle API. - Created via the checkout (web): Transaction created automatically by Paddle.js for a checkout. - Scheduled subscription renewal: Transaction created automatically by Paddle as a result of a subscription renewal. - One-time subscription charge: Transaction created automatically by Paddle as a result of a one-time charge for a subscription. - Subscription update: Transaction created automatically by Paddle as a result of an update to a subscription. - Payment method update: Transaction created automatically as part of updating a payment method. May be a zero value transaction. --- - **Currency** - The currency of the transaction. --- - **Collection mode** - How the customer has been asked to pay. - All: Show both modes. - Manual: Payment is collected manually. Customers are sent an invoice with payment terms and can make a payment offline or using a checkout. - Auto: Payment is collected automatically using a checkout initially, then using a payment method on file. ## Report columns Column headings on transaction reports mirror fields in [the Paddle API](/api-reference). Data is provided in the following columns: --- # Docs MCP server URL: https://developer.paddle.com/sdks/ai/docs-mcp Connect AI agents to current Paddle documentation, the OpenAPI specification, and SDK references using the Paddle docs MCP server. The Paddle docs MCP server gives AI agents full, current knowledge of Paddle so you can build, integrate, and troubleshoot without relying on stale training data. ## How it works AI agents are only as good as what they know. Without access to current documentation, they can give outdated advice, miss important details, or confidently suggest things that no longer work. The Paddle docs MCP server gives your agent access to up-to-date-Paddle documentation. Install it once and your AI agent can search all public Paddle resources, including: - **Paddle docs** Every guide and reference published at [developer.paddle.com](https://developer.paddle.com). - **OpenAPI specification** The full, current [Paddle API specification](https://github.com/PaddleHQ/paddle-openapi) covering endpoints, parameters, response shapes, and webhooks. - **SDK references** Up-to-date references for all Paddle SDKs, including [Node.js](https://github.com/PaddleHQ/paddle-node-sdk), [Python](https://github.com/PaddleHQ/paddle-python-sdk), [Go](https://github.com/PaddleHQ/paddle-go-sdk), and [PHP](https://github.com/PaddleHQ/paddle-php-sdk). The docs MCP server gives your AI agent knowledge of Paddle. To let your agent take actions in your Paddle account, set up the [Paddle MCP server](/sdks/ai/paddle-mcp). ## Install the docs MCP server The server runs remotely on [Kapa.ai](https://kapa.ai). Install the Claude Code plugin to add the Paddle MCP servers and skills to your project in one command. See [Build with Claude Code](/get-started/ai/claude-code) Run the following command in your terminal: ```bash claude mcp add --transport http paddle-docs https://paddlehq.mcp.kapa.ai ``` 1. Open Claude Desktop and go to **Settings > Developer > Edit Config**. 2. In the `claude_desktop_config.json` file, add the `paddle-docs` server configuration to the `mcpServers` object. 3. Save the file and restart Claude Desktop. ```json { "mcpServers": { "paddle-docs": { "command": "npx", "args": ["mcp-remote", "https://paddlehq.mcp.kapa.ai"] } } } ``` 1. Run `codex mcp add --transport http paddle-docs https://paddlehq.mcp.kapa.ai` in your terminal. 2. Run `codex mcp login paddle-docs --scopes openid` to print an authorization URL. 3. Manually append `&resource=https%3A%2F%2Fpaddle-docs.mcp.kapa.ai%2F` to the end of the authorization URL. 4. Open the authorization URL in your browser and complete the authentication flow. The full URL should look something like: ```text https://mcp.kapa.ai/auth/public/authorize?...&scope=openid&resource=https%3A%2F%2Fpaddle-docs.mcp.kapa.ai%2F ``` Appending `resource` is a workaround for a known bug in Codex. Without it, authorization fails with `error=server_error`. ### One-click install Install the Paddle docs MCP in Cursor ### Manual install 1. Open **Cursor Settings > MCP Tools** (or Command/Ctrl + Shift + J > **MCP Tools**). 2. Click Add Custom MCP to open the `mcp.json` file. 3. Add the `paddle-docs` server configuration to the `mcpServers` object. 4. Save the file and restart Cursor. ```json { "mcpServers": { "paddle-docs": { "type": "http", "url": "https://paddlehq.mcp.kapa.ai" } } } ``` ### One-click install {% external-link url="vscode:mcp/install?%7B%22name%22%3A%22paddle-docs%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fpaddlehq.mcp.kapa.ai%22%7D" icon="vscode" %} Install the Paddle docs MCP in VS Code ### Manual install 1. Press Command/Ctrl + Shift + P to open the command palette. 2. Search and select MCP: Add Server.... 3. Select **HTTP** as the server type. 4. Enter `https://paddlehq.mcp.kapa.ai` as the server URL. 5. Enter `paddle-docs` as the server name. 6. Click User Settings. 7. Restart VS Code. Mistral Vibe configures MCP servers in `~/.vibe/config.toml`. 1. Add the `paddle-docs` server to `~/.vibe/config.toml`: ```toml [[mcp_servers]] name = "paddle-docs" transport = "http" url = "https://paddlehq.mcp.kapa.ai" ``` 2. Restart Vibe so it picks up the new server. See the Mistral Vibe configuration docs for more options. {% external-link url="raycast://mcp/install?%7B%22name%22%3A%22paddle-docs%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%20%22mcp-remote%22%2C%20%22https%3A%2F%2Fpaddlehq.mcp.kapa.ai%22%5D%7D" icon="raycast" %} Install the Paddle MCP in Raycast MCP server support in ChatGPT Desktop requires developer mode. 1. Open ChatGPT Desktop and go to **Settings > Features**. 2. Enable **Developer mode**. 3. Navigate to **Settings > MCP Servers > Add Server**. 4. Enter `paddle-docs` as the server name and `https://paddlehq.mcp.kapa.ai` as the URL. 5. Complete the authentication flow when prompted. 1. In the **Agent** sidebar, click the button, then Open MCP Config File. 2. Add the `paddle-docs` server configuration to the `mcpServers` object. 3. Save the file and reload Windsurf. 4. Complete the authentication flow when prompted. ```json { "mcpServers": { "paddle-docs": { "command": "npx", "args": [ "mcp-remote", "https://paddlehq.mcp.kapa.ai" ] } } } ``` 1. Run `npx mcp-remote https://paddlehq.mcp.kapa.ai` in your terminal to authenticate your session. 2. Add the `paddle-docs` server to the `context_servers` block in `~/.config/zed/settings.json`. 3. Reload Zed. ```json { "context_servers": { "paddle-docs": { "source": "custom", "command": "npx", "args": [ "-y", "mcp-remote", "https://paddlehq.mcp.kapa.ai" ], "env": {} } } } ``` 1. In the **Agent** sidebar, click the button, then MCP Servers. 2. Click Manage MCP Servers, then View raw config. 3. Add the `paddle-docs` server configuration to the `mcpServers` object. 4. Save the file and click Refresh. 5. Complete the authentication flow when prompted. ```json { "mcpServers": { "paddle-docs": { "command": "npx", "args": [ "-y", "mcp-remote", "https://paddlehq.mcp.kapa.ai" ] } } } ``` For any other MCP-compatible client, configure the server manually with these details: | Setting | Value | | ------- | ------------------------------ | | Name | `paddle-docs` | | Type | `http` | | URL | `https://paddlehq.mcp.kapa.ai` | ```json { "mcpServers": { "paddle-docs": { "type": "http", "url": "https://paddlehq.mcp.kapa.ai" } } } ``` Once installed, you may be prompted to authenticate with [Kapa.ai](https://kapa.ai). This check keeps the service reliable and secure for everyone. You may have to reauthenticate from time to time. If requests fail, check your MCP settings to make sure you're still authenticated. ## Use the server Once connected, your AI agent can search the Paddle docs, OpenAPI spec, and SDK references automatically when needed. You don't need to configure it to use it. Most agents show you when an MCP server is used. If yours doesn't, ask it to use up-to-date information from the Paddle documentation. That encourages it to call the tool rather than rely on training data. ## Example prompts Use these example prompts to get started. ### Integrate Paddle ```markdown Using the latest Paddle documentation, what's the correct way to verify a webhook signature in my current Node.js implementation? ``` ### Understand the API ```markdown Check the Paddle docs and tell me which fields I need to include when creating a one-time charge on an existing subscription. ``` ### Debug an issue ```markdown Looking at the current Paddle documentation, what are the most likely causes of a `too_many_requests` error when calling the transactions endpoint, and how should I handle it? ``` ### Plan a migration ```markdown Based on the current Paddle docs, what are the main differences between Paddle Classic and Paddle Billing, and what would I need to update in my existing integration to migrate? ``` ### Build a checkout ```markdown Using up-to-date Paddle documentation, plan how to add an overlay, one-page Paddle Checkout to my existing Next.js pricing page. ``` ### Manage subscriptions ```markdown Using up-to-date Paddle documentation, give me detailed instructions on how to open a customer portal for my customers to manage their subscriptions. ``` --- # Next.js starter kit URL: https://developer.paddle.com/sdks/starter-kits/nextjs Production-ready Next.js SaaS starter kit with Paddle Billing, Supabase auth, and Vercel deployment. Includes a localized pricing page, integrated checkout, and customer subscription management screens. A complete Next.js app integrated with Paddle Billing. Drop-in pricing page, inline checkout, customer-facing subscription management, and webhook syncing — ready to deploy to Vercel. ![Grid of logos used in the starter kit: Paddle, Supabase, Next.js, and Vercel.](/src/assets/images/tmp-build/vercel-logos-20240912.svg) View source code, report issues, and contribute on GitHub. Try the starter kit before you get started. Step-by-step walkthrough from setup to test payment. ## What's included - A three-tier pricing page, fully localized for 200+ markets. - A high-converting inline checkout built with [Paddle Checkout](/concepts/sell/self-serve-checkout). - User management and authentication via [Supabase](https://supabase.com/). - Customer-facing screens to manage subscriptions and payments. - Automatic syncing of customer and subscription data between Paddle and your app [using webhooks](/webhooks). ## Tech stack | Layer | Tool | | ------------ | ------------------------------------------------------------------------------------- | | Framework | [Next.js](https://nextjs.org/) | | Hosting | [Vercel](https://vercel.com/) | | Auth + DB | [Supabase](https://supabase.com/) | | Payments | [Paddle Node.js SDK](/sdks/libraries/node) + [Paddle.js wrapper](/sdks/libraries/paddle-js-wrapper) | | Styling | [Tailwind CSS](https://tailwindcss.com/) | | Components | [shadcn/ui](https://ui.shadcn.com/) | ## Requirements - A [Vercel](https://vercel.com/) account. - A [Supabase](https://supabase.com/) account. - A Git provider (GitHub recommended). - Node.js v20+ if you plan to develop locally. ## Get started Deploy the starter kit to Vercel: {% external-link url="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-nextjs-starter-kit&env=PADDLE_API_KEY,PADDLE_NOTIFICATION_WEBHOOK_SECRET,NEXT_PUBLIC_PADDLE_ENV,NEXT_PUBLIC_PADDLE_CLIENT_TOKEN&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6&external-id=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-nextjs-starter-kit%2Ftree%2Fmain" icon="vercel" %} Deploy Paddle Next.js SaaS starter kit to Vercel in one-click Alternatively, follow [the complete tutorial](/get-started/starter-kits/nextjs-saas) to deploy the starter kit manually. --- # Node.js SDK URL: https://developer.paddle.com/sdks/libraries/node Install, authenticate, and make your first request with the official Paddle Node.js SDK for server-side JavaScript and TypeScript. The Paddle Node.js SDK integrates Paddle Billing with server-side JavaScript and TypeScript apps. It ships with TypeScript definitions, iterator-based pagination, and helpers for webhook signature verification. View source code and report issues on GitHub. View and install on npm. See recent releases and changes. To open checkouts, pricing pages, and integrate Retain on the client-side, use [Paddle.js](/paddle-js) or the [Paddle.js wrapper](/sdks/libraries/paddle-js-wrapper). ## Requirements Node.js 18 or later. ## Install ```bash pnpm add @paddle/paddle-node-sdk ``` ```bash yarn add @paddle/paddle-node-sdk ``` ```bash npm install @paddle/paddle-node-sdk ``` ## Authenticate Create an API key in **Paddle > Developer tools > Authentication**, then pass it when you instantiate the client. API keys are environment-specific. Use a sandbox key for sandbox, a live key for production. ```typescript import { Paddle, Environment } from '@paddle/paddle-node-sdk'; const paddle = new Paddle(process.env.PADDLE_API_KEY!, { environment: Environment.sandbox, }); ``` Omit the `environment` option, or set it to `Environment.production`, to use the live API. ## Make your first request List the first page of products in your catalog: ```typescript import { Paddle, Environment } from '@paddle/paddle-node-sdk'; const paddle = new Paddle(process.env.PADDLE_API_KEY!, { environment: Environment.sandbox, }); const products = paddle.products.list(); const firstPage = await products.next(); for (const product of firstPage) { console.log(product.id, product.name); } ``` `paddle.products.list()` returns an iterator. Call `next()` to fetch the first page, check `products.hasMore` to see whether more pages are available, or use `for await (const product of paddle.products.list())` to iterate every product across all pages. ## Naming conventions The Paddle API uses `snake_case`. The Node.js SDK uses `camelCase` to match JavaScript conventions. Field names on requests and responses are `camelCase`. For example, `customData` rather than `custom_data`. ## Next steps - Understand [shared patterns](/sdks/libraries) for pagination, idempotency, retries, and error handling across SDKs. - Browse the [API reference](/api-reference) for every resource and operation the SDK exposes. - Work against [sandbox](/sdks/sandbox) while you build, then follow the [go-live checklist](/build/go-live-checklist) to switch environments. --- # OpenAPI spec URL: https://developer.paddle.com/sdks/specs/openapi Download the Paddle OpenAPI specification to generate client libraries, power editor tooling, or import into API exploration tools. Paddle publishes an OpenAPI 3.1 specification for the Paddle Billing API. Use it to generate client libraries, drive editor autocomplete, or explore the API in tools like Scalar, Redocly, or Stoplight Studio. ## Get the spec To get started, fork the Paddle OpenAPI spec from GitHub: Fork the Paddle OpenAPI spec from GitHub The spec is available at [github.com/PaddleHQ/paddle-openapi](https://github.com/PaddleHQ/paddle-openapi). If you want, you can also: - Clone the repository and pin to a tag for reproducible builds. - Download the latest `openapi.yaml` directly from the `main` branch. ## When to use - **Codegen** Generate a client in a language Paddle doesn't ship an SDK for. - **Editor tooling** Point tools like IntelliJ HTTP Client, REST Client for VS Code, or Jetbrains HTTP Client at the spec for inline autocomplete and validation. - **Import into a local viewer** Explore the API in Scalar, Redocly, or Stoplight Studio. - **Contract testing** Validate that a Paddle-facing integration matches the schema the API promises. Some codegen tools don't fully support OpenAPI 3.1, so may be incompatible with the Paddle OpenAPI spec. See [issue #5 on GitHub for more details](https://github.com/PaddleHQ/paddle-openapi/issues/5). ## Use an AI agent to work with the spec You don't need to get the spec from GitHub when using the docs MCP server. The [docs MCP server](/sdks/ai/docs-mcp) is trained on the OpenAPI spec, as well as the rest of the Paddle documentation. ## Next steps - Browse the [API reference](/api-reference) for every resource and operation the SDK exposes. - Check the developer changelog to see what's changed in the API. - Grab the Postman collection instead to explore the API in a more visual way. --- # Pay gem (Ruby on Rails) URL: https://developer.paddle.com/sdks/community/pay-gem Community-maintained Ruby on Rails gem that provides an abstraction over Paddle Billing alongside other payment processors. [Pay](https://github.com/pay-rails/pay) is a community-maintained payments engine for Ruby on Rails 6.0 and later. It abstracts over Paddle Billing, Stripe, Braintree, and Lemon Squeezy with a single API for customers, subscriptions, charges, and webhooks. The pay gem is maintained by the [pay-rails](https://github.com/pay-rails) community, not by Paddle. For support, use the [Pay issue tracker](https://github.com/pay-rails/pay/issues). View source code and report issues on GitHub. View and install on RubyGems. See recent releases and changes. ## Requirements - Ruby. - Rails 7.0 or later. ## Install Add Pay to your `Gemfile`: ```ruby gem "pay", "~> 11.5" gem "paddle", "~> 2.9" ``` Run `bundle install` and follow the [installation guide](https://github.com/pay-rails/pay/blob/main/docs/1_installation.md) to generate migrations and configure your User model. ## Configure Pay reads Paddle credentials from environment variables, or the equivalent Rails credentials: | Variable | Purpose | | ------------------------------- | ------------------------------------------------------------------------------------------- | | `PADDLE_BILLING_ENVIRONMENT` | `sandbox` or `production`. Defaults to `production`. | | `PADDLE_BILLING_API_KEY` | API key from **Paddle > Developer tools > Authentication**. | | `PADDLE_BILLING_CLIENT_TOKEN` | Client-side token for Paddle.js, created in the same place. | | `PADDLE_BILLING_SIGNING_SECRET` | Notification destination secret key, used to verify webhook signatures. | ## Quick example Create a Paddle customer for a Pay-enabled model: ```ruby user.set_payment_processor :paddle_billing user.payment_processor.api_record ``` Subscriptions are created using webhooks. When Paddle sends a `subscription.created` notification, Pay creates the subscription record automatically. See the [subscriptions guide](https://github.com/pay-rails/pay/blob/main/docs/6_subscriptions.md) for more. ## Links - [Paddle Billing guide in the Pay docs](https://github.com/pay-rails/pay/blob/main/docs/paddle_billing/1_overview.md) - [Pay issue tracker](https://github.com/pay-rails/pay/issues) --- # Pricing display URL: https://developer.paddle.com/sdks/components/pricing-display Full pricing section with localized prices, an interval toggle, and Paddle overlay checkout on selection. ## What it does A full pricing page with localized prices, a billing interval toggle, and Paddle overlay checkout on plan selection. It composes [pricing cards](/sdks/components/pricing-cards), [billing interval toggle](/sdks/components/billing-interval-toggle), and Paddle.js together to deliver a complete pricing section. ## When to use - You're building a pricing page and need a tier comparison and checkout experience flow in one drop-in component. - You want customers to be able to switch between monthly and yearly billing without a page reload. - You need a single component that also doubles as a plan-switcher inside a logged-in account. Use the `currentPriceIds` prop. If you need finer control over the layout, remix the [pricing cards](/sdks/components/pricing-cards) and [billing interval toggle](/sdks/components/billing-interval-toggle) components directly. --- # Laravel Cashier Paddle URL: https://developer.paddle.com/sdks/community/laravel-cashier Official Laravel package that provides a fluent interface to Paddle Billing subscription services, including swaps, pausing, quantities, and grace periods. [Laravel Cashier Paddle](https://github.com/laravel/cashier-paddle) is a Laravel package that provides a fluent interface to Paddle Billing. It wraps subscription management — swaps, pausing, quantities, cancellation grace periods — so you don't have to write the boilerplate. Cashier Paddle is maintained by the [Laravel team](https://github.com/laravel), not by Paddle. For support, use the [Cashier Paddle issue tracker](https://github.com/laravel/cashier-paddle/issues). View source code and report issues on GitHub. View and install on Packagist. See recent releases and changes. ## Install Require the package via Composer: ```bash composer require laravel/cashier-paddle ``` Follow the [Laravel documentation](https://laravel.com/docs/cashier-paddle) for the full install — publishing migrations, configuring environment variables, and wiring up the `Billable` trait on your User model. ## Configure Cashier Paddle reads configuration from standard Laravel environment variables: ```env PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token PADDLE_API_KEY=your-paddle-api-key PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret" PADDLE_SANDBOX=true ``` Create the API key and client-side token in **Paddle > Developer tools > Authentication** and the webhook secret on your notification destination in **Paddle > Developer tools > Notifications**. ## Quick example Load Paddle.js by placing the `@paddleJS` Blade directive right before your application layout's closing `` tag: ```blade @paddleJS ``` Open a checkout for a single price inside a route: ```php use Illuminate\Http\Request; Route::get('/buy', function (Request $request) { $checkout = $request->user() ->checkout('pri_01hsxyh9txq4rzbrhbyngkhy46') ->returnTo(route('dashboard')); return view('checkout', ['checkout' => $checkout]); }); ``` Render the checkout in a Blade template using the Paddle.js button component provided by Cashier: ```blade Buy ``` --- # Paddle MCP server URL: https://developer.paddle.com/sdks/ai/paddle-mcp Give AI agents access to your Paddle account so they can read data and take actions across pricing, billing, and integration workflows. The Paddle MCP server lets AI agents interact with your Paddle account. Instead of navigating the dashboard or making API calls, you can ask an agent in natural language to handle pricing, billing, support, and integration workflows. The Paddle MCP server has access to the data in your account. Review every action the agent takes before and after execution. ## How it works The Paddle MCP server is a remote, hosted MCP server. Your agent connects to it over HTTPS using your Paddle API key as a Bearer token, then it can: - Build and evolve your pricing model, including regional pricing and migrations. - Investigate failed payments and resolve billing issues. - Generate reports and analyze revenue, refunds, and transaction patterns. - Process subscription upgrades, downgrades, pauses, and cancellations. - Onboard enterprise customers with custom pricing and manual invoicing. - Generate integration code, configure webhooks, and simulate events for testing. Paddle hosts an MCP server for each [environment](/sdks/sandbox): | Environment | URL | | ----------- | ------------------------------------------------------------- | | Sandbox | https://sandbox-mcp.paddle.com/mcp | | Live | https://mcp.paddle.com/mcp | Sandbox API keys (prefixed `pdl_sdbx_`) only authenticate against the sandbox URL. Live keys only authenticate against the live URL. A mismatch surfaces as an authentication failure. The Paddle MCP server lets your agent take actions in your account. To give your agent up-to-date knowledge of Paddle, set up the [docs MCP server](/sdks/ai/docs-mcp). ## Codemode The Paddle MCP server uses [a codemode interface](https://blog.cloudflare.com/code-mode/). This means instead of exposing every Paddle API operation as a separate MCP tool, the server exposes three tools that together cover the full API: | Tool | What it does | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `search` | Looks up Paddle API methods by name or description. Returns the method path, parameters, and response shape so the agent knows how to call it. | | `execute` | Runs a JavaScript async function that chains one or more Paddle API calls in a single invocation. Variables don't persist between calls. | | `report_missing_tool` | Lets the agent flag a missing capability so we can prioritize it. This doesn't call the Paddle API, it reports the missing tool to the Paddle team. | A typical workflow looks like this: - The agent calls `search` to confirm the method name and parameter shape. - The agent then calls `execute` with code that chains the relevant API calls. - Result is returned to the agent. Codemode works because agents are better at writing code to call tools than at calling tools directly. [Research shows](https://github.com/imran31415/codemode_python_benchmark) it results in 60% faster execution, 68% fewer tokens, and 88% fewer API round trips while maintaining equal accuracy. As a user of the MCP server, you don't need to do anything differently. The server handles the codemode interface for you. ## Permissions and destructive actions The Paddle MCP server doesn't gate destructive operations on its own. You control what the agent can do by scoping the Paddle API key you connect with. - **Hard scoping**: [Create an API key](/api-reference/about/authentication) with only the [permissions](/api-reference/about/permissions) the agent needs. Start with read-only permissions while you're evaluating, then widen as you build confidence. - **Soft warnings**: Destructive operations are flagged with a `destructiveHint` annotation, and the server attaches a `warning` field to the corresponding `execute` response. There's no guarantee agents will follow the warning, so you should always review each `execute` before it runs. - **Client-side approval**: We recommend enabling "ask permission" mode in your MCP client so you see and approve each `execute` before it runs. Agents in "auto" mode make their own judgement call about whether to execute an operation annotated as destructive based on the intent of your prompt and the context of the conversation. ## Before you begin [Create an API key](/api-reference/about/authentication) with the [permissions](/api-reference/about/permissions) needed for the work you want the agent to do. Sandbox keys are prefixed `pdl_sdbx_`. ## Install the MCP server The Paddle MCP server runs remotely. We recommend adding both environments under separate server names: `paddle-sandbox` and `paddle-live`. This means that your agent can route prompts to the right environment, and help you port data from one environment to the other. Install the Claude Code plugin to add the Paddle MCP servers and skills to your project in one command. See [Build with Claude Code](/get-started/ai/claude-code) Run these commands, replacing `SANDBOX_API_KEY` and `LIVE_API_KEY` with the matching sandbox or live key: ```bash claude mcp add --transport http paddle-sandbox https://sandbox-mcp.paddle.com/mcp --header "Authorization: Bearer SANDBOX_API_KEY" ``` ```bash claude mcp add --transport http paddle-live https://mcp.paddle.com/mcp --header "Authorization: Bearer LIVE_API_KEY" ``` 1. Open Claude Desktop and go to **Settings > Developer > Edit Config**. 2. Add the `paddle-sandbox` and `paddle-live` servers to the `mcpServers` object. Claude Desktop doesn't support remote HTTP transport natively, so we proxy through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote). 3. Replace `SANDBOX_API_KEY` and `LIVE_API_KEY` with your sandbox and live keys. 4. Save the file and restart Claude Desktop. ```json { "mcpServers": { "paddle-sandbox": { "command": "npx", "args": [ "-y", "mcp-remote", "https://sandbox-mcp.paddle.com/mcp", "--header", "Authorization: Bearer SANDBOX_API_KEY" ] }, "paddle-live": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.paddle.com/mcp", "--header", "Authorization: Bearer LIVE_API_KEY" ] } } } ``` Run these commands, replacing `SANDBOX_API_KEY` and `LIVE_API_KEY` with the matching sandbox or live key: ```bash codex mcp add paddle-sandbox --url https://sandbox-mcp.paddle.com/mcp --bearer-token-env-var SANDBOX_API_KEY ``` ```bash codex mcp add paddle-live --url https://mcp.paddle.com/mcp --bearer-token-env-var LIVE_API_KEY ``` ### One-click install Install the Paddle MCP (sandbox) in Cursor Install the Paddle MCP (live) in Cursor Before clicking Install, replace `YOUR_API_KEY` with your sandbox or live keys. ### Manual install 1. Open **Cursor Settings > MCP Tools** (or Command/Ctrl + Shift + J > **MCP Tools**). 2. Click Add Custom MCP to open the `mcp.json` file. 3. Add the `paddle-sandbox` and `paddle-live` servers to the `mcpServers` object. 4. Replace `SANDBOX_API_KEY` and `LIVE_API_KEY` with your sandbox and live keys. 5. Save the file and restart Cursor. ```json { "mcpServers": { "paddle-sandbox": { "type": "http", "url": "https://sandbox-mcp.paddle.com/mcp", "headers": { "Authorization": "Bearer SANDBOX_API_KEY" } }, "paddle-live": { "type": "http", "url": "https://mcp.paddle.com/mcp", "headers": { "Authorization": "Bearer LIVE_API_KEY" } } } } ``` ### One-click install {% external-link url="vscode:mcp/install?%7B%22name%22%3A%22paddle-sandbox%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//sandbox-mcp.paddle.com/mcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20YOUR_API_KEY%22%7D%7D" icon="vscode" %} Install the Paddle MCP (sandbox) in VS Code {% external-link url="vscode:mcp/install?%7B%22name%22%3A%22paddle-live%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A//mcp.paddle.com/mcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20YOUR_API_KEY%22%7D%7D" icon="vscode" %} Install the Paddle MCP (live) in VS Code After installing, click the settings button and choose **Show configuration (JSON)** in the menu, then replace `YOUR_API_KEY` with your sandbox or live keys. ### Manual install 1. Press Command/Ctrl + Shift + P to open the command palette. 2. Search and select MCP: Open workspace folder MCP configuration... option. 3. Add the `paddle-sandbox` and `paddle-live` servers to the `mcpServers` object. 4. Replace `SANDBOX_API_KEY` and `LIVE_API_KEY` with your sandbox and live keys. 5. Save the file and restart VS Code. ```json { "servers": { "paddle-live": { "type": "http", "url": "https://mcp.paddle.com/mcp", "headers": { "Authorization": "Bearer LIVE_API_KEY" } }, "paddle-sandbox": { "type": "http", "url": "https://sandbox-mcp.paddle.com/mcp", "headers": { "Authorization": "Bearer SANDBOX_API_KEY" } } }, "inputs": [] } ``` Mistral Vibe configures MCP servers in `~/.vibe/config.toml`. 1. Export your Paddle API keys in your shell profile: ```sh export PADDLE_SANDBOX_API_KEY= # Your sandbox API key export PADDLE_LIVE_API_KEY= # Your live API key ``` 2. Add the `paddle-sandbox` and `paddle-live` servers to `~/.vibe/config.toml`: ```toml [[mcp_servers]] name = "paddle-sandbox" transport = "http" url = "https://sandbox-mcp.paddle.com/mcp" api_key_env = "PADDLE_SANDBOX_API_KEY" api_key_header = "Authorization" api_key_format = "Bearer {token}" [[mcp_servers]] name = "paddle-live" transport = "http" url = "https://mcp.paddle.com/mcp" api_key_env = "PADDLE_LIVE_API_KEY" api_key_header = "Authorization" api_key_format = "Bearer {token}" ``` 3. Restart Vibe so it picks up the new servers. See the Mistral Vibe configuration docs for more options. {% external-link url="raycast://mcp/install?%7B%22name%22%3A%22paddle-sandbox%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-remote%22%2C%22https%3A//sandbox-mcp.paddle.com/mcp%22%2C%22--header%22%2C%22Authorization%3A%20Bearer%20YOUR_API_KEY%22%5D%7D" icon="raycast" %} Install the Paddle MCP (sandbox) in Raycast {% external-link url="raycast://mcp/install?%7B%22name%22%3A%22paddle-live%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-remote%22%2C%22https%3A//mcp.paddle.com/mcp%22%2C%22--header%22%2C%22Authorization%3A%20Bearer%20YOUR_API_KEY%22%5D%7D" icon="raycast" %} Install the Paddle MCP (live) in Raycast After installing, replace `YOUR_API_KEY` with your Paddle API key. For any other MCP-compatible client, configure a server with these details: | Setting | Sandbox | Live | | ------- | ------------------------------------ | ---------------------------- | | Name | `paddle-sandbox` | `paddle-live` | | Type | `http` | `http` | | URL | `https://sandbox-mcp.paddle.com/mcp` | `https://mcp.paddle.com/mcp` | | Header | `Authorization: Bearer SANDBOX_API_KEY` | `Authorization: Bearer LIVE_API_KEY` | ```json { "mcpServers": { "paddle-sandbox": { "type": "http", "url": "https://sandbox-mcp.paddle.com/mcp", "headers": { "Authorization": "Bearer SANDBOX_API_KEY" } }, "paddle-live": { "type": "http", "url": "https://mcp.paddle.com/mcp", "headers": { "Authorization": "Bearer LIVE_API_KEY" } } } } ``` If your client doesn't support remote HTTP transport, proxy through `mcp-remote`: ```bash npx -y mcp-remote https://sandbox-mcp.paddle.com/mcp --header "Authorization: Bearer YOUR_API_KEY" ``` ## Use the server Once installed, the agent sees two parallel toolsets for sandbox and live. Be explicit in your prompts about which environment you mean: "create a product in sandbox" routes to `paddle-sandbox`, "create the equivalent product in live" routes to `paddle-live`. If you enable `--dangerously-skip-permissions` or similar setting in your client, your agent can run tools without your approval. This includes using the Paddle MCP server. ## Example prompts Use these example prompts to get started. Mention `paddle-sandbox` or `paddle-live` explicitly so the agent picks the right toolset. ### Manage your pricing Describe your pricing structure to the agent — tiers, billing cycles, trial periods, regional overrides — and let it create the products and prices. For migrations, walk through old plans, grandfather pricing, and archiving the old catalog step by step. ```markdown Using paddle-sandbox, set up three subscription plans for my collaboration SaaS: Basic at $29/mo or $290/yr (14-day trial), Pro at $79/mo or $790/yr (14-day trial), Advanced at $299/mo or $2,990/yr (14-day trial). Also add a seats add-on at $10/seat/month or $100/seat/year, quantity 1-1000. After you create everything, show me the products and prices in a markdown table. ``` ### Debug and resolve billing issues Give the agent a customer email or ID, a time range, and the issue to investigate. It can check subscription history, payment attempts, adjustments, and scheduled changes, then recommend or execute a fix. ```markdown Using paddle-live, investigate the failed payment for sarah@example.com. Think step-by-step: when it failed, whether it's recurring, what plan they're on, whether there are scheduled changes, and details of the payment method. If you find multiple failures, check whether other subscriptions for this customer show the same pattern. ``` ### Understand company performance Ask the agent to generate a [report](/build/reports) and analyze it. Reports arrive as CSV files — the agent can retrieve them once ready. ```markdown Using paddle-live, generate a transaction_line_items report for the past 6 months, filtered for completed and canceled statuses across all currencies. Identify our three highest- and lowest-revenue product/price combinations, flag pricing anomalies across regions, and recommend adjustments. ``` ### Handle subscription and lifecycle changes For upgrades, downgrades, pauses, or cancellations, always ask for a preview first so you see the financial impact before the change is applied. ```markdown Using paddle-live, customer john@startup.com wants to upgrade from Basic ($29/mo) to Pro ($79/mo) immediately. Preview the change, show me the current and new amounts plus any proration, then wait for my approval before executing. ``` ### Onboard enterprise customers Paste the quote or contract details, include tax ID and billing contacts, and ask the agent to create custom pricing, configure manual invoicing, and preview the first invoice before execution. ```markdown Using paddle-live, set up Example Inc. as an enterprise customer: 100 licenses at $45/user/month billed annually in USD, plus a $6,000/year Premium Support flat fee. Billing contact bob@example.com, tax ID 12-3456789, address 123 Business Park, New York, NY 10001. Preview the full transaction before creating anything. ``` ### Integrate and test Paddle Use the agent to scaffold integration code, configure notification destinations, and simulate webhook events end to end. ```markdown Using paddle-sandbox, create a notification destination for my Next.js webhook endpoint at https://example.com/api/webhooks, subscribed to subscription.created, subscription.updated, and transaction.completed. Then simulate a subscription.created event so I can verify my handler. ``` ## Best practices - **Mention Paddle explicitly.** "Find a customer in Paddle" is less ambiguous than "find a customer." - **Name the environment.** Prefix prompts with "Using paddle-sandbox" or "Using paddle-live" so the agent routes to the right toolset. - **Provide all the context.** Customer IDs, time ranges, currencies — pass them in rather than letting the agent guess. - **Ask for a plan first.** For complex work, get the agent to draft a plan before executing. Approve or edit, then run. - **State your goal.** Tell the agent what success looks like and what format you want the output in. - **Iterate.** If the first response misses, refine the prompt rather than starting over. --- # Postman collection URL: https://developer.paddle.com/sdks/specs/postman Import the Paddle Postman collection to explore and test the Paddle Billing API interactively. Postman is the quickest way to explore and test the Paddle Billing API. The Paddle Postman collection includes every operation, so you can try the API interactively before writing code. Paddle are a verified publisher on the Postman Public API network, which means our API meets a high standard of quality, reliability, and AI-readiness. ## Get the collection To get started, for the Paddle Postman collection. Postman walks you through authentication and making your first request. {% external-link url="https://god.gw.postman.com/run-collection/29428794-16aca740-3ad6-4c1a-97be-05dbc504b4c3?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D29428794-16aca740-3ad6-4c1a-97be-05dbc504b4c3%26entityType%3Dcollection%26workspaceId%3D5ed2587a-f47e-43e0-810c-0c3e782bca12#?env%5BSandbox%5D=W3sia2V5IjoiYmFzZVVybCIsInZhbHVlIjoiaHR0cHM6Ly9zYW5kYm94LWFwaS5wYWRkbGUuY29tIiwiZW5hYmxlZCI6dHJ1ZSwidHlwZSI6ImRlZmF1bHQifSx7ImtleSI6ImJlYXJlclRva2VuIiwidmFsdWUiOiIiLCJlbmFibGVkIjp0cnVlLCJ0eXBlIjoic2VjcmV0In1d" icon="postman" %} Fork our Postman collection to get started with the API ## When to use - **Quickstart** Fork the collection in one-click, let Postman walk you through authentication, then make your first request. - **Explore** Tap through the Paddle API to understand shapes, required fields, and response structures. - **Codegen** Generate a client in a language Paddle doesn't ship an SDK for. - **Prototype** Test an integration idea with real sandbox data before committing to implementation. Use [Agent Mode in Postman](https://www.postman.com/product/agent-mode/) to explore, debug, and build APIs using natural language. Postman AI can fix broken requests, write test scripts, generate docs, organize collections, and build custom MCP servers. ## Authenticate When you fork the collection, Postman guides you through setting the `bearerToken` and `baseUrl` environment variables: | Variable | Where to find it | | -------------- | --------------------------------------------------------------------------- | | `bearerToken` | **Paddle > Developer tools > Authentication**. Use a [sandbox](/sdks/sandbox) key for exploration. | | `baseUrl` | `https://sandbox-api.paddle.com` for sandbox or `https://api.paddle.com` for live. | Add multiple environments to your forked collection to switch between sandbox and live. --- # Pricing cards URL: https://developer.paddle.com/sdks/components/pricing-cards Tier cards with localized pricing, feature lists, and selection or current-plan states. ## What it does This component is the core building block for pricing pages. It's used to display a single plan card, or a group of plan cards in a grid or stack layout. Each card shows the plan name, description, localized price, a feature list, and an optional badge. `PricingTierCardGroup` lays them out side by side. ## When to use - You're composing a pricing page and want pre-built tier cards. - You need to highlight a "current plan" in a logged-in account view via `isCurrent`. - You want a "select a plan" interaction without opening Paddle checkout (use `isSelected` + `onSelect`). If you're building a full pricing page from scratch, use the [pricing display](/sdks/components/pricing-display) component, which packages pricing cards, [interval toggle](/sdks/components/billing-interval-toggle), and checkout together. --- # Python SDK URL: https://developer.paddle.com/sdks/libraries/python Install, authenticate, and make your first request with the official Paddle Python SDK. The Paddle Python SDK integrates Paddle Billing with Python applications. It handles pagination automatically, exposes operation classes for creates and updates, and includes a webhook signature verifier that works with Flask, Django, and anything speaking the request protocol. View source code and report issues on GitHub. View and install on PyPI. See recent releases and changes. ## Requirements Python 3.11 or later. ## Install ```bash pip install paddle-python-sdk ``` ```bash poetry add paddle-python-sdk ``` ```bash uv add paddle-python-sdk ``` ## Authenticate Create an API key in **Paddle > Developer tools > Authentication**, then pass it when you construct the client. API keys are environment-specific. Use a sandbox key for sandbox, a live key for production. ```python from os import getenv from paddle_billing import Client, Environment, Options paddle = Client( getenv('PADDLE_API_KEY'), options=Options(Environment.SANDBOX), ) ``` Omit the `options` argument, or use `Environment.PRODUCTION`, to use the live API. ## Make your first request List the products in your catalog: ```python from os import getenv from paddle_billing import Client, Environment, Options paddle = Client( getenv('PADDLE_API_KEY'), options=Options(Environment.SANDBOX), ) products = paddle.products.list() for product in products: print(product.id, product.name) ``` `paddle.products.list()` returns an iterator. Iterating with `for ... in` walks every page automatically. The SDK fetches the next page when the current one is exhausted. ## Next steps - Understand [shared patterns](/sdks/libraries) for pagination, idempotency, retries, and error handling across SDKs. - Browse the [API reference](/api-reference) for every resource and operation the SDK exposes. - Work against [sandbox](/sdks/sandbox) while you build, then follow the [go-live checklist](/build/go-live-checklist) to switch environments. --- # Sandbox URL: https://developer.paddle.com/sdks/sandbox Build and test your Paddle integration in a risk-free environment with separate data and no real payments. Sandbox is a separate Paddle environment for building and testing your integration without affecting real data or accepting real payments. Create a sandbox account in addition to your live account, then point your integration at sandbox during development and staging. ## Types of Paddle accounts Paddle has two types of account: - [**Sandbox accounts**](https://sandbox-vendors.paddle.com/) for building and testing integrations. - [**Live accounts**](https://vendors.paddle.com/) for accepting real payments. Sandbox and live accounts have the same features and APIs but completely separate datasets, credentials, and dashboards. API keys, client-side tokens, notification destinations, products, prices, and customers aren't shared between environments. We recommend using sandbox whenever you want to test without impacting real data or accepting real payments, including in local development and staging. Use environment variables to switch between sandbox and live. ## Create a sandbox account Sandbox accounts are separate from live accounts. You need to create a sandbox account even if you already have a live account. 1. Go to the [sandbox signup page](https://sandbox-vendors.paddle.com/signup). 2. Enter your personal and business details. 3. Click Continue to create your account. ## Authenticate with sandbox Sandbox and live accounts use different credentials. Generate them in each account separately. | Credential | Sandbox identifier | | ----------------- | ---------------------------- | | API key | Contains `_sdbx`. | | Client-side token | Prefixed with `test_`. | Using a sandbox credential against the live API (or vice versa) returns a [`forbidden`](/errors/shared/forbidden) error. For SDK-specific setup, including how to select the sandbox environment in code, see the relevant [library page](/sdks/libraries). For Paddle.js, see [include and initialize Paddle.js](/paddle-js/about/include-paddlejs). ## API base URLs | Environment | Base URL | | ----------- | ------------------------------- | | Live | `https://api.paddle.com` | | Sandbox | `https://sandbox-api.paddle.com` | ## Go live When you've tested your integration, work through the [go-live checklist](/build/go-live-checklist) to move to a live account. You'll swap credentials, recreate catalog and notification data in your live account, and switch base URLs. ## Differences between sandbox and live Core functionality is the same in both environments. A handful of differences make sandbox safer and faster to work against. | Area | Sandbox | Live | | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------- | | [**Website approval**](/build/transactions/default-payment-link) | No approval required. | Approval required. | | [**Default payment link**](/build/transactions/default-payment-link) | Any domain, no verification. | Verified, approved domain. | | [**Card payments**](/concepts/payment-methods/card) | Test cards only, no real money. | Real cards, real money. | | [**Checkout appearance**](/concepts/sell/self-serve-checkout) | 'Test Mode' watermark. | No watermark. | | [**Hosted checkouts**](/concepts/sell/hosted-checkout-mobile-apps) | No approval required. | Approval required. | | [**Adjustment approvals**](/api-reference/adjustments) | Refunds auto-approved every 10 minutes. | Paddle approval needed for most refunds. | | [**Webhook retries**](/webhooks/about/respond-to-webhooks) | 3 retries in 15 minutes. | 60 retries in 3 days. | | [**Email delivery**](/webhooks/about/notification-destinations) | Sent to your account's domain; others forwarded to your main email. | Sent to the customer's email or the email provided. | | [**Paddle Retain**](/concepts/retain) | Not available. May be simulated. | Available. | ### Test cards Sandbox accounts reject real card numbers. Use these test cards to complete checkouts: | Card type | Card number | | ------------------------------------------------------ | ------------------------------------------------------ | | Valid Visa debit card | 4000 0566 5566 5556 | | Valid card without 3DS | 4242 4242 4242 4242 | | Valid card with 3DS | 4000 0038 0000 0446 | | Declined card | 4000 0000 0000 0002 | | Initial success, subsequent decline | 4000 0027 6000 3184 | Enter any cardholder name and a valid expiry date in the future. Test cards don't work in live accounts, and live cards don't work in sandbox. ### Email delivery in sandbox Paddle sends emails to customers when they make a purchase and when subscription lifecycle events occur, like subscription renewals or cancellations. These emails are required for compliance reasons. Sandbox emails are only sent to email addresses that match your account's registered domain. Emails to other addresses are forwarded to the main address on your account. For example, if your account's registered email is `team@aeroedit.com`: | Email | Sent to | Reason | | ------------------- | ------------------- | ------------------------------------------------------------------- | | `team@aeroedit.com` | `team@aeroedit.com` | Matches your main address. | | `sam@aeroedit.com` | `sam@aeroedit.com` | Same domain as your main address. | | `jo@example.com` | `team@aeroedit.com` | Forwarded because the domain doesn't match. | Free email addresses like `gmail.com` or `hotmail.com` always forward to your main address, regardless of the local part. ### Webhook retries Sandbox retries failed webhook deliveries 3 times within 15 minutes. Live retries 60 times within 3 days. Make sure your notification endpoint returns a `2xx` response within 5 seconds, see [Respond to webhooks](/webhooks/about/respond-to-webhooks). ### Adjustments and refunds Sandbox auto-approves refund adjustments every 10 minutes so you can test the full flow end to end. In live accounts, [most refunds require Paddle approval](/build/transactions/create-transaction-adjustments). ### Paddle Retain Retain only works with live accounts. You can't set up Retain on a sandbox account, but you can [simulate its frontend interventions](/paddle-js/about/test-retain) to test cancellation flows, payment recovery, and term optimization. --- # Web monetization starter kit URL: https://developer.paddle.com/sdks/starter-kits/web-monetization Production-ready Next.js starter kit for monetizing iOS apps on the web. Includes a customizable landing page, pricing page, paywall, and integrated Paddle Checkout, designed to meet platform approval requirements. A Next.js starter kit for monetizing your iOS app on the web. Ships with a customizable landing page, pricing page, paywall, and integrated checkout — designed to meet Paddle's website requirements for platform approval so you can go live quickly. ![Grid of logos used in the Web Monetization Kit: Paddle, RevenueCat, Next.js, and Vercel.](/src/assets/images/tmp-build/vercel-ios-logos-20250508.svg) View source code, report issues, and contribute on GitHub. Try the starter kit before you get started. Step-by-step walkthrough from setup to test payment. ## What's included - A customizable landing page, pricing page, and paywall. - A high-converting checkout built with [Paddle Checkout](/concepts/sell/self-serve-checkout). - [shadcn/ui components](https://ui.shadcn.com/) so you can theme to match your brand. - A setup that meets Paddle's website requirements for platform approval. ## Tech stack | Layer | Tool | | ------------ | --------------------------------------------------------------------------------- | | Framework | [Next.js](https://nextjs.org/) | | Hosting | [Vercel](https://vercel.com/) | | Payments | [Paddle.js wrapper](/sdks/libraries/paddle-js-wrapper) | | Fulfillment | [RevenueCat](https://www.paddle.com/revenuecat-integration-beta) or [webhooks](/webhooks) | | Styling | [Tailwind CSS](https://tailwindcss.com/) | | Components | [shadcn/ui](https://ui.shadcn.com/) | ## Requirements - A [Vercel](https://vercel.com/) account. - A Git provider (GitHub recommended). - An Apple Developer account, Xcode, and access to your iOS project (for the in-app integration). - Node.js v20+ if you plan to develop locally. ## Get started Deploy the starter kit to Vercel: {% external-link url="https://vercel.com/new/clone?demo-title=Paddle+Mobile+Web+Payments+Starter&demo-description=Starter+project+for+iOS+apps+using+Paddle+checkout+in+Next.js.&demo-url=https%3A%2F%2Fpaddle-mobile-web-payments-starter.vercel.app%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F5fJcPbgzJYPsQCU1xJSKMO%2Ffb9049837adf2718d8e9bf8c987dffdb%2FHaJrngN.png&project-name=Paddle+Mobile+Web+Payments+Starter&repository-name=paddle-mobile-web-payments-starter&repository-url=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-mobile-web-payments-starter&from=templates" icon="vercel" %} Deploy Paddle web monetization starter kit to Vercel in one-click Alternatively, follow [the complete tutorial](/get-started/starter-kits/web-monetization) to deploy the starter kit manually. --- # Agents and AI URL: https://developer.paddle.com/sdks/ai MCP servers, Agent Skills, and embedded docs AI — tools that let AI agents integrate, operate, and reason about Paddle. Connect AI agents like Claude and Cursor directly to Paddle to work through complex flows in seconds. Build pricing models, investigate failed payments, integrate Paddle features, or process customer refunds — all in natural language. ## AI tools Lets AI agents read data and take actions in your Paddle account. Give AI agents full up-to-date knowledge of Paddle. Drop-in instructions that teach coding agents how to integrate Paddle. Use them together. The docs MCP server tells the agent how Paddle works; the Paddle MCP server lets it take actions in your account; Agent Skills give coding agents the step-by-step patterns they need to write correct Paddle integration code. ## Use cases Combine the Paddle MCP server with the docs MCP server to: - [**Integrate Paddle**](/sdks/ai/agent-skills) Use Agent Skills to give your coding agent step-by-step instructions for checkout, webhooks, subscription sync, and more — then use the Paddle MCP server to create the catalog and test the result. - [**Build and evolve your pricing**](/sdks/ai/paddle-mcp) Create complete pricing models or catalogs with regional variations by describing your structure. - [**Investigate billing issues**](/sdks/ai/paddle-mcp) Ask about specific customer problems and get complete transaction and subscription histories with explanations. - [**Onboard enterprise customers**](/sdks/ai/paddle-mcp) Migrate and set up enterprise customers with custom pricing and manual invoicing. Use in conjunction with quote management tools and CRMs. - [**Handle subscription changes**](/sdks/ai/paddle-mcp) Process subscription changes and transaction adjustments intelligently with context, recommendation, and action. - [**Analyze company performance**](/sdks/ai/paddle-mcp) Generate custom reports and get insights about revenue, churn, and customer behavior. ## Integration experience Ask your AI agent about billing operations, customer issues, integration tasks, or anything Paddle-related. Describe what you're trying to achieve in plain language. The agent selects which [tools](/sdks/ai/paddle-mcp) it needs to fulfill your request. There can be multiple. It may ask follow-up questions to clarify, gather required details, or better understand your goal. Most agents verify their actions before accessing data or making changes to your account. You can approve, request changes, or ask questions. Nothing happens without your confirmation by default. Products get created, issues get resolved, code gets written, or reports get generated. The agent explains the results and provides insights when relevant. The full context is maintained, so you can ask follow-up questions, request adjustments, or work through complex scenarios without starting over. ## Ask AI in the docs Click Ask AI on any page to open the agent embedded in the docs. Two modes: - **Fast:** quick questions about concepts, API behavior, and how Paddle works. - **Thinking:** detailed explanations or working through a specific integration problem. ## llms.txt The [`/llms.txt`](/llms.txt) file is a plain-text index of every page in these docs. AI clients can use it to find Paddle content without parsing HTML. ## Get content as Markdown Every page in the docs is available as Markdown: - Append `.md` to any doc URL to fetch the raw source, for example `https://developer.paddle.com/sdks/ai.md` - Click **Copy for LLM** on every page to copy the Markdown directly to your clipboard. When AI agents browse the documentation, we automatically serve them the Markdown version of the page to preserve tokens. --- # Go SDK URL: https://developer.paddle.com/sdks/libraries/go Install, authenticate, and make your first request with the official Paddle Go SDK for server-side Go applications. The Paddle Go SDK integrates Paddle Billing with server-side Go applications. It returns typed request and response structs, provides iterator-based pagination, and includes helpers for webhook signature verification. View source code and report issues on GitHub. View and install on pkg.go.dev. See recent releases and changes. ## Requirements Go 1.21 or later. ## Install ```bash go get github.com/PaddleHQ/paddle-go-sdk/v5 ``` Import the package in your Go program: ```go import ( paddle "github.com/PaddleHQ/paddle-go-sdk/v5" ) ``` ## Authenticate Create an API key in **Paddle > Developer tools > Authentication**, then pass it when you create a client. API keys are environment-specific. Use a sandbox key for sandbox, a live key for production. ```go package main import ( "os" paddle "github.com/PaddleHQ/paddle-go-sdk/v5" ) func main() { client, err := paddle.New( os.Getenv("PADDLE_API_KEY"), paddle.WithBaseURL(paddle.SandboxBaseURL), ) if err != nil { panic(err) } _ = client } ``` Use `paddle.ProductionBaseURL` to use the live API. ## Make your first request List the products in your catalog: ```go package main import ( "context" "fmt" "os" paddle "github.com/PaddleHQ/paddle-go-sdk/v5" ) func main() { client, err := paddle.New( os.Getenv("PADDLE_API_KEY"), paddle.WithBaseURL(paddle.SandboxBaseURL), ) if err != nil { panic(err) } ctx := context.Background() products, err := client.ListProducts(ctx, &paddle.ListProductsRequest{}) if err != nil { panic(err) } err = products.Iter(ctx, func(p *paddle.Product) (bool, error) { fmt.Printf("%s %s\n", p.ID, p.Name) return true, nil }) if err != nil { panic(err) } } ``` `ListProducts` returns a collection. Call `Iter` with a callback that returns `(true, nil)` to continue to the next product or `(false, nil)` to stop early. The SDK fetches subsequent pages as needed. ## Next steps - Understand [shared patterns](/sdks/libraries) for pagination, idempotency, retries, and error handling across SDKs. - Browse the [API reference](/api-reference) for every resource and operation the SDK exposes. - Work against [sandbox](/sdks/sandbox) while you build, then follow the [go-live checklist](/build/go-live-checklist) to switch environments. --- # next-forge URL: https://developer.paddle.com/sdks/community/next-forge Production-ready Next.js boilerplate with a pluggable payments package that supports Paddle Billing. [next-forge](https://www.next-forge.com) is a production-grade Turborepo template for Next.js apps. Its `@repo/payments` package is processor-agnostic, with a documented migration path to swap in Paddle Billing using the official [Paddle Node.js SDK](/sdks/libraries/node) and [Paddle.js wrapper](/sdks/libraries/paddle-js-wrapper). next-forge is maintained by the community, not by Paddle. For support, use the [next-forge issue tracker](https://github.com/vercel/next-forge/issues). View source code, report issues, and contribute on GitHub. Step-by-step guide to swap the default payments processor for Paddle. ## Requirements - Node.js 20+ ## Install Create a new next-forge project: ```bash pnpm dlx next-forge@latest init ``` ```bash npx next-forge@latest init ``` ```bash npx next-forge@latest init ``` Then, configure your environment variables, set up required services, and run the development server. See the [next-forge documentation](https://www.next-forge.com/docs) for more details. When setting up your environment variables, don't set up the default payment processor. Instead, follow the [Paddle installation guide](https://www.next-forge.com/docs/migrations/payments/paddle) to configure Paddle. ## Quick example Initialize the server-side client (after running through the migration steps): ```ts title="packages/payments/index.ts" import 'server-only'; import { Paddle } from '@paddle/paddle-node-sdk'; import { keys } from './keys'; const { PADDLE_SECRET_KEY, PADDLE_ENV } = keys(); export const paddle = new Paddle(PADDLE_SECRET_KEY, { environment: PADDLE_ENV, }); export * from '@paddle/paddle-node-sdk'; ``` Open a checkout from a client component: ```tsx title="apps/web/app/pricing/page.tsx" 'use client'; import { usePaddle } from '@repo/payments/checkout'; export default function Pricing() { const paddle = usePaddle(); return ( ); } ``` --- # Pricing select cards URL: https://developer.paddle.com/sdks/components/pricing-select-cards Compact, selectable plan cards in a radio-group pattern. Stacked or grid layout. ## What it does This component powers the plan-picker inside express checkout, and works anywhere you need a compact "select a plan" step without the full pricing-page treatment. ## When to use - You need a mobile-friendly, radio-group-style plan picker. - The customer should pick a plan before moving to a subsequent step, or you want to open checkout in a modal or drawer. - You want stacked or grid layouts that work well in narrow viewports. --- # Agent skills and plugins URL: https://developer.paddle.com/sdks/ai/agent-skills Drop-in instructions that teach AI coding agents how to integrate Paddle, including catalog setup, checkout, webhooks, subscription sync, pricing pages, and sandbox testing. Agent skills are a standardized way to give AI agents instructions on how to integrate Paddle. They give your agent specialized knowledge and instructions for specific Paddle workflows, helping it to build integrations faster and more accurately. Skills make agents more accurate, not infallible. Always review the code an agent writes before merging or deploying it, especially around authentication, webhook secrets, and access gating. ## How it works Skills tell an agent how to use Paddle in code, with framework-specific patterns, verification instructions, and best practices. They're written specifically for agents, rather than humans. They complement the docs MCP server, which gives agents knowledge of Paddle, and the Paddle MCP server, which lets agents take actions in Paddle. Skills are deliberately short and focused, so agents know exactly what to do. They include links to the relevant docs and MCP tool calls. Agents automatically load skills on demand when their `description` matches the task they're working on. ## Available skills Each skill is self-contained, but several link to each other. For example, `paddle-subscription-sync` assumes you have `paddle-webhooks` working. ## Install in your project ### Agentic coding plugins If you use Claude Code or another agentic coding plugin, the fastest way to get started is to use one of our official plugins. Plugins bundle skills with the [Paddle docs MCP server](/sdks/ai/docs-mcp) and the [Paddle MCP server](/sdks/ai/paddle-mcp). #### Connect the Paddle MCP servers When using plugins, you need to set the API keys for the environments you use. [Create API keys](/api-reference/about/authentication) at **Paddle > Developer tools > Authentication**, then export them in your shell profile: ```sh export PADDLE_SANDBOX_API_KEY= # Your sandbox API key export PADDLE_LIVE_API_KEY= # Your live API key ``` Restart your agentic coding plugin and your terminal after setting the variables so the MCP servers pick them up. The skills default to sandbox unless you've explicitly opted into live, so `PADDLE_SANDBOX_API_KEY` is the one to start with during development. ### Skills CLI For Cursor, Windsurf, and other agent tools that support the [agent skills discovery RFC](https://github.com/cloudflare/agent-skills-discovery-rfc), use the skills CLI: ```bash pnpm dlx skills add https://developer.paddle.com/ ``` ```bash yarn dlx skills add https://developer.paddle.com/ ``` ```bash npx skills add https://developer.paddle.com/ ``` ### Manual installation Save each skill in the `.agents/skills` directory. For example, to install the `paddle-webhooks` skill: ```bash mkdir -p .agents/skills # if not created curl -fsSL https://developer.paddle.com/.well-known/skills/paddle-webhooks/SKILL.md \ -o .agents/skills/paddle-webhooks/SKILL.md ``` Repeat for each skill you want available. Claude Code loads skills on demand when their `description` matches the task you're working on. To install all skills at once: ```bash for slug in paddle-billing-history paddle-catalog-setup paddle-checkout-web \ paddle-customer-portal paddle-pricing-pages paddle-sandbox-testing \ paddle-subscription-cancel paddle-subscription-sync \ paddle-subscription-update paddle-webhooks; do mkdir -p .agents/skills/$slug # if not created curl -fsSL https://developer.paddle.com/.well-known/skills/$slug/SKILL.md \ -o .agents/skills/$slug/SKILL.md done ``` You can also fork the [`@PaddleHQ/paddle-agent-skills`](https://github.com/PaddleHQ/paddle-agent-skills) repository and add the `SKILL.md` files to your agent's rules or context directory. Fork or clone paddle-agent-skills on GitHub ## Update skills Paddle updates skills periodically as features are added or changed. Re-fetch periodically (or have your CI do it) to make sure your local copies stay current. ## Discovery and verification Paddle publishes skills using the [agent skills discovery RFC](https://github.com/cloudflare/agent-skills-discovery-rfc). This means agents should be able to automatically discover the skills when hitting `developer.paddle.com` The RFC is still in progress, so if it's not automatically detected by your agent, then ask it to fetch the index from: ```text https://developer.paddle.com/.well-known/agent-skills/index.json ``` --- # Billing interval toggle URL: https://developer.paddle.com/sdks/components/billing-interval-toggle Segmented toggle for switching between billing intervals, like monthly and yearly. ## What it does A small, focused component for switching between billing term intervals. Labels are automatically formatted from the interval string. For example, `"month"` becomes `"Monthly"` and `"year"` becomes `"Yearly"`. ## When to use - You're building a custom pricing page or interface that doesn't use the [pricing display](/sdks/components/pricing-display) component. - You want a simple plan term interval switcher to drive your own pricing UI. --- # Libraries URL: https://developer.paddle.com/sdks/libraries Official Paddle server-side SDKs for Node.js, Python, Go, and PHP, plus the Paddle.js wrapper for client-side JavaScript and TypeScript. Paddle publishes official SDKs for Node.js, Python, Go, and PHP, plus a typed ES-module wrapper for Paddle.js. Each SDK is hand-written, kept in lockstep with the API, and distributed on the package registry for its language. ## Server-side SDKs TypeScript-first, iterator-based pagination, webhook verifier. Python 3.11+, typed operation classes, Flask and Django webhook verifier. Typed request and response structs, webhook middleware, idiomatic Go errors. PHP 8.1+, typed operation classes, PSR-7 compatible webhook verifier. ## Client-side Typed ES-module wrapper around Paddle.js for use with a JavaScript package manager. For the untyped `