The client_secret authorises one specific browser session to load the hosted checkout for that session. It is scoped to a single cs_… id and cannot be used to create, modify, refund, or list anything.
It is safe to send to the browser (embed page, redirect URL, iframe src). It is not safe to log publicly or share across users — anyone with the value can open that particular checkout.
Lifetime
Event
Effect on client_secret
Session created
Valid for 24 hours or until session status changes.
Session complete / expired / canceled
Immediately invalid — loading the checkout returns session_expired.
Session paid via async method (SEPA, Klarna)
Immediately invalid; buyer is redirected to success_url.
There is no way to renew a client_secret. If it expires, create a new Checkout Session and redirect to the new checkout_url.
Correct usage
Backend (your server):
const session = await fetch("https://api.southbill.com/v1/checkout/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOUTHBILL_SECRET_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order_${orderId}`,
},
body: JSON.stringify({ amount: 4990, currency: "eur", customer_name, customer_email }),
}).then(r => r.json());
// Redirect the buyer:
res.redirect(303, session.checkout_url);
// OR return only what the browser needs:
res.json({ embed_url: session.embed_url });
Every POST endpoint in the Southbill API accepts an Idempotency-Key header. Use it to safely retry a request after a network error without creating duplicate resources.
How it works
The uniqueness scope is (merchant_id, method, path, Idempotency-Key). When the API receives a POST with an Idempotency-Key:
If the key has never been used on this endpoint → the request is processed normally, and its response (status + body) is stored for 24 hours.
If the key was already used with the same request body → the original response is returned verbatim. The endpoint is not re-executed.
If the key was already used with a different request body → the API returns 409 Conflict:
{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "Idempotency-Key reused with a different request body"
}
}
Keys expire after 24 hours. After that, the same key can be reused for a new request.
Choosing a good key
Deterministic per business action: order_12345, sub_2026-07-18_001, refund_ch_abc_partial_1.
Do not use timestamps or random UUIDs generated per retry — the whole point is that a retry uses the same key.
1–255 characters, ASCII.
Endpoints that support it
Method
Path
POST
/v1/checkout/sessions
POST
/v1/checkout/sessions/{id}/expire
POST
/v1/subscriptions
POST
/v1/subscriptions/{id}/cancel
POST
/v1/products
POST
/v1/products/{id}
POST
/v1/products/{id}/prices
POST
/v1/products/{id}/default_price
GET and DELETE requests ignore the header — they are already idempotent by definition.
Retries
Combine Idempotency-Key with exponential backoff for 5xx and 429 responses:
Create one-off payments programmatically — the only public payments endpoint today.
Checkout Sessions
A Checkout Session is the primary API endpoint for taking a payment. You create it from your server, redirect the customer to checkout_url, and receive a webhook when it's paid.
Base URL: https://api.southbill.com/v1
Auth: Authorization: Bearer sk_live_… (or sk_test_…)
line_items[].price_id is optional — pass it when the item comes from a product you created in the dashboard, so it appears in receipts and analytics with its catalog link. The actual amount charged is amount — the price is not fetched from the catalog.
Fees are calculated in EUR on your active plan and deducted from the merchant's balance in the transaction currency (converted at live FX). See Fees, currency & minimum amounts.
The Payment object returned by the API and delivered on webhooks.
Payments are not a standalone REST resource. There is no GET /v1/payments in the Merchant API — payment data reaches you through Checkout Session responses and webhooks. Installed marketplace apps can read payments through the App API (GET /v1/app/payments).
Payments
A Payment represents a single money movement from a customer to a merchant. It is created automatically when a Checkout Session completes or an Invoice is paid — you don't create it directly.
Fetch the parent session/invoice — the payment is embedded in its response:
GET /v1/checkout/sessions/{id}
A dedicated GET /v1/payments/{id} endpoint is planned for the next API release.
Currency & fees
Every fee is computed in EUR against your plan's rate for the actual card region and payment method, then converted back to the charge currency. See Fees, currency & minimum amounts.
Refund a full or partial charge — from the dashboard today, via API soon.
Refunds
Refunds can be issued through the Dashboard → Transactions → Refund action or with the public API: POST /v1/refunds (requires the refunds:write scope).
How it works
Merchant clicks Refund on a succeeded transaction and enters an amount (full or partial).
Southbill issues a reversal of the destination transfer on the platform account so the money comes out of the merchant's balance, not Southbill's.
Southbill also reverses the pro-rata application fee — you only keep fees for what the buyer actually paid.
Transaction status becomes refunded (full) or partially_refunded (partial).
A charge.refunded webhook fires.
Rules
You cannot refund a disputed charge — resolve the dispute first. The dashboard shows a friendly error (charge_disputed).
Refunds can be issued for up to 180 days after the original charge (processor limit).
Partial refunds can be issued multiple times up to the total captured amount.
Fee reversal is automatic and visible in the merchant's Wallet as Southbill fee refund.
How invoices work in Southbill, how customers pay them, and the roadmap for the invoice API.
Invoices
Invoices in Southbill are created in the merchant dashboard (/dashboard/invoices/new). Each invoice generates a public payment link that anyone with the link can pay — no API key needed on the customer side.
A public REST API for create / list / send invoice is on the roadmap. Today you can automate invoice payments through Checkout Sessions and use the dashboard for invoicing.
Anatomy
Field
Description
number
Auto-generated SPK-INV-YYYY-NNNN.
currency
ISO code — locked to the currency of any product line-item you add.
line_items
Free-form or imported from your Products catalog. Product-sourced items are locked to Southbill values (price, quantity, VAT, currency).
total_amount
Sum incl. VAT. Minimum 2.50 in invoice currency (below that, PSP + Southbill fees can't be covered).
status
draft → open → paid / void / uncollectible.
public_token
Random token used in the customer-facing URL.
Customer payment flow
Merchant creates the invoice and clicks Send — status becomes open, public_token is minted.
Customer opens https://payments.southbill.com/i/{public_token} (link is emailed automatically).
Opening the link starts a southbill-hosted payment for the invoice, which:
creates the payment against the merchant's Southbill account,
resolves enabled payment methods for the invoice currency (Klarna, iDEAL, Bancontact, etc. are filtered per currency),
applies the merchant's Southbill fees via the shared fee engine.
On success the invoice is marked paid, a receipt is emailed and a invoice.paid webhook is delivered to your endpoints.
Adding products to an invoice
In the New invoice page, click Add from products → pick a product. Southbill will:
copy name, description, image, price, currency, VAT from your catalog;
lock those fields (marked "From product · locked to Southbill values");
lock the invoice currency to the product currency;
disable recurring products (recurring billing runs through the Subscriptions API, POST /v1/subscriptions).
Webhooks
Configure your endpoints in Dashboard → Developers → Webhooks.
Create recurring subscriptions for customers using a recurring price from your catalog. Subscriptions run on your connected Southbill account; Southbill deducts the platform fee automatically from each renewal invoice.
Base URL: https://api.southbill.com/v1
Auth: Authorization: Bearer sk_live_… (or sk_test_…)
Prerequisites
Create a product and a recurring price (see Products and Prices).
The price must have recurring.interval set (day, week, month, year).
The subscription is created in incomplete state. Use the returned client_secret on the customer's browser to confirm the first payment. Once confirmed the status transitions to active (or trialing if trial_days was set) and a customer.subscription.updated webhook is fired.
Retrieve
GET /v1/subscriptions/{id}
List
GET /v1/subscriptions?limit=20
Returns up to 100 subscriptions ordered by creation date (newest first).
Keep active until the current period ends, then cancel.
at_period_end: false
—
Cancel immediately. No further invoices.
Status model
Status
Meaning
incomplete
Waiting for the first payment confirmation.
incomplete_expired
First payment was not confirmed within 23 h.
trialing
Free trial in progress.
active
Paid and current.
past_due
Renewal failed. Dunning in progress.
unpaid
All retries exhausted, subscription frozen.
canceled
Terminated.
Fees
Recurring invoices carry a platform fee computed from your plan and payment method. Fees are settled in EUR and deducted per invoice — same rules as one-off Checkout Sessions. See Fees, currency & minimum amounts.
Idempotency
Send Idempotency-Key: <your-key> on POST /v1/subscriptions and POST /v1/subscriptions/{id}/cancel. Keys are scoped to (merchant_id, method, path) and expire after 24 hours.
Same key + same body → original response replayed verbatim.
Same key + different body → 409 Conflict with type: "idempotency_error", code: "idempotency_key_reused". See Error reference.
Errors
HTTP
error.type
error.code
Meaning
400
invalid_request
field-specific
Missing / malformed field, or price is not recurring.
Create products in the dashboard or via API — they live directly on your Southbill account.
Products
A Product describes something you sell — a shoe, a subscription tier, a service. Products in Southbill are created directly on your Southbill account — either via the dashboard (Dashboard → Products → New) or via the public API described below.
Fields
Field
Description
name
Public name shown on receipts, invoices and checkout.
description
Long description.
images[]
Up to 8 image URLs. First image is used as the thumbnail.
tax_code
Optional Southbill tax code (txcd_…).
unit_label
e.g. "seat", "month".
statement_descriptor
What appears on the buyer's card statement (≤ 22 chars).
url
Link back to your product page.
shippable
Physical goods = true.
package_dimensions
Length / width / height (cm) and weight (g).
metadata
Free-form key/value.
tags[]
southbill-internal — used for catalog auto-grouping.
default_price
The Price object linked as the default.
Minimum price
Every unit_amount must be ≥ 250 minor units (2.50) in the price's currency. Smaller amounts don't cover PSP + Southbill fees.
API — Create a product
POST https://api.southbill.com/v1/products
Authenticate with a secret API key (sk_live_… / sk_test_…) — scope products:write. Pass an optional Idempotency-Key header to make retries safe.
The returned prod_… and price_… IDs are what you use everywhere else — catalog management and invoice product selection. Recurring prices define the billing terms, but automatic subscription billing requires a recurring-billing flow.
API — Other product operations
Method
Path
Description
GET
/v1/products
List products (params: limit, starting_after). Scope products:read.
Archive the product and all its prices (active=false).
POST
/v1/products/{id}/default_price
Body { "price": "price_…" } — change the default price.
API — Prices on a product
Method
Path
Description
POST
/v1/products/{id}/prices
Create a new Price on this product. Fields: currency, unit_amount, nickname, recurring, tax_behavior, lookup_key, set_as_default, metadata.
GET
/v1/products/{id}/prices
List all prices on this product.
Prices are immutable except for active, nickname, tax_behavior, lookup_key, metadata. To change amount or currency, create a new Price and mark the old one inactive.
In an invoice: New invoice → Add from products → the item is locked to the product's Southbill values.
In a Checkout Session: pass line_items[].price_id: "price_…" so receipts and analytics link back to the catalog entry.
Product ID vs Price ID
ID
What it is
When you use it
prod_…
The container — name, description, images.
Reporting, catalog management.
price_…
The billable price — amount, currency, one-time or recurring.
Every transaction and invoice line-item.
Rule of thumb: to charge someone you always need a price (or an ad-hoc amount), never just a product.
Idempotency
All POST endpoints (/v1/products, /v1/products/{id}, /v1/products/{id}/prices, /v1/products/{id}/default_price) accept Idempotency-Key. Same key + same body replays the original response; same key + different body returns 409 Conflict with type: "idempotency_error", code: "idempotency_key_reused". See Error reference.
A Price represents how much and how often a product costs. Every product needs at least one price to be sellable.
Types
Type
recurring
Use for
one_time
null
Physical goods, single services, invoices.
recurring
{ interval, interval_count }
Subscriptions via POST /v1/subscriptions.
Fields
Field
Description
unit_amount
Amount in minor units (cents). Must be ≥ 250 (= 2.50).
currency
3-letter ISO code, lowercase.
product
The parent prod_… ID.
type
one_time or recurring.
recurring.interval
day, week, month, year.
recurring.interval_count
e.g. 3 → every 3 months.
nickname
Internal label (e.g. "Pro monthly EUR").
active
false archives it — existing subscriptions keep billing.
Currency lock
Once a price exists, its currency is immutable (Southbill rule). To sell in another currency, create a second price for the same product with a different currency.
Recurring prices in invoices
Recurring products are disabled in the invoice UI — invoices are one-shot documents. To bill on a schedule, use the Subscriptions API (POST /v1/subscriptions).
Creating a price
Prices are created automatically when you create a product from the dashboard. To add additional prices to an existing product, use Dashboard → Products → {product} → Add price or the API: POST /v1/products/{product_id}/prices.
Finding your Price ID
Dashboard → Products — the list shows both the Product ID (prod_…) and the Price ID (price_…) with copy buttons.
The products list has a category dropdown that filters by the category's tag. Pagination kicks in at 20 products.
API
Not exposed publicly. All catalog operations go through the dashboard function merchant-products (list_categories, create_category, update_category, delete_category, update_product_tags).