Use your own customers and products

Already have customer records and a product catalogue? Keep them. Use SubCharge only for what it is best at — the recurring billing engine — and refer to everything by your own ids. There is no second copy to keep in sync, and nothing to re-enter in our dashboard.

Status: the API and billing-only mode described on this page are available today. An option to send your own customer emails is planned and not yet released; see what's coming.

How it works

Your system is the source of truthYour system pushes customers, plans and subscriptions to SubCharge using your own ids. SubCharge runs the billing schedule and returns your ids in API responses and webhooks. SubCharge never calls your system to bill.Your systemsource of truthcustomersproducts & pricesyour own idsSubChargerecurring billingschedule & renewalsinvoices & dunningpayments & webhooksyou pushPUT / POST + your idsyour ids come backAPI & webhooksSubCharge never calls your system to bill, so your outages never stall a billing run.
You push your records to SubCharge. SubCharge hands your ids back; it never reaches into your system.
  • You stay the source of truth. SubCharge keeps a thin record per customer, product and plan, tagged with your id (externalId).
  • Billing never waits on you. Every invoice freezes the price and customer details when it is issued, so a slow or unavailable system on your side cannot stall or change a billing run.
  • Your ids come back everywhere they are useful: API responses and webhook payloads, so you can match events to your own records without a lookup table.
  • Nothing changes for existing integrations. Everything here is additive; the dashboard and the existing endpoints work exactly as before.

1. Customers

Create or update a customer by your own id with one call. Fields you leave out are not touched, so a partial update is safe. You get 201 when it was created and 200 when it already existed.

curl -X PUT https://www.subcharge.co.za/v1/customers/by-external-id/crm_8431 \
  -H "Authorization: Bearer bl_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com", "name": "Ada Lovelace" }'

An email is required the first time (invoices and payment reminders go to it). To look a customer up later: GET /v1/customers?external_id=crm_8431, which returns an empty list if there is no match.

2. Products and plans

Products and plans work the same way. A plan is a priced way to buy a product, so you create the product first (or let the subscription call below do it for you).

# a product
curl -X PUT https://www.subcharge.co.za/v1/products/by-external-id/gym \
  -H "Authorization: Bearer bl_test_..." -H "Content-Type: application/json" \
  -d '{ "name": "Gym membership" }'

# a monthly plan for it, R599.00 (amounts are in cents)
curl -X PUT https://www.subcharge.co.za/v1/plans/by-external-id/gold-monthly \
  -H "Authorization: Bearer bl_test_..." -H "Content-Type: application/json" \
  -d '{ "productExternalId": "gym", "name": "Gold monthly",
        "billingInterval": "month", "intervalCount": 1, "amountCents": 59900 }'

Prices never change, so a new price gets a new id

What a customer was billed has to stay explainable years later, so a plan's amount and interval are fixed once it exists. Sending the same plan id again with identical terms is fine (it returns the same plan, and you may rename it). Sending different terms is rejected.

Plan prices never change; a new price gets a new idPlan gold-monthly is created at R599. Sending it again with R649 is rejected with a 409 plan_immutable error. Creating gold-monthly-2027 at R649 succeeds. Existing subscriptions keep billing on the old plan until you move them.gold-monthlyR599 / monthcreatedsend R649409plan_immutablesame id, new priceuse a new idgold-monthly-2027R649 / monthcreated
Existing subscriptions keep billing on the old plan; new ones use the new plan.

3. Start a subscription in one call

You do not have to create anything first. Describe the customer and plan by your ids, and SubCharge finds what exists and creates what does not, all in one request. If any part fails, none of it is kept.

One request creates everything, all or nothingA single create-subscription request carries your customer id and plan id. Inside one transaction SubCharge finds or creates the customer, the product, the plan and then the subscription. If any step fails, nothing is kept.POST/subscriptionsyour customer idyour plan idone transaction1find or create the customer2find or create the product3find or create the plan4create the subscriptionIf any step fails, nothing is kept.
Everything happens in one transaction, so a failed request leaves no half-created records behind.
curl -X POST https://www.subcharge.co.za/v1/subscriptions \
  -H "Authorization: Bearer bl_test_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-5521" \
  -d '{
    "billingInterval": "month",
    "customer": { "externalId": "crm_8431", "email": "ada@example.com", "name": "Ada Lovelace" },
    "items": [{
      "plan": {
        "externalId": "gold-monthly",
        "name": "Gold monthly",
        "amountCents": 59900,
        "product": { "externalId": "gym", "name": "Gym membership" }
      }
    }]
  }'
  • Send either customerId (our id) or customer (your id), not both. Each item takes either planId or plan.
  • Once a plan exists you can refer to it by id alone: "plan": { "externalId": "gold-monthly" }. The amount and product are only needed the first time.
  • The plan takes the subscription's billingInterval and intervalCount.
  • Send an Idempotency-Key so a retry returns the stored response instead of creating a second subscription.

Your ids come back

You never need to store our ids to find your own records:

  • customer, product and plan objects include externalId.
  • subscription and invoice responses include customerExternalId.
  • Webhook events whose payload names a customer (invoice.paid, payment.failed and so on) include customerExternalId. See webhooks.
  • Creating or changing a customer through the call above sends customer.created or customer.updated (only when something actually changed).
// invoice.paid webhook payload (abridged)
{ "id": "…", "status": "paid", "totalCents": 59900,
  "customerId": "…",
  "customerExternalId": "crm_8431" }

Billing-only mode

If your customers and products live in your own system, you do not need our screens for them. Turn on Billing-only mode under Settings and the dashboard sidebar drops Customers and Products. Subscriptions and Invoices show your own customer id under each email, and the Overview swaps the customer count for the number of past-due subscriptions.

Billing-only mode hides Customers and ProductsThe normal dashboard sidebar lists Overview, Customers, Subscriptions, Products, Invoices and Settings. With billing-only mode on, Customers and Products are removed and the rest stay.NormalOverviewCustomersSubscriptionsProductsInvoicesSettingsbilling-only onBilling-onlyOverviewSubscriptionsInvoicesSettings
Only what you see changes. Your data, the API and every other page are untouched, and you can switch it off at any time.

It is a display setting, not a permission: the pages are still reachable by URL (with a reminder that billing-only mode is on), and the API behaves exactly the same either way.

Errors and limits

  • 409plan_immutable

    You sent an existing plan id with a different amount, interval or product. The response names the fields in `param`.

    Fix: Create a new plan with a new id (for example gold-monthly-2027).

  • 422catalog_plan_limit_reached

    Creating one more plan would exceed the account limit (500 plans).

    Fix: Reuse existing plans, or archive ones you no longer sell.

  • 422invalid_request

    A customer or plan reference is missing or ambiguous: both customerId and customer, neither, or a new plan with no amountCents or product.

    Fix: Send exactly one of each pair; include amountCents and product when the plan is new.

  • 404not_found

    A product you referenced by id does not exist in your account.

    Fix: Create the product first, or reference it by its own id when creating the plan inline.

Plans are created once per distinct price, so avoid creating a plan per customer. If you need a different price for every customer, talk to us first; this is a limit we want to design for properly.

What's coming

These are planned, not released. They are not needed to use the API above.

  • Send your own emails.An option where SubCharge sends nothing to your customers and you drive invoice and reminder emails from webhooks. In that mode a customer's email becomes optional, and the customer portal (which signs in by email) is not available for customers without one.

Your customers' data and POPIA

You decide what personal information you send us. We process it on your instruction as your operator (see the data processing agreement). Erasing a customer anonymises their personal details but keeps the invoices and payments we must retain for tax records.

Back to the developer docs.