For AI agents and LLMs: a structured documentation index is available at /llms.txt. Every page has a Markdown sibling — append .md to any URL. To search these docs, send a GET request to https://paddle-docs-retrieval.kapa-paddle.workers.dev/?q=<question> with a URL-encoded question. No authentication needed.

Skip to content
Paddle Docs home

Provision access and handle subscription state

Use webhooks to keep your app in sync with Paddle, then decide what access to grant based on subscription state.

AI summary

Use webhooks to sync subscription state with your app and gate access based on subscription status and features.

  • •Choose between lean cache (store only access-critical fields and fetch billing data on-demand) or state mirror (replicate full subscription state locally) based on your app's access decision patterns
  • •Listen to subscription.created and subscription.updated webhooks to keep customer accounts and access permissions in sync, deduping on event_id and comparing occurred_at timestamps to handle out-of-order events
  • •Map subscription status (trialing, active, past_due, paused, canceled) and scheduled_change.effective_at to access rules, and use product_id or price_id to gate feature access based on purchased products

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:

EventPurpose
subscription.createdA new subscription exists. Save the customer and subscription IDs against your user, and grant access.
subscription.updatedSomething about the subscription has changed, like its status, items, scheduled changes, or billing period. Reread 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 create 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 only enough to identify the customer in Paddle and answer simple access checks without an API call:

FieldWhat it's for
customer.idThe link between your user and Paddle. Save it once, against the user record.
subscription.idLook up or update the subscription via the API.
subscription.statusCheap access check without an API call.
subscription.items[].price.id and subscription.items[].price.product_idMap to features in your app.
subscription.scheduled_change.effective_atKnow 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 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
    C --> D
    D --> C
    B --> E[canceled]
    C --> E
StatusAccessNotes
trialingFullTreat the same as active.
activeFullFull access to your app.
past_dueFullFor automatically-collected subscriptions, show a banner and link to the customer portal so the customer can update their payment method. Paddle Retain automatically retries payment for you. For manually-collected subscriptions, the customer needs to pay their overdue invoice. Paddle doesn't retry or cancel these for you.
pausedNone or read-onlyNo transactions are created while paused.
canceledNoneRevoke access when the status changes to canceled. If a scheduled change is set, keep access until it takes effect.

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 what happens, without reasoning about which event occurs 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. For automatically-collected subscriptions, show a banner directing the customer to the customer portal to update their payment method. For manually-collected subscriptions, remind the customer to pay their overdue invoice, and decide when to cancel if it isn't paid.
  • 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.
  • 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:

Was this page helpful?