Self-serve billing integration
This guide connects the main self-serve Billing APIs into one implementation path:
- Your backend issues a short-lived bearer token for a stable
principal_id. - Your frontend uses that bearer token to read catalog products and prices.
- Your frontend creates a hosted checkout session when the user buys.
- 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
- You understand how your stable customer or account identifier maps to Vluna:
- You have active catalog products and prices:
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/productsGET /api/v1/catalog/pricesPOST /api/v1/checkout/sessionsPOST /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:
- Resolve the correct billing owner in your system.
- Use that stable identifier as
principal_id. - Call
POST /mgt/v1/token/issuewith scopes such ascheckoutandportal. - Store the returned
billing_account_idalongside yourprincipal_id. - Return only the short-lived bearer token to the frontend.
Recommended request shape:
principal_id: stable billing owner in your systemuser_id: end-user identifier for the current sessionscopes:['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_idmapping 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/productsGET /api/v1/catalog/prices
Recommended storefront pattern:
- List products first.
- For each displayed product, load prices with the relevant
product_id. - Render recurring and one-time prices differently.
- Respect returned ordering instead of re-sorting prices in the client.
- 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/pricesrequiresproduct_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_idas the primary identifiersuccess_urlfor the post-purchase return pagecancel_urlfor 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:
- Checkout session is created.
- User completes payment in the provider.
- Provider sends webhook events to Vluna.
- Vluna updates subscriptions, assignments, wallet balances, and related state.
- 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/issuereturns bothaccess_tokenandbilling_account_id.GET /api/v1/catalog/productsreturns the active products visible to the realm.GET /api/v1/catalog/pricesreturns the expected active prices for a chosen product.POST /api/v1/checkout/sessionsreturns a hosted URL and the browser can redirect there.POST /api/v1/portal/sessionsreturns a hosted management URL.- After webhook finalization, subscription or wallet state reflects the completed purchase.
Troubleshoot
401ontoken/issue: wrong service key or missing service authentication.401/403on 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
- Billing model baseline: Fixed pricing model (subscriptions and credits)
- UX flow design: Self-serve orchestration
- Subscription modeling: Subscription group modeling
- Install and connectivity checks: Verify installation and integration