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 Recommended
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.createdandcustomer.updatedif you don't already capture customer details at signup.transaction.completedif you bill one-off charges and need to record them locally.
To see exactly which events occur for common scenarios, use the webhook simulator.
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:
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 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:
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
Onsubscription.created, save thecustomer_idagainst your user and grant access. - Upgrade or downgrade
Onsubscription.updated, refresh the cachedprice_idandproduct_id. Update which features the customer has access to. - Past due
Keep access. Show a banner directing the customer to the customer portal to update their payment method. - Pause or resume On
subscription.updated, gate access by the newstatus. Resumed subscriptions go straight back toactive(orpast_due). - Cancellation
Ifscheduled_change.effective_atis in the future, revoke access at that time. If the subscription is alreadycanceled, 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. - Events can arrive out of order. Compare
occurred_atwith 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 and list subscriptions accept a
customer_idquery parameter. - List transactions accepts a
subscription_idparameter to return a subscription's transactions. - Get a transaction and get a subscription support
includeparameters to return related entities in one request.