Skip to main content

Self-serve billing integration

This guide connects the main self-serve Billing APIs into one implementation path:

  1. Your backend issues a short-lived bearer token for a stable principal_id.
  2. Your frontend uses that bearer token to read catalog products and prices.
  3. Your frontend creates a hosted checkout session when the user buys.
  4. Your frontend creates a hosted portal session when the user manages billing.

Use this guide when you want a practical storefront path. If you already have a sales-led or contract-driven flow, prefer direct plan assignment instead of provider checkout.

Prereqs

Provider-hosted checkout, portal, and webhook finalization are handled by Vluna. This guide focuses only on what your backend and frontend need to integrate.

Architecture split

Keep the responsibility boundary explicit:

  • Backend only:
    • POST /mgt/v1/token/issue
  • Frontend or mobile app with bearer token:
    • GET /api/v1/catalog/products
    • GET /api/v1/catalog/prices
    • POST /api/v1/checkout/sessions
    • POST /api/v1/portal/sessions

Do not call token/issue directly from an untrusted client. It is a service-to-service step that binds principal_id to billing_account_id and returns the short-lived bearer token your client will use afterward.

Step 1: issue a bearer token from your backend

Your backend should issue the token as early as possible in a customer-scoped session:

  1. Resolve the correct billing owner in your system.
  2. Use that stable identifier as principal_id.
  3. Call POST /mgt/v1/token/issue with scopes such as checkout and portal.
  4. Store the returned billing_account_id alongside your principal_id.
  5. Return only the short-lived bearer token to the frontend.

Recommended request shape:

  • principal_id: stable billing owner in your system
  • user_id: end-user identifier for the current session
  • scopes: ['checkout', 'portal']
  • session_ttl_sec: short lifetime, for example 900

Why this should happen up front:

  • It validates connectivity before the user reaches your pricing page.
  • It creates or confirms the immutable principal_id -> billing_account_id mapping outside the purchase path.
  • It gives support and analytics a stable Vluna-side account identifier.

Step 2: render the storefront from the public catalog

Use the bearer token from step 1 to call:

  • GET /api/v1/catalog/products
  • GET /api/v1/catalog/prices

Recommended storefront pattern:

  1. List products first.
  2. For each displayed product, load prices with the relevant product_id.
  3. Render recurring and one-time prices differently.
  4. Respect returned ordering instead of re-sorting prices in the client.
  5. Use response hints such as subscription-state projections to decide whether the next action should be buy or manage.

Important catalog semantics:

  • Public catalog endpoints return only active products and active prices.
  • GET /api/v1/catalog/prices requires product_id.
  • Price metadata, not client-only logic, drives downstream billing-plan, grants, and gate-bundle behavior.

Step 3: create checkout when the user buys

When the user selects a purchasable price, call POST /api/v1/checkout/sessions from the frontend with the bearer token.

Recommended inputs:

  • items[].catalog_price_id as the primary identifier
  • success_url for the post-purchase return page
  • cancel_url for the cancel path

Operational expectations:

  • The response returns checkout_url.
  • Your app should perform a full-page redirect to that hosted provider URL.
  • Redirect completion is not the final source of truth. Wait for provider webhook processing before showing entitlement-sensitive success states.

Conflict behavior matters:

  • If the requested purchase conflicts with an existing non-stackable active subscription, Vluna may return a portal destination instead of a new checkout flow.
  • Your frontend should treat the returned URL as the next billing action, not assume it always represents a new purchase session.

Step 4: create portal when the user manages billing

Use POST /api/v1/portal/sessions when the user wants to:

  • manage an existing subscription
  • update payment method
  • inspect invoices or billing history in the provider-hosted surface

The response returns portal_url. Redirect the user there, then return them to your app with return_url.

Portal is also your fallback path when:

  • checkout detects a subscription conflict
  • the user already has an active recurring relationship
  • the safest next step is customer-managed provider UX instead of creating a second checkout

Step 5: treat webhooks as the finalizer

The self-serve path is not complete when the browser returns from checkout.

Your product should assume this lifecycle:

  1. Checkout session is created.
  2. User completes payment in the provider.
  3. Provider sends webhook events to Vluna.
  4. Vluna updates subscriptions, assignments, wallet balances, and related state.
  5. Your UI refreshes from Vluna APIs and shows the finalized state.

This means:

  • do not grant final access based only on redirect success
  • do not assume wallet or subscription state updates synchronously with browser navigation
  • do poll or refresh the relevant Billing APIs after return

Verify

  • POST /mgt/v1/token/issue returns both access_token and billing_account_id.
  • GET /api/v1/catalog/products returns the active products visible to the realm.
  • GET /api/v1/catalog/prices returns the expected active prices for a chosen product.
  • POST /api/v1/checkout/sessions returns a hosted URL and the browser can redirect there.
  • POST /api/v1/portal/sessions returns a hosted management URL.
  • After webhook finalization, subscription or wallet state reflects the completed purchase.

Troubleshoot

  • 401 on token/issue: wrong service key or missing service authentication.
  • 401/403 on catalog, checkout, or portal: frontend is using an expired bearer token or insufficient token scopes.
  • Empty catalog: products or prices are not active, or the selected catalog entries are not available to the current integration.
  • Checkout appears successful but product access is unchanged: provider redirect completed, but webhook finalization has not yet updated Vluna state.
  • Checkout sends the user to management instead of purchase: the selected price conflicts with an existing recurring subscription, so portal is the correct next action.

Next