/v1 API, sends the customer to Custral’s hosted quote page, and learns the outcome by webhook. The customer’s card or bank details go to Stripe’s page, the subscription runs on your workspace’s Stripe account, and the quote, invoices and payments land on the customer’s record in Custral.
No endpoint charges a card, bills an invoice or changes a live subscription. Money moves only when the customer accepts or pays.
Before you start
1
Set up payments
An admin completes Settings → Payments: a Products object, a Customers object, Stripe connected, and payment defaults. Connect a Stripe test-mode account while you build, so Stripe’s test cards work and nobody is charged.
2
Pick a customer key
On the Developers tab of Settings → Payments, set Customer key to the field on your Customers object that holds your app’s own customer id (an
Account ID, say). Then you can name customers by your id instead of a Custral record id.3
Allow your return domain
Add your app’s host (for example
app.example.com) to Allowed return domains. A returnUrl on any other host is refused.4
Create an API key
In Settings → Applications, create a secret key with the
quotes:write and billing:read scopes. Add billing:portal if you’ll open billing pages (step 6).5
Register a webhook endpoint
Subscribe it to the quote and billing events you need. See the webhooks quickstart.
1. Create the quote
When the customer clicks Upgrade, your server creates a quote withPOST /v1/quotes:
customeris your app’s id (matched through the customer key) or a Custral record id (rec_…).createCustomercreates the customer record when none matches, with your id as its key. Without it, an unknown customer is refused withcustomer_not_found.productsnames catalog products by record id or exact name.unitPricesets a custom price for this deal, in major units (49.5is $49.50).send: truemarks the quote sent in the same call, so itsurlopens right away. Sending emails nobody. You hand the customer the link yourself.- A
returnUrlmust behttps(httponly forlocalhost), and its host must be in Allowed return domains. A refused URL is checked before anything is written, so no quote is left behind.
2. Send the customer to the hosted page
Redirect toquote.url. The page is branded with your workspace and needs no sign-in. The customer reviews the lines and what they’ll be charged when, types their name and email, and accepts.
- Autopay quotes hand over to Stripe’s hosted page, where the customer adds a card or bank account. The quote is accepted only once it’s saved.
- Send invoices quotes are accepted straight away, and Stripe emails each invoice with a pay link.
3. The customer comes back
After accepting, the page shows “Quote accepted” with a Continue button. It takes the customer to yourreturnUrl with two parameters added:
returnUrl is kept. Use the return to show a page (“Thanks, you’re on Pro”), not to grant access: a customer can close the tab before they click Continue.
4. Hear the outcome by webhook
Grant access when the webhook arrives. Custral starts the subscription on Stripe right after acceptance, and the events follow:recordId, not your own id; keep the mapping, or read billing back by your id as below.
5. Read billing back
Whenever your app needs the current state, read it live from Stripe withGET /v1/customers/{customer}/billing:
hostedUrl pay link) and upcoming instalments. For the workspace as a whole, custral.billing.invoices({period: "30d"}) returns receivables with totals per currency, and custral.billing.metrics() returns MRR, ARR and net revenue retention.
6. Give customers a billing page
Once a customer is paying, they need somewhere to see their plan, change their card, read invoices, switch plans and cancel. Custral hosts that page; your server opens it for one customer at a time. Addbilling:portal to your key, then open a session when the customer clicks Billing in your app:
url, token and expiresAt. It lasts 15 minutes, so open one per visit rather than storing it. The page shows what you turned on in Revenue › Billing page (or with PATCH /v1/billing/page), and its Back link goes to returnUrl.
Or draw it inside your app
Pass the token toCustralBilling.mount from @custral/js instead of redirecting:
onTokenExpired is optional. With it, the widget swaps in the fresh token and reloads the frame; without it, the page says the session has ended.
What the customer can do there
Your server can cancel or keep a plan too, without the page:
SDK methods at a glance
Common refusals
See the Quote object for the full list.
To set up Stripe, the catalog and the billing page itself from code, see Set up billing from code.