Skip to content

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.

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.

POSThttps://api.wajub.com/invoices
customer_namestringrequired
Who the invoice is addressed to.
itemsarrayrequired
At least one line, each with name, quantity and unit_price.
invoice_datestring (date)required
The date printed on the document.
currencystringrequired
ISO 4217 code. Every amount on the invoice is in it.
customer_emailstringoptional
Prefills the payment page, and is where the Dashboard sends the PDF.
due_datestring (date)optional
On or after invoice_date. What decides when the invoice turns overdue.
customer_idstring (uuid)optional
Attaches an existing [customer](/payments/customers). 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.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. Totals are computed from the lines, so subtotal, total and amount_due come back filled even though you never sent them.

Response · 201 Created
{
"code": 201,
"status": "Created",
"message": "Invoice created",
"invoice": {
"id": "inv_LRQqYvlhrgUOE225KMKU",
"invoice_number": "INV-202609-0001",
"status": "draft",
"customer_name": "Amina Traoré",
"invoice_date": "2026-09-11",
"due_date": "2026-09-25",
"currency": "XAF",
"subtotal": 450000,
"total": 450000,
"amount_paid": 0,
"amount_due": 450000,
"sent_at": null,
"paid_at": null
}
}

Keep the id: every call that follows takes it. 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.

2. Open its payment page

A draft invoice cannot be paid. Its public page answers 403 This invoice is not available for payment. to anyone who opens it, and only four statuses are payable: sent, viewed, overdue and partial.

One call moves it out of draft.

POSThttps://api.wajub.com/invoices/{id}/send
curl -X POST https://api.wajub.com/invoices/inv_LRQqYvlhrgUOE225KMKU/send \
  -H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…"

It answers 200 with status: "sent" and sent_at stamped.

3. Hand over the URL

Delivery is yours: WhatsApp, SMS, your own email, whatever you already use with that customer. The address is the invoice id on its own host.

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

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

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.updatedevent
Fires on any change to the invoice. Read `data.status` to know which one.
Payload
{
"id": "evt_aio5DpN577tNU2vOxdmuZGhT",
"event": "invoice.updated",
"livemode": true,
"created": "2026-09-11T15:42:10+00:00",
"api_version": "2026-09-01",
"pending_webhooks": 1,
"request": {
"id": null,
"idempotency_key": null
},
"data": {
"id": "inv_LRQqYvlhrgUOE225KMKU",
"invoice_number": "INV-202609-0001",
"status": "paid",
"currency": "XAF",
"total": 450000,
"amount_paid": 450000,
"amount_due": 0,
"paid_at": "2026-09-11T15:42:09+00:00"
}
}

There is no invoice.paid, no invoice.sent and no invoice.overdue. The three events an invoice ever emits are invoice.created, invoice.updated and invoice.deleted, so the status on the payload is what tells you which transition you are looking at.

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.

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

Send no amount and the invoice is settled in full. Send one and it is added to amount_paid, which leaves the invoice partial until the total is covered. A cancelled or refunded invoice refuses the call with 400.

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 a nightly job, which is worth knowing before you build a state machine of your own on top.

StatusMeaningWritten by
draftCreated, not payableCreation
sentPayable, waitingYour /send call
viewedThe customer opened the pageThe customer, on first open
partialSome of the total is inA payment, or your /mark-paid
paidSettled in fullA payment, or your /mark-paid
overduePast due_date, still unpaidA job, nightly at 01:00
cancelledClosed, no longer payableYour /cancel call
refundedPaid, then given backA refund on the payment

What did you think of this content?