Walkthrough

Full Licenzy and Stripe integration example

Build the full backend flow from frontend checkout to Stripe payment, webhook processing, Licenzy entitlements, access checks, and optional usage consumption.

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

Category: Integration Flow15 sections

Introduction

This page is the full end-to-end guide: how checkout, webhook processing, entitlements, access checks, and optional usage all fit together in one working flow.

This guide shows a full integration using Licenzy: your backend creates checkout, Stripe handles payment, Licenzy processes the billing webhook, updates entitlements, and your backend later verifies access through Licenzy runtime access validation.

Assume you are selling a paid plan called PRO_MONTHLY to a user withsubject_ref user_123. Your backend owns the Licenzy API key. Your frontend only starts checkout through your backend and then redirects the user.

The sequence is: your backend creates the Licenzy checkout session, Licenzy creates Stripe Checkout internally, the frontend redirects to checkout_url, Stripe later calls Licenzy webhooks, Licenzy updates entitlements, and your app then checks access or consumes usage.

Architecture overview

The full integration has one clear direction of trust: frontend to your backend, backend to Licenzy, Licenzy to Stripe Checkout, Stripe webhook back to Licenzy, Licenzy entitlement state, then backend access checks.

  1. Your frontend asks your backend to start checkout.
  2. Your backend calls Licenzy with a server-side API key.
  3. Licenzy creates Stripe Checkout internally and returns checkout_url.
  4. The frontend redirects the user to Stripe Checkout.
  5. Stripe calls Licenzy webhook endpoints after billing events occur.
  6. Licenzy updates entitlement state from the webhook processing result.
  7. Your backend calls Licenzy access or usage endpoints during product runtime.
Source of truth
The browser success page is not the source of truth for access. Webhook-updated entitlement state is the source your backend should query through runtime access checks.

What you'll build

This walkthrough covers the complete payment-to-access flow:

  • Checkout creation from your backend.
  • Redirecting the customer to Stripe Checkout.
  • Webhook handling after payment.
  • Entitlement creation and updates inside Licenzy.
  • Access verification from your backend.

If you want the isolated purchase entry point first, start with checkout session. This page stays focused on the full end-to-end integration rather than any single runtime endpoint.

Step-by-step implementation

In a production system, each step has a separate owner. Keep this boundary intact while you wire the flow:

  • The frontend triggers checkout through your backend.
  • The backend creates the session through Licenzy.
  • Stripe processes the payment inside Checkout.
  • Licenzy handles the webhook from Stripe.
  • Licenzy updates entitlements.
  • The backend checks access before protected work.

That separation is what makes access control reliable and secure.

What you need

  • A configured Licenzy API key stored in backend secrets.
  • A Stripe connection configured in the matching mode.
  • A Licenzy product with code PRO_MONTHLY.
  • A stable subject_ref that stays the same through the full flow.

Step 1 — Create a checkout session

Your backend calls POST /v1/checkout/session. This is the purchase entry point. Licenzy uses the stored Stripe connection and returns a Stripe checkout URL. See checkout session for the focused version of this step.

Backend checkout creationConcrete 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();

Step 2 — Redirect the user

Your frontend calls your own backend route, receives checkout_url, and redirects the customer. The browser should not call Licenzy directly with a secret key.

Frontend redirectConcrete 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);
}

Step 3 — Handle payment via webhook

After payment, Stripe calls Licenzy webhook endpoints directly. Licenzy verifies the Stripe signature, processes the event idempotently, and updates entitlements. The dedicated webhooks page covers the delivery model in more detail. If you specifically need the post-payment verification step, see how to check user access after Stripe payment.

Critical behavior
Do not grant access from the success page alone. Checkout starts payment. Access is granted only after webhook processing completes.

Step 4 — Access is granted via entitlements

Once webhook processing has finished, your backend can ask Licenzy whether the subject currently has access. That decision comes from stored entitlements, not from Stripe objects or frontend state. See access checks for the focused runtime contract.

Access checkConcrete integration example
HTTP
POST /v1/access/check
Authorization: Bearer lz_test_...
Content-Type: application/json

{
  "subject_ref": "user_123"
}

{
  "subject_ref": "user_123",
  "allowed": true
}

Step 5 — Check access from your backend

Use GET /v1/customer/access/:subject_ref when you need a richer customer view for account pages, support tools or billing status screens. Use GET /v1/entitlements/:subject_ref when you need direct entitlement inspection. The main access checks page explains when to use each runtime read.

Customer access viewConcrete integration example
HTTP
GET /v1/customer/access/user_123
Authorization: Bearer lz_test_...

// Simplified example. Actual responses may include additional fields per entitlement.
{
  "subject_ref": "user_123",
  "has_access": true,
  "status": "active",
  "entitlements": [
    {
      "kind": "subscription",
      "status": "active",
      "available": true,
      "usage_remaining": 900,
      "availability_reason": "subscription_active",
      "expires_at": "2026-04-30T23:59:59Z"
    }
  ]
}

Step 6 — Consume usage (optional)

If the active entitlement is a usage_pack, consume units after the protected action is accepted or completed. Reuse the same idempotency key for retries of the same business event. The focused usage metering guide covers insufficient usage and retry handling.

Usage consumptionConcrete integration example
HTTP
POST /v1/usage/consume
Authorization: Bearer lz_test_...
Idempotency-Key: usage-user_123-req_987
Content-Type: application/json

{
  "subject_ref": "user_123",
  "units": 100
}

{
  "ok": true,
  "consumed": 100,
  "usage_remaining": 900
}

What not to do

  • Granting access too early, before webhook processing has finalized billing state.
  • Relying on frontend state or a success page as proof that access should be enabled.
  • Missing webhook retries or not treating webhook delivery as an idempotent, retryable path.
  • Mixing payment logic with access logic instead of letting webhook-updated entitlement state drive access.
  • Creating Stripe Checkout directly instead of calling POST /v1/checkout/session.
  • Sending mode in a runtime request body instead of letting the API key select the environment.
  • Sending product_code to POST /v1/access/check.
  • Using inconsistent subject_ref values across checkout, access and usage calls.

Why this matters

Payment processing answers whether money moved. Access control answers whether a subject should currently be allowed to use your product. Those are related, but they are not the same decision.

This integration keeps them connected in the right order: Stripe handles payment, Licenzy processes the billing event, updates entitlements, and your backend checks access from that resulting state. That separation is what makes the flow reliable.

Production validation checklist

  • Make sure API key, Stripe connection and product catalog are all in the same mode.
  • Validate one full test-mode purchase from checkout creation through access check.
  • Confirm Stripe webhook delivery reaches Licenzy and entitlement state changes after payment.
  • Verify the browser success page does not grant access by itself.
  • Validate one full real-money purchase path before broad rollout.
  • Use customer access and entitlements reads for support and debugging, not only the minimal access check.
  • If you sell usage packs, test successful consumption and insufficient_usage handling.