---
title: "Checkout and portal sessions"
description: "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."
canonical_url: "https://twominutebilling.com/help/developers/recipes/checkout-and-portal"
---
# 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

<endpoint method="POST" path="/v1/businesses/{biz}/checkout_sessions">



</endpoint>

```bash
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

<table>
<thead>
  <tr>
    <th>
      Mode
    </th>
    
    <th>
      What it does
    </th>
  </tr>
</thead>

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

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.

<endpoint method="POST" path="/v1/businesses/{biz}/checkout_sessions/{id}/revoke">



</endpoint>

Revoke a link that leaked.

## Create a customer portal session

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

<endpoint method="POST" path="/v1/businesses/{biz}/customers/{cus}/portal_sessions">



</endpoint>

```bash
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.

<endpoint method="POST" path="/v1/businesses/{biz}/customers/{cus}/portal_sessions/{id}/revoke">



</endpoint>

## Handle the link correctly

<warning title="The link is single-use and expires in an hour">

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.

</warning>

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

<table>
<thead>
  <tr>
    <th>
      Situation
    </th>
    
    <th>
      Use
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      A new buyer subscribing
    </td>
    
    <td>
      Checkout, <code>
        mode: create
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      An existing customer changing plan, confirming payment themselves
    </td>
    
    <td>
      Checkout, <code>
        mode: change
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      An existing customer updating a card
    </td>
    
    <td>
      Portal
    </td>
  </tr>
  
  <tr>
    <td>
      Changing a plan with no buyer interaction
    </td>
    
    <td>
      Neither — call <code>
        change
      </code>
      
       directly
    </td>
  </tr>
</tbody>
</table>

## 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:

<endpoint method="PATCH" path="/v1/businesses/{biz}">



</endpoint>

<endpoint method="POST" path="/v1/businesses/{biz}/brand_images">



</endpoint>

## 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](https://twominutebilling.com/help/developers/webhooks).

## Related

- [Hosted checkout and customer portal features](https://twominutebilling.com/product/checkout-portal)
- [Set up hosted checkout and the portal in the dashboard](https://twominutebilling.com/help/guides/hosted-checkout-and-portal)


## Sitemap

See the full [sitemap](https://twominutebilling.com/sitemap.md) for all pages.
