Recipes

Checkout and portal sessions

Create short-lived, single-use sessions so buyers can subscribe and customers can manage their own billing, without your API key ever reaching a browser.

Your API key can charge every card in the organization, so keep it on your server. Both surfaces here work the same way: you create a single-use, short-lived session from your server and send your buyer a link.

Create a hosted checkout session

POST/v1/businesses/{biz}/checkout_sessions
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/checkout_sessions \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{
    "items": [{ "price": "pro-monthly-usd", "quantity": 1 }],
    "coupons": ["WELCOME20"]
  }'

You get back a URL of the form /checkout/s/<token>. Send your buyer there. They enter their details and card, and tmbilling creates the subscription when they finish.

Select a mode

ModeWhat it does
create (default)Subscribes a new customer to the items. Pass price and quantity as shorthand for a single-item items array.
changeApplies the items to an existing subscription when the buyer finishes — a plan swap, a quantity change, an add-on. Requires subscription. tmbilling pins the customer to that subscription's owner when you create the session, so the buyer cannot reassign it.

tmbilling validates items and coupons when you create the session, not when the buyer finishes. A bad price or coupon gives you a 404 now instead of breaking the page halfway through payment.

POST/v1/businesses/{biz}/checkout_sessions/{id}/revoke

Revoke a link that leaked.

Create a customer portal session

Give an existing customer a place to manage their own billing:

POST/v1/businesses/{biz}/customers/{cus}/portal_sessions
curl -X POST $TMBILLING_URL/v1/businesses/$BIZ/customers/$CUS/portal_sessions \
  -H "Authorization: Bearer $TMBILLING_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{}'

You get back a /portal/s/<token> link. Behind it your customer manages payment methods and billing details, so your support team never handles a card.

POST/v1/businesses/{biz}/customers/{cus}/portal_sessions/{id}/revoke
tmbilling returns the credential exactly once and it stays valid for one hour. Opening the link spends it: tmbilling exchanges the credential for a session held in an HttpOnly cookie.So don't store the URL, don't email it twice, and don't put it behind a link checker or a mail scanner that follows links — the scanner spends it and your customer gets an expired page. Create a fresh link when your customer asks, from a button in your own application.

The session has its own, later expiry. The one hour limits how long the link works, not how long your customer can stay on the page.

Select the right surface

SituationUse
A new buyer subscribingCheckout, mode: create
An existing customer changing plan, confirming payment themselvesCheckout, mode: change
An existing customer updating a cardPortal
Changing a plan with no buyer interactionNeither — call change directly

Brand both surfaces

tmbilling renders both with your business's logo, icon and brand colour, taken from the business record. Your buyer sees your company, so set these before you send anyone a link:

PATCH/v1/businesses/{biz}
POST/v1/businesses/{biz}/brand_images

Confirm the outcome from the webhook

Don't rely on your buyer landing back on the return URL. They close tabs, and networks fail at the worst moment.

Treat the webhook as the source of truth: subscription_created for a create session, and the relevant subscription and invoice events for a change. See Webhooks.

Copyright © 2026