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
Where /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)optional
On or after invoice_date. What decides when the invoice turns overdue.
acceptobjectoptional
Narrows the payment methods the invoice page offers, by countries, channels, types and currencies. See [Choosing what a payment accepts](/payments/payment-methods#choosing-what-a-payment-accepts).
customer_idstringoptional
Attaches an existing [customer](/payments/customers), by its cus_ 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.

Response · 201 Created
{
"code": 201,
"status": "Created",
"message": "Invoice created",
"invoice": {
"id": "inv_LRQqYvlhrgUOE225KMKU",
"invoice_number": null,
"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 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.

POSThttps://api.wajub.com/invoices/{id}/send
curl -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.

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.

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.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-10-11",
"pending_webhooks": 1,
"request": {
"id": null,
"idempotency_key": null
},
"data": {
"id": "inv_LRQqYvlhrgUOE225KMKU",
"invoice_number": "INV-2026-0001",
"status": "paid",
"currency": "XAF",
"total": 450000,
"amount_paid": 450000,
"amount_due": 0,
"paid_at": "2026-09-11T15:42:09+00:00"
}
}

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

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

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

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 unpaidWajub, overnight once due_date has passed
cancelledClosed, no longer payableYour /cancel call
refundedPaid, then given backRefunds covering every payment that settled it online

What did you think of this content?