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.
| What you need | What to reach for |
|---|---|
| Bill a customer once and get paid online | Invoice |
| Bill the same customer at a fixed interval | A recurring invoice: a new draft each cycle, still to be sent and paid |
| Charge a saved wallet on a schedule, unattended | Nothing yet, see Subscriptions |
| Take a payment your own checkout starts | Payments, not billing |
Live only, and plan-gated
Every /invoices route is live only, 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)requiredcurrencystringrequired422. Every amount on the invoice is in it.languageenumoptionalfr or en, the language of the e-mail and the PDF. Defaults to the linked customer's preferred language, else your account's language (**Settings → Regional → Language**), else en.customer_idstringoptionalcus_ id. Their details are snapshotted onto the document at creation.customer_emailstringoptional/send e-mails the invoice and its PDF, and the address its payment and receipt use. Optional: an invoice without one stays payable, and [its page asks the payer](#where-the-customer-pays) for an e-mail.due_datestring (date)optionalinvoice_date. The invoice turns overdue once this date has passed.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.xxxxxxxxxxxxxxxx…" \
-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. invoice_number is still
null: the human reference printed on the document is assigned when the invoice is sent, from your
account's numbering, the same sequence the Dashboard uses: INV- (or the prefix you set), the year,
then a counter that restarts at 0001 each year.
The life of an invoice
The repetition on those arrows is the point. Every transition the API makes arrives as
invoice.updated, and the status field on the payload is what tells you which one it was. Two
events say more: invoice.paid when the invoice is settled in full, and invoice.payment_succeeded
for each payment made on its page, an instalment included. There is no invoice.sent, no
invoice.cancelled and no invoice.overdue.
Two of those states move on their own. viewed is written the first time the customer opens the
payment page, and an invoice turns overdue overnight once its due_date has passed.
Neither is something you set.
Sending an invoice
https://api.wajub.com/invoices/{id}/sendOn a draft, the call assigns the invoice_number, moves the invoice to sent, stamps sent_at,
and e-mails customer_email a link to the invoice with a PDF copy attached, in the invoice's
language. Calling it again on an invoice already sent e-mails it again and stamps sent_at anew,
without changing its status.
The call needs someone to send to: customer_email, or the e-mail of the customer customer_id
links. Without either it answers 422 on customer_email and changes nothing, a draft included.
An invoice already paid, cancelled or refunded answers 400: there is nothing left to ask
for. A draft addressed to a name only can be sent from the Dashboard, which numbers it and opens
its page without e-mailing anyone; share the link yourself, and the page asks the payer for an
e-mail.
Sending is also what opens the payment page. A draft answers 404 Invoice not found. to anyone
who opens its URL, as if it did not exist, and only four statuses are payable: sent, viewed,
overdue and partial. Any other status answers 403 This invoice is not available for payment.
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 opens in the invoice's language, the one its e-mail and PDF are written in. The payer can
show it in the other by adding ?locale=fr or ?locale=en to the address.
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. The payment names the invoice in its invoice field,
the invoice's id.
Refund that payment in full, or every payment made on the invoice, and the invoice moves to
refunded, with refunded_at stamped and invoice.updated sent. That holds for an invoice paid in
part too, partial or overdue with a part paid: once what came in online has all gone back, it is
refunded. A partial refund leaves the invoice as it was. An invoice that holds money recorded with
mark-paid keeps its status: that money never went through Wajub, so a Wajub refund cannot give it
back. Issue a credit note from the Dashboard for it.
An invoice does not need an e-mail to be paid. When it carries none, meaning no customer_email and
no customer_id, the page shows the payer an e-mail field above the pay button and opens the
payment only once it holds a valid address. That address receives the payment confirmation and its
receipt, and the payment is recorded against the customer of yours that has
it, created with the invoice's name if you have none yet. The address is also written on the
invoice as its customer_email, and invoice.updated tells you so: the next installment does not
ask again, and /send and the Dashboard's reminders now have someone to write to. Only the first
address given is kept, and an e-mail you put on the invoice is never replaced.
The page asks for nothing when the invoice carries an e-mail, and the payer cannot substitute another address. It asks for nothing either when the invoice is linked to a customer: that customer is the one who pays, with or without an e-mail on their record.
The page also serves the invoice as a PDF, for the customer to keep, at
https://api.wajub.com/i/{id}/pdf, in the invoice's language unless ?locale=fr or ?locale=en
asks for the other. Anyone holding the id can open both, so share it the way you would share the
link itself.
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, above zero, and it is added to
amount_paid, which moves the invoice to partial until the total is covered. The invoice has to
be open: a draft answers 400, since it was never sent, and so does an invoice already paid,
which keeps its paid_at. A cancelled or refunded invoice refuses the call with 400 too, so a
document its own flow has closed cannot be resurrected. An amount of zero answers 422.
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. Every night at 02:00 UTC, Wajub generates the
copies that have come due: each recurring invoice whose next occurrence is due is copied into a
fresh document with new dates and the customer's current details. The first copy is due one interval after the
invoice's own date, and each cycle is generated once.
Two things shape what recurrence means here. Each generated invoice is created as a draft, so
the chain produces documents rather than charges, and each one still needs to be sent and paid.
And it needs the Recurring billing feature, which starts at Growth: without it no copy is
generated and the parent invoice is left intact, so renewals resume on their own if a downgraded
account upgrades again.
Creating a recurring invoice through the API succeeds on any plan; without the feature, it is the generation that is skipped. Recurring invoices covers the calendar, what a copy carries over, and how a recurrence ends.
What reaches your webhook
Five events, all of them carrying the invoice rather than the money.
| Event | Fires when |
|---|---|
invoice.created | An invoice is created, in draft: once, with its lines and totals |
invoice.updated | Any field or status changes through the API, plus viewed and a payment. One per PUT, with its lines and totals, and none for a PUT that changes nothing |
invoice.payment_succeeded | A payment made on the invoice's page succeeds, each instalment included |
invoice.paid | The invoice is settled in full, online or with mark-paid |
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, then invoice.updated and invoice.payment_succeeded
for the document, and invoice.paid once it is settled. payment.succeeded is the one to fulfil
on, since it carries the amount, the channel and the provider reference, and its invoice field
names the invoice it paid.
Only the API emits these
Changes made in the Dashboard send no webhook: sending an invoice, marking it paid or cancelling
it from the interface is silent. Invoices turn overdue overnight, and that change sends no
webhook 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. On top of what the API does, it tracks reminders and exports the list to CSV, and it has four tools the API does not:
- Import creates invoices from a CSV or Excel file, 10 MB at most.
- Bulk actions send, cancel or delete up to 100 invoices at once.
- Duplicate copies an invoice into a new one, opened for editing.
- Credit note issues a credit note against an invoice that has money paid on it, for up to
what was paid, with a reason. It is a document: it moves no money. It lowers the invoice's
amount_paidby its amount, and a later payment on the invoice counts it: the invoice turnspaidonce what was paid, less its credit notes, covers the total.
An unpaid invoice turns overdue overnight once its due_date has passed, and reminders go out on
their own 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.