Billing & subscriptions
Invoices, how a customer pays one, and what recurring billing does today.
Payments and billing ask for money in two different ways. A payment is something your code starts: you call the API, the handset lights up, the customer confirms within the minute. An invoice is a document you hand over, and the customer settles it whenever they decide to.
This section is the second kind.
What the section contains
One product ships: Invoice. It produces the numbered document, hosts the page the customer pays on, and tracks what has come in against it. Subscriptions, in the sense of a plan a customer signs up to and a wallet charged every month without them acting, is not something you can call today.
Live only, and plan-gated
Every /invoices route sits behind the live-environment guard, so a sandbox key answers 403
with This feature is only available in live mode. on all of them, GET included. The feature
itself is invoicing: 10 invoices in total on Pay as you go, not ten per month, and
unlimited from Growth upward. Past the cap, creation answers 403 with an upgrade message.
An invoice is a document, not a charge
Creating one moves no money and notifies nobody. It writes a numbered document with its line items, its totals and a status, and stops there. Sending it, paying it and chasing it are three separate things that happen afterwards.
https://api.wajub.com/invoicescustomer_namestringrequireditemsarrayrequiredname, quantity and unit_price.invoice_datestring (date)requiredcurrencystringrequiredcustomer_idstring (uuid)optionalcustomer_emailstringoptionaldue_datestring (date)optionalinvoice_date. What the overdue job compares against.payment_terms_daysintegeroptionaldue_date that many days from today, and only when due_date is absent.items[].tax_ratenumberoptionalitems[].discount_typeenumoptionalpercentage or fixed, paired with items[].discount_value.items[].discount_valuenumberoptionaldocument_typeenumoptionaldefault : invoiceinvoice, quote or estimate. Only invoices are listed and retrieved by the API.is_recurringbooleanoptionalnotesstringoptionalterms and footer sit alongside it.Totals are computed for you from the lines, so you never send subtotal or total. Send the
invoice with a live secret key.
curl https://api.wajub.com/invoices \
-H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-amina-september" \
-d '{
"customer_name": "Amina Traoré",
"customer_email": "amina@example.com",
"invoice_date": "2026-09-11",
"due_date": "2026-09-25",
"currency": "XAF",
"items": [
{ "name": "Consulting, September", "quantity": 3, "unit_price": 150000 }
]
}'The answer is 201 Created. The id is what every other call takes, and invoice_number is the
human reference printed on the document: INV- plus the year and month, then a counter that
restarts at 0001 each month, per account.
The life of an invoice
The repetition on those arrows is the point. There is no invoice.sent, no invoice.paid and no
invoice.overdue: a transition that notifies you at all arrives as the same invoice.updated, and
the status field on the payload is what tells you which one it was.
Two of those states move on their own. viewed is written the first time the customer opens the
payment page, and overdue is written by a nightly job that compares due_date against today.
Neither is something you set.
Sending an invoice does not send it
POST /invoices/{id}/send delivers nothing
The call sets status to sent and stamps sent_at. That is all it does. It renders no PDF and
it emails nobody. The email with the PDF attached is sent by the Dashboard's own Send button,
which is a different code path the API does not reach.
What the call does do is open the payment page. A draft invoice answers 403 This invoice is not available for payment. to anyone who opens its URL, and only four statuses are payable: sent,
viewed, overdue and partial.
https://api.wajub.com/invoices/{id}/sendSo an API-driven flow has three steps rather than two: create the invoice, mark it sent to unlock its page, then deliver the URL yourself over whatever channel you already use to talk to that customer. Nothing leaves Wajub on your behalf.
Where the customer pays
The payment page lives on its own host, with the invoice id as the whole path.
https://invoice.wajub.com/inv_LRQqYvlhrgUOE225KMKUThat address is not in the API response. Build it from the id you got back, or copy it from the
Dashboard, which is also the only place it changes: a merchant serving invoices from a verified
custom domain gets that host instead of invoice.wajub.com.
The page itself is an ordinary checkout. It shows your branding, the invoice's lines, and the
operators available for its currency, and a payment made there is an ordinary payment: it lands in
your Payments list, fires payment.succeeded, settles into your balance and refunds like any
other. On success the invoice moves to paid, or to partial when the invoice is split into
installments and only one has been settled.
Recording money you took elsewhere
Plenty of invoices are paid in cash, or by a bank transfer that never touches Wajub. Marking one paid records that against the document without inventing a payment.
https://api.wajub.com/invoices/{id}/mark-paidPass no amount and the invoice is settled in full. Pass one and it is added to amount_paid,
which moves the invoice to partial until the total is covered. A cancelled or refunded invoice
refuses the call with 400, so a document its own flow has closed cannot be resurrected.
Recurring invoices, as they stand today
is_recurring and recurring_interval are accepted on creation, the interval being one of
daily, weekly, monthly, quarterly or yearly. A job runs every night at 02:00, looks for
recurring invoices whose next occurrence is due, and copies each one into a fresh document with new
dates and the customer's current details.
Nothing starts the chain
The job reads next_invoice_date, and the only code that ever writes that field is the job
itself, on the invoice it has just generated. Creation never sets it, through the API or the
Dashboard. So a recurring invoice is flagged, listed and filterable as recurring, and never
produces a second document. Treat recurrence as recorded intent for now and issue each cycle
yourself.
Two things are worth knowing for when that gap closes, because they shape what recurrence will
mean here. Each generated invoice is created as a draft, so even a working chain would produce
documents rather than charges, and each one would still need to be sent and paid. And the feature
is recurring_billing, which starts at Growth: the generator skips accounts that do not have
it and leaves the parent invoice intact, so renewals resume on their own if a downgraded account
upgrades again.
That gate is checked by the Dashboard and by the generator, but not by POST /invoices. Creating a
recurring invoice through the API succeeds on any plan; it is the generation that would refuse.
What reaches your webhook
Three events, all of them about the document rather than the money.
| Event | Fires when |
|---|---|
invoice.created | An invoice is created, in draft |
invoice.updated | Any field or status changes through the API, plus viewed and a payment |
invoice.deleted | A draft invoice is deleted |
The money arrives on a separate event. When a customer pays an invoice online you receive
payment.succeeded for the payment itself and invoice.updated for the document, and the first is
the one to fulfil on, since it carries the amount, the channel and the provider reference.
Only the API emits these
Webhooks are dispatched by the API, so a change made in the Dashboard reaches no endpoint: sending
an invoice, marking it paid or cancelling it from the interface is silent. The nightly overdue job
writes its status in bulk, which skips events entirely, so overdue never notifies either. Poll
GET /invoices if you need to see those.
In the Dashboard
Billing → Invoices is the full surface, and it is wider than the API: it renders the PDF, emails it to the customer with the payment link, tracks reminders, and exports the list to CSV.
Two scheduled jobs run behind it. Statuses are refreshed nightly at 01:00, which is what flips an
unpaid invoice to overdue, and reminders go out hourly to customers whose invoice is still open.
Related pages
- Invoice referenceEvery field, how the totals are computed, and the eight calls.
- Invoice QuickstartIssue your first invoice and get it paid.
- CustomersAttach an invoice to a customer record rather than a name.
- WebhooksListen for payment.succeeded and reconcile the invoice.
- Invoices API referenceEvery field and the eight endpoints.