Skip to content

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.

Your server creates the invoice, Wajub hosts the page it is paid on, and the cycle comes back round. Handing the address to the customer stays yours.

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 needWhat to reach for
Bill a customer once and get paid onlineInvoice
Bill the same customer at a fixed intervalA recurring invoice: a new draft each cycle, still to be sent and paid
Charge a saved wallet on a schedule, unattendedNothing yet, see Subscriptions
Take a payment your own checkout startsPayments, not billing

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.

POSThttps://api.wajub.com/invoices
customer_namestringrequired
Who the invoice is addressed to. A name is enough, a customer record is not required.
itemsarrayrequired
At least one line. Each takes name, quantity and unit_price.
invoice_datestring (date)required
The date printed on the document. Required: there is no default.
currencystringrequired
ISO 4217 code of a currency Wajub supports; any other answers 422. Every amount on the invoice is in it.
languageenumoptional
fr 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_idstringoptional
Links the invoice to one of your [customers](/payments/customers), by its cus_ id. Their details are snapshotted onto the document at creation.
customer_emailstringoptional
Where /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)optional
On or after invoice_date. The invoice turns overdue once this date has passed.
payment_terms_daysintegeroptional
Sets due_date that many days from today, and only when due_date is absent.
items[].tax_ratenumberoptional
Percentage on that line, 0 to 100. Applied per line, not per invoice.
items[].discount_typeenumoptional
percentage or fixed, paired with items[].discount_value.
items[].discount_valuenumberoptional
The amount taken off that line. Discounts are per line: the API sets none at invoice level.
document_typeenumoptionaldefault : invoice
invoice, quote or estimate. Only invoices are listed and retrieved by the API.
is_recurringbooleanoptional
Flags the document as recurring. Read the section on recurring invoices before you rely on it.
notesstringoptional
Free text under the lines, up to 5 000 characters. terms 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.

Response · 201 Created
{
"code": 201,
"status": "Created",
"message": "Invoice created",
"invoice": {
"id": "inv_LRQqYvlhrgUOE225KMKU",
"invoice_number": null,
"document_type": "invoice",
"status": "draft",
"customer_name": "Amina Traoré",
"customer_email": "amina@example.com",
"invoice_date": "2026-09-11",
"due_date": "2026-09-25",
"currency": "XAF",
"subtotal": 450000,
"discount_amount": 0,
"tax_amount": 0,
"total": 450000,
"amount_paid": 0,
"amount_due": 450000,
"is_recurring": false,
"sent_at": null,
"paid_at": null,
"created_at": "2026-09-11T14:00:00+00:00"
}
}

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

POSThttps://api.wajub.com/invoices/{id}/send

On 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.

The customer's URL
https://invoice.wajub.com/inv_LRQqYvlhrgUOE225KMKU

That 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.

POSThttps://api.wajub.com/invoices/{id}/mark-paid

Pass 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.

EventFires when
invoice.createdAn invoice is created, in draft: once, with its lines and totals
invoice.updatedAny 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_succeededA payment made on the invoice's page succeeds, each instalment included
invoice.paidThe invoice is settled in full, online or with mark-paid
invoice.deletedA 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.

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_paid by its amount, and a later payment on the invoice counts it: the invoice turns paid once 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.

What did you think of this content?