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
- 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.
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.
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) orcustomer(your id), not both. Each item takes eitherplanIdorplan. - 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
billingIntervalandintervalCount. - Send an
Idempotency-Keyso 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,productandplanobjects includeexternalId.subscriptionandinvoiceresponses includecustomerExternalId.- Webhook events whose payload names a customer (
invoice.paid,payment.failedand so on) includecustomerExternalId. See webhooks. - Creating or changing a customer through the call above sends
customer.createdorcustomer.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.
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
- 409
plan_immutableYou 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).
- 422
catalog_plan_limit_reachedCreating one more plan would exceed the account limit (500 plans).
Fix: Reuse existing plans, or archive ones you no longer sell.
- 422
invalid_requestA 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.
- 404
not_foundA 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.