Access control

How to verify user access after Stripe payment (without race conditions)

Stripe payment success does not automatically mean access is active. Access should be verified against entitlement-backed runtime state after webhook-confirmed billing updates, and Licenzy provides the backend checks for that flow.

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

Category: Integration Flow6 sections

Introduction

This page is for a specific integration problem: payment looks successful, but access is still not active yet.

A common real-world scenario looks like this: the user completes Stripe Checkout, lands on a success page, and then immediately tries to use the product, but access is still not active.

That is usually expected when billing confirmation and webhook-driven backend state have not fully settled yet. The browser may already show a successful payment flow while your runtime access state is still waiting for webhook processing to finish.

In this model, access must come from backend runtime state, not from a frontend redirect or success URL. Licenzy answers that post-payment access decision from entitlements updated after billing webhooks are processed.

Why payment and access are different decisions

Stripe confirms payment flow. Your product still needs a separate decision about access.

  • Stripe handles payment collection and billing events.
  • Licenzy processes webhook-confirmed billing updates.
  • Licenzy updates entitlement-backed runtime state.
  • Your backend should verify access from that resulting state.

That separation is what keeps access reliable when retries happen, billing changes, or frontend state becomes stale.

Licenzy solves this by making access a direct function of entitlement state updated by Stripe webhooks, removing the need for custom billing-to-access logic.

Example access check

A clean backend access check is usually enough for protected routes, paid features, or post-purchase verification.

Runtime access checkConcrete integration example
TypeScript
const response = await fetch("https://api.licenzy.app/v1/access/check", {
  method: "POST",
  headers: {
    Authorization: "Bearer lz_test_***",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    subject_ref: "user_123",
  }),
});

const result = await response.json();
// { subject_ref: "user_123", allowed: true }
Access denied responseConcrete integration example
JSON
{
  "subject_ref": "user_123",
  "allowed": false
}

When you need more than a boolean answer, use customer access or entitlement reads for support views, account pages, or debugging.

Common mistakes

  • Trusting a browser success page as proof that access is already active.
  • Enabling access too early, before webhook-driven entitlement updates finish.
  • Checking access from the frontend only instead of from a backend runtime path.
  • Reading payment success as if it were the same thing as current access state.
Do not trust payment success alone
Payment success is part of the billing flow. Access should come from Licenzy runtime state after webhook processing has updated entitlements.