Docs
BlogHomeStart building

Payments

Let your app take real payments.


An app takes payments into its owner's Stripe account — no Stripe key in the code. Ask the agent ("add a checkout for the courses") and it builds the payment flow with totalum.payments (SDK). These calls are the account owner's.

  1. Try it in the preview right away. The preview always takes Stripe test cards, with no setup: pay with 4242 4242 4242 4242, any future date and any CVC. No real money moves.
  2. Publish, then connect your Stripe to receive real payments — one click in the project's Payments window, or Create Payments Onboarding Link: Stripe asks for your business and bank details on its own page and the money goes to your Stripe account.
  3. Switch the published site to real payments when you are ready (Set Payments Mode). The preview stays in test mode.

Payment events arrive by themselves. Renewals, cancellations, failed payments and refunds reach the app's own /api/webhooks/stripe route with nothing to set up, on the site where the checkout was made: test payments made in the preview go to the preview, test payments made on the published site (before it takes real payments) and real payments go to the published site (on its custom domain if it has one). They are signed by Totalum, and the app checks them with totalum.webhooks.verify (SDK). To also send them to another address (your own server, Zapier…), set the optional project webhook (in the platform: Payments → Advanced).

Checkout opens in a new tab. Stripe's payment page cannot run inside the platform's preview frame, so the app opens Checkout and the customer portal in a new tab unless you ask otherwise.

Already have a Stripe account and prefer your own keys? Add your secret key as a project secret named STRIPE_SECRET_KEY and ask the agent to use it ("take payments with my own Stripe key"). The agent then calls Stripe from the app's server only, never from the browser. To receive Stripe's events, also add your endpoint's signing secret as STRIPE_WEBHOOK_SECRET and add https://<your site>/api/webhooks/stripe as an endpoint in your Stripe Dashboard: with your own keys that route checks Stripe's signature instead of Totalum's. Whether it charges test or real cards is up to the key (sk_test_… or sk_live_…).

#Get Payments Status

GET/api/v1/vcaas/projects/:projectId/payments/statusapi-keyFree

Your connected Stripe account and whether it can take payments.

Path parameters

ParameterTypeDescription
projectIdstringThe project id

Query parameters

FieldTypeRequiredDescription
modestringNo"live" (default: your Stripe account) or "test" (the preview's test account)

Response fields

FieldTypeDescription
data.enabledbooleanA Stripe account is connected
data.modestring"connect"
data.accountIdstring | nullThe Stripe account id
data.livemodebooleantrue for the live account
data.chargesEnabledbooleanIt can take payments
data.payoutsEnabledbooleanIt can receive payouts
data.detailsSubmittedbooleanOnboarding is complete
data.requirementsobject{ currentlyDue, pastDue, eventuallyDue }: what Stripe still asks for
data.liveEnabledbooleanThe published site charges in live mode
data.disconnectedAtstring | null | undefinedWhen the owner disconnected the account from Stripe

Example request

curl -H "api-key: tlm_sk_your_key" \
  "https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/payments/status?mode=live"
Success · 200 OK
json
{ "errors": null, "data": { "enabled": true, "mode": "connect", "accountId": "acct_1Q2w3E4r", "livemode": true, "chargesEnabled": true, "payoutsEnabled": true, "detailsSubmitted": true, "requirements": { "currentlyDue": [], "pastDue": [], "eventuallyDue": [] }, "liveEnabled": false, "disconnectedAt": null } }
POST/api/v1/vcaas/projects/:projectId/payments/onboarding-linkapi-keyFree

A single-use link to Stripe's onboarding, where the owner connects (or creates) their Stripe account to receive real payments. The first link creates the account and needs its country. Publish the project first: real payments go to the published site, so a project that was never published is refused with PAYMENTS_PUBLISH_FIRST. The preview needs no onboarding: it takes test cards with no setup.

Path parameters

ParameterTypeDescription
projectIdstringThe project id

Query parameters

FieldTypeRequiredDescription
modestringNo"live" (default and the only one: "test" is refused, the preview's test account needs no onboarding)

Body parameters

FieldTypeRequiredDescription
returnUrlstringYeshttps:// page to come back to when done
refreshUrlstringYeshttps:// page that asks for a new link if this one expired
typestringNo"account_onboarding" (default) or "account_update"
countrystringNoTwo-letter country of the business: required for the first link (and after a disconnection), cannot change later

Response fields

FieldTypeDescription
data.urlstringThe onboarding link
data.expiresAtstringWhen it expires

Example request

curl -X POST \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"returnUrl":"https://yourapp.com/payments/done","refreshUrl":"https://yourapp.com/payments/retry","country":"ES"}' \
  "https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/payments/onboarding-link"
Success · 200 OK
json
{ "errors": null, "data": { "url": "https://connect.stripe.com/setup/e/acct_1Q2w3E4r/…", "expiresAt": "2026-10-08T13:10:00.000Z" } }

Error codes

CodeHTTPMeaning
OWNER_ONLY403Account owner only
VALIDATION_ERROR400Missing or non-https URLs, or mode=test
MISSING_INPUT400No country for a new account
PAYMENTS_PUBLISH_FIRST409The project was never published: publish it, then connect Stripe
End of Create Payments Onboarding LinkNext endpointPUTSet Payments Mode

#Set Payments Mode

PUT/api/v1/vcaas/projects/:projectId/payments/modeapi-keyFree

Switch the published site between test and real (live) payments (10 switches an hour). Live needs your connected Stripe account to have completed onboarding. The preview always stays in test mode.

Path parameters

ParameterTypeDescription
projectIdstringThe project id

Body parameters

FieldTypeRequiredDescription
modestringYes"test" or "live"

Response fields

FieldTypeDescription
data.modestringThe mode now

Example request

curl -X PUT \
  -H "api-key: tlm_sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"mode":"live"}' \
  https://builder-api.totalum.app/api/v1/vcaas/projects/barber-bookings/payments/mode
Success · 200 OK
json
{ "errors": null, "data": { "mode": "live" } }

Error codes

CodeHTTPMeaning
PAYMENTS_LIVE_ONBOARDING_REQUIRED403Finish connecting your Stripe account first
OWNER_ONLY403Account owner only
RATE_LIMITED429More than 10 switches in an hour
End of Set Payments Mode Back to top