Build an Integration
This page covers the whole job of connecting an app (a school, an ISP, a SaaS product) to PimaBill. Each step links to the guide with the details. If you're an AI agent doing the integration, follow the steps in order and keep the rules for agents at the end.
1. Two environments, two keys
The hosted cloud runs two separate copies of PimaBill. They share no data.
| Live | Sandbox | |
|---|---|---|
| API base URL | https://api.pimabill.com/api | https://sandbox.pimabill.com/api |
| Dashboard | https://app.pimabill.com | The same, with the Sandbox switch (foot of the sidebar) on |
| API key | sk_live_... | sk_test_... |
| Money | Real | Test only |
- Get each key from Settings → API keys in the dashboard: with Sandbox off for the live key, and on for the test key. A key only works in the environment that issued it.
- Configure your app per environment, for example
PIMABILL_API_URLandPIMABILL_API_KEY. Never hard-code either. - Send the key as
Authorization: Bearer sk_.... Give each system the narrowest scopes it needs.
Build and test everything in the sandbox first, then repeat the same setup in live. See Sandbox.
2. Refer to things by your own codes, not IDs
Every record has a different ID in each environment. Your own identifiers don't change, so make your app depend on them:
| Record | Your identifier | Look it up with |
|---|---|---|
| Plan | code, e.g. diploma-monthly | GET /api/plans?code=diploma-monthly |
| Add-on (one-time product) | code, e.g. cert-bundle-3m | GET /api/add-ons?code=cert-bundle-3m |
| Customer | externalId: your user's ID | GET /api/customers?externalId=usr_123 |
If you store PimaBill IDs, for example the customer ID on your user record, store them per environment.
3. Set up your catalogue
Decide, for each thing you sell, whether it renews:
- It's billed again every period (a monthly fee, a yearly licence): create a plan with a price in each currency. See Plans & Pricing.
- It's bought once (a course, a bundle, a setup fee, a "3-month package" paid up front): create an add-on. It never renews; your app decides how long access lasts. See One-time products & partial payments.
Create the catalogue with a script that you can run against either environment and that is safe to run twice. If the code exists, update it; otherwise, create it.
4. Create customers
Create a PimaBill customer when your user first needs to pay, with your user's ID as externalId:
curl -X POST "$PIMABILL_API_URL/customers" \
-H "Authorization: Bearer $PIMABILL_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalId": "usr_123", "name": "Amina N.", "email": "amina@example.com", "country": "UG", "currency": "UGX" }'
A second create with the same externalId is refused with 409. Look the customer up with ?externalId= instead. See Customers.
5. Take payments
| You're selling | Call | Guide |
|---|---|---|
| A plan | POST /api/subscriptions, then POST /api/invoices/{id}/checkout for the first invoice | Subscriptions |
| A one-time product | POST /api/invoices with items: [{ "addOnCode": "...", "quantity": 1 }] and checkout: {} | One-time products |
| A custom amount | POST /api/invoices with your own items and checkout: {} | Charge a customer |
| A deposit or instalments | checkout.amount, or amount on /checkout and /mark-paid | Paying in parts |
| Cash or a bank transfer you received | POST /api/invoices/{id}/mark-paid | Invoices & Payments |
Each checkout returns a checkoutUrl. Redirect the customer to it, or send it by WhatsApp, SMS or email. Set callbackUrl to the page they come back to. That page should say "we're confirming your payment", not "paid": the webhook is what confirms it.
Payment providers. Checkouts use your own connected providers first, in priority order. If none can take the payment, they use managed payments (the provider named managed). With managed payments, PimaBill collects for you and pays out to your mobile money, bank or PayPal. You don't need a provider account to start. See Payment Providers and Managed payments.
6. Listen to webhooks
Your app learns about payments from webhooks, not from the redirect.
- Register your endpoint in each environment, with that environment's key:
For example, point live at your production app and the sandbox at your staging app. The key needs the
curl -X PATCH "$PIMABILL_API_URL/tenants/me" \ -H "Authorization: Bearer $PIMABILL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://your-app.example.com/webhooks/pimabill" }'writeortenants:writescope. - Read the signing secret from
GET /api/tenants/me(webhookSecret) and store it with your other secrets. - Check
X-Webhook-Signature-V2on every request before trusting it. See Handling webhooks. - Handle each event idempotently. Deliveries are retried, so the same event can arrive more than once. Key your handling on the invoice or payment ID.
The events most apps need:
| Event | What to do |
|---|---|
invoice.paid | Grant what was bought, or extend access for a renewal |
invoice.partially_paid | Record the part payment; apply your deposit policy |
payment.failed | Tell the customer; renewals are retried automatically |
subscription.canceled | Remove access at the end of what was paid for |
subscription.renewed | A new period began; its invoice follows |
See the full event list.
7. Let customers help themselves
Give customers a link to the hosted portal, where they can see invoices, download PDFs and pay. Your server asks for a short-lived link per customer with POST /api/portal/customers/{externalId}/sessions. Never put an API key in a link. See Customer Portal.
8. Go live
- Every flow passes in the sandbox: buy, renew, pay in parts, fail and retry, cancel.
- The catalogue script has run against live, with the same codes.
- Live has a payment provider: your own, or managed payments approved for your organisation.
- Your live webhook URL is set, its secret is stored, and signature checks are on.
- Your app's live config uses
https://api.pimabill.com/apiand thesk_live_key. - One small real payment has gone through and the
invoice.paidwebhook was received.
Rules for AI agents
If you're an agent integrating an app with PimaBill:
- Work in the sandbox (
sk_test_,https://sandbox.pimabill.com/api) until the owner says to go live. Never use a live key for tests. - Never print, log or commit API keys or the webhook secret. Read them from the app's secret store.
- Make every setup step safe to repeat. Look up by
codeorexternalIdbefore creating, and update instead of duplicating. - Don't create plans for things that are paid once. Use add-ons. Plans renew and charge again.
- Don't treat the redirect back from checkout as payment. Wait for
invoice.paid(orinvoice.partially_paid). - Ask the owner before anything irreversible in live: voiding invoices, refunds, deleting catalogue items, changing prices customers already pay.
- The API reference lists every endpoint and field. The OpenAPI document is at
https://api.pimabill.com/api/openapi.json.