MainSDK
Search⌘K
Integrations

Paddle

Use Paddle as a planned MainSDK payments driver - merchant of record checkout, sandbox and live keys, webhook secrets and swapping.

1 interface1 driverWritten guide
Updated 2026-10-28

Paddle is a merchant of record: it sells your software to your customers on your behalf and handles sales tax and invoicing in the countries it covers. It is a planned driver behind pay. Pick Paddle when you sell software or subscriptions internationally and would rather not register for and file sales tax yourself.

Interfaces it serves

Pick Paddle in main.yaml for any of these. Your calls stay the same, and you can swap later.

Before you start

You need a Paddle account. Paddle gives you a separate sandbox account for testing and a live account, each with its own keys and webhook destinations.

  1. Create an API key in the sandbox account's developer settings.
  2. Add a webhook destination that points at the URL where your app receives pay events, and copy the secret Paddle shows for it.
  3. Set up your products and prices in Paddle; Paddle reviews your account before you can sell live.
  4. Repeat in the live account once it is approved.

Configure

main.yaml
payments: { default: paddle }
pay:
  paddle:                                   # [to be decided] Paddle is not in the pay spec; shape follows the other providers
    key: secret:PADDLE_API_KEY              # suggested name
    webhook: secret:PADDLE_WEBHOOK_SECRET   # suggested name

Provider keys are always secret: references; a literal key fails mainsdk check.

Use it

app/billing/actions.ts
"use server";
import { auth, pay } from "mainsdk";

export async function subscribe(req: Request, plan: string) {
  const session = await auth.require(req);
  return pay.subscribe({ customer: session.user.id, plan });   // [to be decided] how customer is identified
}

Subscription changes arrive as subscription.updated through pay.on().

Local development and testing

mainsdk dev with no provider key runs the local test card flow. Use Paddle's sandbox account for the real hosted checkout. In tests, createTestApp() from @mainsdk/testing runs pay on its in-memory fake, and chaos.on("pay", { outage: true }) simulates an outage.

What MainSDK adds on top

  • Checkout only on Paddle's hosted checkout; card data never touches your app (MAIN-PAY-0001).
  • Paddle webhooks verified before your handler runs, and duplicates handled once.
  • Idempotency keys on checkout, subscribe and refund.
  • Amounts in integer minor units with an ISO 4217 currency.

See Payments.

Limits and notes

  • Planned driver, not in the spec yet. Paddle is out of scope for pay@1; when and how it lands is not decided yet.
  • Because Paddle is the seller, your customers' receipts and tax invoices come from Paddle, not from your business.
  • Paddle sells digital products and software; check that what you sell is eligible.
  • pay.raw() would return the Paddle client; mainsdk check lists every use.

Swap to or from it

main.yaml
payments: { default: paddle }   # was { default: stripe }

FAQ

Can I use Paddle today? Not through pay@1. It is planned; use Stripe or Razorpay until it lands.

Does Paddle replace Stripe for usage billing? No. Usage metering goes through meter, with Stripe meters or Lago.

Next

Open questions

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet