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.
- Your frontend asks your backend to start checkout.
- Your backend calls Licenzy with a server-side API key.
- Licenzy creates Stripe Checkout internally and returns
checkout_url. - The frontend redirects the user to Stripe Checkout.
- Stripe calls Licenzy webhook endpoints after billing events occur.
- Licenzy updates entitlement state from the webhook processing result.
- Your backend calls Licenzy access or usage endpoints during product runtime.
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_refthat 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.
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.
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.
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.
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.
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.
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_codetoPOST /v1/access/check. - Using inconsistent
subject_refvalues 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_usagehandling.