Hosted Checkout
Redirect to payments.southbill.com/c/{id} or embed payments.southbill.com/embed/{id} — card data never touches your servers, which minimizes your PCI scope (SAQ A).
Hosted Checkout
Southbill ships two URLs for the hosted checkout — one for real payments, one for design preview. Do not mix them up.
| Route | Purpose | Real payment? |
|---|---|---|
/c/{session_id} |
Real hosted checkout for a session created via API | ✅ Yes |
/embed/{session_id} |
Same as above, optimised for iframe embedding | ✅ Yes |
/c/s/{slug} |
Preview only — renders your Checkout Builder branding with a dummy product. Ignores ?session=…. |
❌ No |
1. Create a session (server-side)
curl -X POST https://api.southbill.com/v1/checkout/sessions \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"amount": 4990,
"currency": "CHF",
"line_items": [{ "name": "T-Shirt", "quantity": 1, "amount": 4990 }],
"success_url": "https://your-shop.com/thanks",
"cancel_url": "https://your-shop.com/cart"
}'
Response:
{
"id": "cs_01H…",
"checkout_url": "https://payments.southbill.com/c/cs_01H…?cs=cs_secret_…",
"embed_url": "https://payments.southbill.com/embed/cs_01H…"
}
2. Send the buyer to the real checkout
Redirect
window.location = session.checkout_url; // -> /c/{session_id}?cs={client_secret}
Iframe
<iframe src="{{embed_url}}"
style="width:100%;height:720px;border:0"
allow="payment *"></iframe>
Web component
<script src="https://payments.southbill.com/southbill.js"></script>
<southbill-checkout session-id="cs_01H…"></southbill-checkout>
The slug in the snippets from Dashboard → Checkout builder only tells Southbill which branding profile to apply — the session_id is what makes it a real transaction. Without a valid session, the page falls back to preview mode with dummy data.
3. What the buyer sees
- Merchant logo, business name and support email (from Checkout builder).
- Line items with product images (
line_items[].image_url). - All payment methods enabled for the currency in your plan + Stripe.
- 3-D Secure / SCA when required.
- Success page with southbill-hosted receipt (
/r/{merchant_id}/{tx_id}) + PDF.
Preview mode (/c/s/{slug})
Use this URL in your browser to see how your checkout looks with the current Builder settings. It renders a hard-coded sample product for CHF 49.90 and a static list of payment methods. Any ?session=… parameter is ignored — this route never talks to Stripe and never charges anyone.
To test a real payment end-to-end, always create a session via the API first and open the returned checkout_url unchanged — the ?cs= query parameter is what authorises the browser to load that session.
Success & cancel URLs
Optional. When set, Southbill redirects the buyer after the session ends and appends ?session_id={id} so you can look up the result.
Customising
Dashboard → Checkout builder controls colours, logo, business name, support email, return URL and the pay button label. Changes apply to every future session — no code changes needed.
Pay button labels (fixed set)
The button text is not free text. It is picked from a curated list in Dashboard → Checkout builder → Button label, so the button can never say something misleading about what the buyer is agreeing to. There is no API field for it — the label always comes from the Builder profile of the merchant that owns the session.
| Value stored | Renders as | Allowed for |
|---|---|---|
Pay {amount} |
Pay $99.00 |
one-time (mode: "payment") |
Pay securely |
Pay securely |
one-time |
Complete purchase |
Complete purchase |
one-time |
Buy now |
Buy now |
one-time |
Place order |
Place order |
one-time |
Donate {amount} |
Donate $99.00 |
one-time |
Subscribe |
Subscribe |
subscription (mode: "subscription") |
Subscribe now |
Subscribe now |
subscription |
Start subscription |
Start subscription |
subscription |
Pay subscription |
Pay subscription |
subscription |
Rules:
{amount}is replaced at render time with the formatted session total (currency + locale). It is the only placeholder.- A one-time label is never shown on a subscription checkout. If the profile holds a one-time label and the session is
mode: "subscription", the checkout falls back to Start subscription. - Defaults:
Pay {amount}for one-time,Start subscriptionfor subscriptions. - Legacy or unknown values (from before the fixed list) fall back to the defaults instead of rendering raw text.
- Any other value sent to the Checkout Builder API is rejected with
400 invalid_request.
Wallets (Apple Pay / Google Pay)
Express wallet buttons are rendered above the card form by Stripe's Express Checkout Element. They appear only when all of the following are true:
- wallets work for one-time and subscription sessions. For
mode: "subscription"the wallet sheet shows the recurring terms (amount, interval, cancellation link) and stores the mandate for future billing cycles; - express checkout is enabled in the Checkout Builder and the wallet is in the enabled wallet list;
- the buyer's browser/device actually offers the wallet (Apple Pay: Safari/iOS with a card in Wallet; Google Pay: Chrome with a saved card);
- the page is served over HTTPS.
Nothing has to be configured on your own domain — the wallet runs inside the Southbill checkout origin (payments.southbill.com), which is already registered with Apple. When you embed the checkout, keep allow="payment *" on the iframe, otherwise the browser blocks the wallet inside the frame.
Ad-hoc vs. catalog line items
| Key type | Allowed line_items |
|---|---|
Secret key (sk_…, server-side) |
Ad-hoc items with name, quantity, amount, or catalog items with price. |
Publishable key (pk_…, browser) |
Only catalog items: { "price": "price_…", "quantity": 1 }. Amounts are resolved server-side from your catalog. |