Purchase flow

Create checkout and grant access after payment

Use Licenzy to start Stripe checkout, then grant access only after webhook-confirmed payment updates entitlement state.

Start free in test mode. Go live when you're ready.

Category: Integration Flow8 sections

How to create a Stripe Checkout Session without granting access too early

This page is for the purchase start of the flow: how to open Stripe Checkout through Licenzy and make sure access is granted only after payment is actually finalized.

If you are trying to sell subscriptions, credits, or usage-based products with Stripe, creating checkout is the easy part. The harder part is tying payment to reliable access control. Stripe does not decide access for your product. Licenzy does.

Licenzy solves that gap by creating Stripe Checkout for you, processing the billing webhook, and updating entitlement state before your app makes runtime access decisions. That keeps checkout creation, payment confirmation, and access control in one consistent flow. See the full integration example if you want the entire purchase-to-access sequence.

When you should use this

This flow is useful when payment should activate or extend product access, including:

  • SaaS products with subscriptions.
  • Pay-per-use APIs.
  • Credit-based systems.
  • Gated products such as tools, features, or courses.

In all of these cases, creating checkout is not enough. You also need reliable access control after payment completes.

How to create a Stripe Checkout Session from your backend

Call POST /v1/checkout/session from your backend. Send the subject you are selling to and the Licenzy product code. Licenzy creates the Stripe checkout session internally and returns the redirect URL.

Checkout requestConcrete integration example
HTTP
POST /v1/checkout/session
Authorization: Bearer lz_test_...
Idempotency-Key: checkout-user_123-pro_monthly-001
Content-Type: application/json

{
  "subject_ref": "user_123",
  "product_code": "PRO_MONTHLY"
}
Checkout responseConcrete integration example
JSON
{
  "attempt_id": "att_123",
  "status": "created",
  "checkout_url": "https://checkout.stripe.com/...",
  "mode": "test"
}

Common mistake: granting access too early

A common integration bug is treating checkout creation or a browser success page as proof that access should be granted. That is too early. Checkout starts payment, but access must follow confirmed billing state.

Your app should not unlock features when Stripe Checkout is created, when the browser is redirected, or when a user lands on a success URL. The authoritative signal comes after webhook processing updates Licenzy entitlement state.

Do not trust the browser as payment confirmation
A success page is a user experience step, not your access-control source of truth. Runtime access should come from Licenzy access checks or the post-payment access guide and entitlement reads after webhook processing completes.

How to redirect to Stripe Checkout and handle idempotency

Keep the responsibilities separate. Your backend talks to Licenzy. Your frontend talks to your backend and performs the browser redirect using the returnedcheckout_url.

Backend: create checkout sessionConcrete integration example
TypeScript
const response = await fetch("https://api.licenzy.app/v1/checkout/session", {
  method: "POST",
  headers: {
    Authorization: "Bearer lz_test_***",
    "Idempotency-Key": "checkout-user_123-pro_monthly-001",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    subject_ref: "user_123",
    product_code: "PRO_MONTHLY",
  }),
});

if (!response.ok) {
  throw new Error("Failed to create checkout session.");
}

return response.json();
Frontend: redirect to checkout_urlConcrete integration example
TypeScript
async function startCheckout() {
  const response = await fetch("/api/billing/checkout", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      productCode: "PRO_MONTHLY",
    }),
  });

  if (!response.ok) {
    throw new Error("Unable to start checkout.");
  }

  const { checkout_url } = await response.json();

  window.location.assign(checkout_url);
}

Reuse the same Idempotency-Key when retrying the exact same checkout creation attempt. Do not generate a new key for transport retries.

Critical behavior
Checkout only initiates payment. Access is granted only after webhook processing completes inside Licenzy. The full integration example shows that end-to-end sequence.

What happens after payment

After the customer completes Stripe Checkout, Stripe sends the billing event to Licenzy. Licenzy validates and processes that webhook, then creates or updates the relevant entitlement state for the subject you sold to.

This is the critical difference between payment processing and access control.

Your app should not grant access manually at this stage. Instead, read the resulting state through Licenzy runtime endpoints and let entitlement-backed access checks drive the final decision.

  • Stripe sends the payment outcome through a billing webhook.
  • Licenzy processes the event and updates entitlement state.
  • Your app reads access from Licenzy after that state exists.
Operational rule
Payment completion and access enablement are connected through webhook-processed entitlement state. Do not add a separate manual access grant on top of that flow.

Next steps

Continue with the runtime surfaces that turn payment state into actual product access: