Sandbox & testing

Test the full Southbill API with test keys — real test-mode payments, zero real money.

Sandbox overview

Two completely separated worlds: live and test.

The Southbill sandbox is a full test environment, not a simulator. A test key talks to a real payment processor in test mode: real checkout pages, real 3-D Secure, real webhooks — only no money moves.

Two separated worlds

Live Sandbox
Key prefix sk_live_… sk_test_…
Account your verified account an auto-created test account
Data live tables separate sandbox storage
Money real none
Fees / payouts yes never
Verification (KYC) emails yes never

There is no fallback between the two. A test key can never read, write or refund live data, and a live key never touches sandbox data.

Getting a sandbox

  1. Open Dashboard → Developers → Sandbox.
  2. Click Create sandbox account. Your test account is created and verified instantly — no second onboarding.
  3. Click Create test key. Test keys start with sk_test_ and stay visible: you can reveal and copy them any time.

Test keys do not count against the API key limit of your plan.

Base URL and auth

https://api.southbill.com/v1
Authorization: Bearer sk_test_…

Everything else — paths, request bodies, errors, idempotency, pagination — is identical to live. Switching to production is only a key change.

Data lifetime

Sandbox records expire 30 days after creation and are deleted automatically. You can also wipe everything from the Sandbox tab.

Test keys & test cards

Cards for success, decline, 3-D Secure and disputes.

Test keys

  • Create and revoke them under Dashboard → Developers → Sandbox.
  • A test key is always visible and copyable — unlike live keys, which are shown only once.
  • Scopes work exactly as in live. Give the key the scopes you need (checkout:write, payments:read, refunds:write, …).
  • Using a test key where the endpoint is live-only returns a clear error — never a silent live write.

Test cards

Use any future expiry date, any 3-digit CVC and any postal code.

Card number Result
4242 4242 4242 4242 Payment succeeds
4000 0000 0000 0002 Card declined (card_declined)
4000 0000 0000 9995 Insufficient funds
4000 0025 0000 3155 Requires 3-D Secure authentication
4000 0000 0000 0259 Succeeds, then becomes a dispute (fraudulent)
4000 0000 0000 0341 Attaches, but charging fails

What the sandbox never does

  • No application fees, no fee ledger entries.
  • No payouts and no balance settlement.
  • No emails to your customers about verification or payouts.
  • No effect on your live reporting, exports or invoices.

What you can test

Endpoint-by-endpoint coverage of the sandbox.

Fully supported with sk_test_

Area Endpoints
Checkout POST /v1/checkout/sessions, retrieve, list, PATCH, expire
Products & prices /v1/products, /v1/products/{id}/prices
Customers /v1/customers (create, list, retrieve, update, delete)
Invoices /v1/invoices, items, send, mark_paid, /installments, /payments
Subscriptions /v1/subscriptions (create, list, retrieve, update, cancel)
Refunds POST /v1/refunds, retrieve, list
Payments GET /v1/payments (list, retrieve, status & source filters)
Events GET /v1/events
Webhook endpoints /v1/webhook_endpoints (CRUD, secret rotation, deliveries)
Balance GET /v1/balance (test balance, always zero-settlement)

Objects created with a test key carry "livemode": false.

Live-only (a test key is rejected)

Area Why
/v1/subscription_links Hosted links always create live subscriptions
Payouts There is no money to pay out in test mode
Store API (/v1/store/*) Storefronts run against your live catalogue
Hosted invoice payment pages Test invoices are settled via checkout sessions or mark_paid
Instant payouts, fee ledger, accounting exports Live accounting only

Disputes

Pay with 4000 0000 0000 0259. The payment succeeds, then turns into a dispute: the payment is flagged as disputed and you receive charge.dispute.created on your test webhook endpoint.

Webhooks in the sandbox

Signed test events, delivery log and replay.

Webhook endpoints are mode-scoped: an endpoint created with a test key only ever receives test events, and never sees live traffic.

Create a test endpoint

curl https://api.southbill.com/v1/webhook_endpoints \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.example.com/webhooks","enabled_events":["*"]}'

The response contains the signing secret (whsec_…) — store it, it is shown once.

Verify the signature

Identical to live: the signature header carries a timestamp and an HMAC-SHA256 of timestamp.payload. See Webhooks → Verify signatures.

Events you will see in test

checkout.session.completed, payment_intent.succeeded, payment_intent.payment_failed, checkout.session.refunded, charge.dispute.created, invoice.paid, invoice.partially_paid, invoice.payment_succeeded, invoice.installment.paid, invoice.installment.due, invoice.installment.overdue, subscription.created, subscription.updated, subscription.canceled.

Payout events exist in live only.

Delivery log & replay

curl "https://api.southbill.com/v1/webhook_endpoints/{id}/deliveries" \
  -H "Authorization: Bearer sk_test_…"

Failed deliveries are retried automatically with exponential backoff, exactly as in live.

No public URL yet?

Point the endpoint at any HTTPS request bin, or tunnel your local server (for example ngrok http 3000) and use the public HTTPS URL.

End-to-end test walkthrough

One full pass: product, checkout, payment, refund, invoice, subscription.

This walkthrough runs the complete flow with curl. It works the same on macOS, Linux and Windows (CMD or PowerShell).

Set your key first:

export SB=sk_test_…            # macOS / Linux
set SB=sk_test_…               # Windows CMD

1. Create a product and a price

curl https://api.southbill.com/v1/products -H "Authorization: Bearer $SB" \
  -H "Content-Type: application/json" \
  -d '{"name":"Test Plan","default_price":{"unit_amount":2500,"currency":"eur"}}'

2. Create a customer

curl https://api.southbill.com/v1/customers -H "Authorization: Bearer $SB" \
  -H "Content-Type: application/json" \
  -d '{"email":"tester@example.com","name":"Test Buyer"}'

3. Create a checkout session and pay it

curl https://api.southbill.com/v1/checkout/sessions -H "Authorization: Bearer $SB" \
  -H "Content-Type: application/json" \
  -d '{"amount":2500,"currency":"eur","customer_email":"tester@example.com",
       "success_url":"https://example.com/ok","cancel_url":"https://example.com/no"}'

Open the returned url in a browser and pay with 4242 4242 4242 4242.

4. Check the payment

curl "https://api.southbill.com/v1/payments?limit=5" -H "Authorization: Bearer $SB"

5. Refund it

curl https://api.southbill.com/v1/refunds -H "Authorization: Bearer $SB" \
  -H "Content-Type: application/json" \
  -d '{"payment_intent":"pi_…","amount":1000,"reason":"requested_by_customer"}'

6. Create an invoice with installments

curl https://api.southbill.com/v1/invoices -H "Authorization: Bearer $SB" \
  -H "Content-Type: application/json" \
  -d '{"customer":"cus_…","currency":"eur","payment_mode":"partial",
       "items":[{"description":"Consulting","quantity":1,"unit_amount":120000}],
       "installments":[{"amount":60000,"due_date":"2026-10-01"},
                       {"amount":60000,"due_date":"2026-11-01"}]}'

Then POST /v1/invoices/{id}/send, inspect GET /v1/invoices/{id}/installments, and settle with POST /v1/invoices/{id}/mark_paid.

7. Create a subscription

curl https://api.southbill.com/v1/subscriptions -H "Authorization: Bearer $SB" \
  -H "Content-Type: application/json" \
  -d '{"customer":"cus_…","price":"price_…"}'

8. Read the event stream

curl "https://api.southbill.com/v1/events?limit=20" -H "Authorization: Bearer $SB"

Every object in these responses has "livemode": false. When the pass is green, swap sk_test_ for sk_live_ — nothing else changes.