Quickstart
Issue an invoice, open its payment page, and know when it has been paid.
Four calls take you from nothing to money in your balance: create the document, open its page, hand the customer the URL, then listen for the payment. Nothing happens between those steps on its own, which is the thing most people get wrong on their first invoice.
Live only, and your first invoice is real
Invoices do not exist in sandbox. Every /invoices route answers 403 with This feature is only available in live mode. to a sk_test. key, GET included, so there is nothing to try against
first. Issue your first one to yourself for a small amount. The feature is Invoicing: 10
invoices in total on Pay as you go, unlimited from Growth upward.
1. Create the document
An invoice is created as a draft and stays there until you move it. Creating one writes a numbered document and nothing else: no money moves, no mail goes out, and the customer learns nothing.
Four fields are required, and a customer record is not among them. An invoice is addressed to a name, so you can bill someone who has never paid you before.
https://api.wajub.com/invoicescustomer_namestringrequireditemsarrayrequiredname, quantity and unit_price.invoice_datestring (date)requiredcurrencystringrequiredcustomer_emailstringoptional/send e-mails the invoice and its PDF, and the address its payment and receipt use. Without it, /send answers 422 unless customer_id names a customer with an e-mail; an invoice sent from the Dashboard without one has its page ask the payer.due_datestring (date)optionalinvoice_date. What decides when the invoice turns overdue.acceptobjectoptionalcountries, channels, types and currencies. See [Choosing what a payment accepts](/payments/payment-methods#choosing-what-a-payment-accepts).customer_idstringoptionalcus_ id. Their details are copied onto the document at creation.The full field list, taxes, discounts and the rest, is on the Invoice page. Send this one 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": "[email protected]",
"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. Totals are computed from the lines, so subtotal, total and
amount_due come back filled even though you never sent them.
Keep the id: every call that follows takes it. invoice_number is still null. The human
reference printed on the document is assigned in the next step, 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.
2. Open its payment page
A draft invoice cannot be paid. Its public page answers 404 Invoice not found. to anyone who opens
it, and only four statuses are payable: sent, viewed, overdue and partial.
One call moves it out of draft.
https://api.wajub.com/invoices/{id}/sendcurl -X POST https://api.wajub.com/invoices/inv_LRQqYvlhrgUOE225KMKU/send \
-H "Authorization: sk.xxxxxxxxxxxxxxxx…"It answers 200 with status: "sent", sent_at stamped and the invoice_number assigned. The
call also e-mails customer_email a link to the invoice, with a PDF copy attached. Calling it again
later e-mails the invoice again and stamps sent_at anew, without changing its status.
The call refuses to send to nobody. Without customer_email, and without a customer_id whose
customer has an e-mail, it answers 422 on customer_email and the invoice stays a draft. An
invoice already paid, cancelled or refunded answers 400.
3. Hand over the URL
The e-mail is not always enough: WhatsApp or SMS may be how you actually reach that customer. The address to share is the invoice id on its own host.
https://invoice.wajub.com/inv_LRQqYvlhrgUOE225KMKUThat URL is not in the API response, so build it from the id you kept. The host is the one thing
that varies: a merchant serving invoices from a verified custom domain gets that domain instead, and
the Dashboard shows the real address on the invoice.
What opens is an ordinary checkout carrying your branding, the invoice's lines and the operators
available for its currency. The first time the customer opens it, the invoice moves from sent to
viewed on its own.
If you left customer_email out, /send refuses the invoice: send it from the Dashboard instead,
which opens its page without e-mailing anyone. The page then shows the payer an e-mail field above
the pay button and opens the payment only once it is filled in. That address receives the payment
confirmation and its receipt, and it is written on the invoice as its customer_email
(invoice.updated is sent), so later installments and /send use it.
4. Know when it has been paid
A paid invoice produces two events, and they are not interchangeable.
payment.succeeded is the money. It carries the amount, the channel, the provider reference, and
it is the one to fulfil on, because it is the only one that proves funds moved.
invoice.updated is the document. It fires on every change the invoice goes through, and paid is
just one of them.
invoice.updatedeventTwo more events follow the money on the document's side. invoice.payment_succeeded arrives for
each payment made on the invoice's page, an instalment included, and invoice.paid once the
invoice is settled in full. Both carry the invoice. There is no invoice.sent and no
invoice.overdue: for those, the status on an invoice.updated payload is what tells you which
transition you are looking at. And payment.succeeded names the invoice it paid in its invoice
field.
Fulfil on the payment, reconcile on the invoice
Deliver what the customer bought when payment.succeeded arrives, and use invoice.updated to
keep your own copy of the document in step. Doing it the other way round means acting on a status
that a manual mark-paid can also produce.
When the money came in elsewhere
Cash, a bank transfer, a payment that never touched Wajub: record it against the document rather than leaving the invoice open.
https://api.wajub.com/invoices/{id}/mark-paidSend no amount and the invoice is settled in full. Send one, above zero, and it is added to
amount_paid, which leaves the invoice partial until the total is covered. A draft, an invoice
already paid, a cancelled one and a refunded one refuse the call with 400; an amount of zero
answers 422.
The statuses, and who writes them
Only three of the eight are yours to set. The rest are written by the customer's own actions or by Wajub overnight, which is worth knowing before you build a state machine of your own on top.
| Status | Meaning | Written by |
|---|---|---|
draft | Created, not payable | Creation |
sent | Payable, waiting | Your /send call |
viewed | The customer opened the page | The customer, on first open |
partial | Some of the total is in | A payment, or your /mark-paid |
paid | Settled in full | A payment, or your /mark-paid |
overdue | Past due_date, still unpaid | Wajub, overnight once due_date has passed |
cancelled | Closed, no longer payable | Your /cancel call |
refunded | Paid, then given back | Refunds covering every payment that settled it online |
Related pages
- Invoice referenceEvery field, how the totals are computed, and the eight calls.
- Recurring invoicesWhat the recurring flag does today, and what it does not.
- Reminders & follow-upChasing an invoice that has gone quiet.
- CustomersBill a customer record rather than a name.
- Invoices API referenceEvery field and the eight endpoints.