Skip to content

Invoice ​

Create programmable invoices via API and let your customers pay through the standard checkout gateway. Invoices support partial payments, tax calculations, line items, and automated status tracking.

Required permission: invoices

How it works ​

  1. Create an invoice via API with customer details, line items, and due date
  2. Send the invoice (transitions from draft to sent)
  3. Share the payment_url with your customer
  4. Customer opens the payment URL and pays via bank transfer or PayID
  5. Invoice status updates automatically: partial → paid
  6. You receive webhook events for each status change

Create Invoice ​

POST /v1/checkout/invoice

Creates a new invoice in draft status. You must send the invoice before customers can pay.

Authentication ​

Requires a secret key (sk_*).

Request body ​

ParameterTypeRequiredDescription
customer_namestringYesCustomer's full name (max 255 characters)
customer_emailstringYesCustomer's email address
customer_addressstringNoCustomer's street address
customer_citystringNoCustomer's city (max 100)
customer_statestringNoCustomer's state/region (max 100)
customer_countrystringNoCustomer's country (max 100)
line_itemsarrayYesArray of line item objects (at least one)
line_items[].descriptionstringYesDescription of the item (max 500)
line_items[].quantitynumberYesQuantity (supports decimals, min 0.01)
line_items[].unit_priceintegerYesUnit price in kobo
tax_ratenumberNoTax rate as percentage (0–100, e.g., 7.5 for 7.5%). Default: 0. Mutually exclusive with tax_amount
tax_amountintegerNoFlat tax amount in kobo, taken verbatim: total = subtotal + tax_amount, exactly. Use when your system computes tax itself. Mutually exclusive with tax_rate
due_datestringYesDue date in ISO 8601 format
invoice_numberstringNoCustom invoice number (max 64). Auto-generated if omitted (e.g., INV-202603-001)
referencestringNoYour unique reference for reconciliation (max 255)
notestringNoNote to display on the invoice payment page (max 2000)
payment_methodsstring[]NoMethods to offer on this invoice's checkout: bank_transfer, payid, card, crypto. Omit for your key's configuration. See Choosing payment methods
crypto_assetsstring[]NoNarrow the crypto assets offered, e.g. ["USDT:TRON"]
metadataobjectNoCustom key-value data stored with the invoice

Example request ​

bash
curl -X POST https://api.zevpaycheckout.com/v1/checkout/invoice \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_test_your_secret_key" \
  -d '{
    "customer_name": "Chidi Okonkwo",
    "customer_email": "chidi@example.com",
    "customer_address": "15 Admiralty Way, Lekki Phase 1",
    "customer_city": "Lagos",
    "customer_state": "Lagos",
    "customer_country": "Nigeria",
    "line_items": [
      {
        "description": "Website Development",
        "quantity": 1,
        "unit_price": 35000000
      },
      {
        "description": "Monthly Hosting (12 months)",
        "quantity": 12,
        "unit_price": 500000
      }
    ],
    "tax_rate": 7.5,
    "due_date": "2026-03-31T00:00:00Z",
    "reference": "PROJECT-2026-001",
    "note": "Thank you for your business!",
    "metadata": {
      "project_id": "PRJ-123"
    }
  }'
javascript
const response = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer sk_test_your_secret_key",
    },
    body: JSON.stringify({
      customer_name: "Chidi Okonkwo",
      customer_email: "chidi@example.com",
      customer_address: "15 Admiralty Way, Lekki Phase 1",
      customer_city: "Lagos",
      customer_state: "Lagos",
      customer_country: "Nigeria",
      line_items: [
        { description: "Website Development", quantity: 1, unit_price: 35000000 },
        { description: "Monthly Hosting (12 months)", quantity: 12, unit_price: 500000 },
      ],
      tax_rate: 7.5,
      due_date: "2026-03-31T00:00:00Z",
      reference: "PROJECT-2026-001",
      note: "Thank you for your business!",
      metadata: { project_id: "PRJ-123" },
    }),
  }
);
const data = await response.json();
python
import requests

response = requests.post(
    "https://api.zevpaycheckout.com/v1/checkout/invoice",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer sk_test_your_secret_key",
    },
    json={
        "customer_name": "Chidi Okonkwo",
        "customer_email": "chidi@example.com",
        "customer_address": "15 Admiralty Way, Lekki Phase 1",
        "customer_city": "Lagos",
        "customer_state": "Lagos",
        "customer_country": "Nigeria",
        "line_items": [
            {"description": "Website Development", "quantity": 1, "unit_price": 35000000},
            {"description": "Monthly Hosting (12 months)", "quantity": 12, "unit_price": 500000},
        ],
        "tax_rate": 7.5,
        "due_date": "2026-03-31T00:00:00Z",
        "reference": "PROJECT-2026-001",
        "note": "Thank you for your business!",
        "metadata": {"project_id": "PRJ-123"},
    },
)
data = response.json()

Response ​

json
{
  "success": true,
  "data": {
    "public_id": "abc123def456ghi78",
    "invoice_number": "INV-202603-001",
    "status": "draft",
    "customer_name": "Chidi Okonkwo",
    "customer_email": "chidi@example.com",
    "customer_address": "15 Admiralty Way, Lekki Phase 1",
    "customer_city": "Lagos",
    "customer_state": "Lagos",
    "customer_country": "Nigeria",
    "subtotal": 41000000,
    "tax_rate": 7.5,
    "tax_amount": 3075000,
    "total": 44075000,
    "amount_paid": 0,
    "currency": "NGN",
    "due_date": "2026-03-31T00:00:00.000Z",
    "issued_at": null,
    "paid_at": null,
    "cancelled_at": null,
    "merchant_reference": "PROJECT-2026-001",
    "metadata": { "project_id": "PRJ-123" },
    "note": "Thank you for your business!",
    "payment_url": "https://invoice.zevpaycheckout.com/pay/abc123def456ghi78?token=...",
    "created_at": "2026-03-08T10:00:00.000Z"
  }
}

Response fields ​

FieldTypeDescription
public_idstringUnique public identifier for the invoice
invoice_numberstringHuman-readable invoice number (auto-generated or custom)
statusstringCurrent status: draft, sent, partial, paid, overdue, cancelled
customer_namestringCustomer's full name
customer_emailstringCustomer's email address
customer_addressstring | nullCustomer's street address
customer_citystring | nullCustomer's city
customer_statestring | nullCustomer's state/region
customer_countrystring | nullCustomer's country
subtotalintegerSubtotal in kobo (sum of line items, before tax)
tax_ratenumberTax rate as a percentage (e.g., 7.5). 0 when the invoice uses a flat tax_amount
tax_amountintegerTax amount in kobo (derived from tax_rate, or the flat amount you supplied)
totalintegerTotal amount in kobo (subtotal + tax)
amount_paidintegerCumulative amount paid so far, in kobo
currencystringCurrency code. Always "NGN"
due_datestringDue date in ISO 8601 format
issued_atstring | nullTimestamp when invoice was sent (ISO 8601). null for drafts
paid_atstring | nullTimestamp when invoice was fully paid. null until paid
cancelled_atstring | nullTimestamp when invoice was cancelled. null unless cancelled
merchant_referencestring | nullYour custom reference, if provided
metadataobject | nullYour custom key-value data, if provided
notestring | nullInvoice note, if provided
payment_urlstringURL for the customer to view and pay the invoice
created_atstringInvoice creation timestamp (ISO 8601)

Amounts

All monetary amounts in the API are in kobo (1 NGN = 100 kobo). For example, 35000000 kobo = ₦350,000.00.

Payment URL

Store the public_id (and optionally the payment_url) in your database. The payment_url resolves to the payment page when the invoice is payable, or shows a receipt when paid.


Update Invoice ​

PATCH /v1/checkout/invoice/:publicId

Updates an invoice. What you may change depends on whether it has been sent.

Draft invoices are fully editable, including line items, tax and the customer.

Sent invoices can still have their terms changed, because things like extending a due date are ordinary and should not cost you the invoice's history or its link:

Editable after sendingNot editable after sending
due_dateline_items
notetax_rate, tax_amount
allow_partial_paymentscustomer_name, customer_email, customer_address
minimum_initial_paymentinvoice_number
subsequent_payment_rule
due_date_action
payment_methods, crypto_assets
return_url, merchant_reference, metadata

The second column is the money and who owes it. Changing that after a customer has agreed the invoice, and possibly already paid part of it, produces a different document rather than an edited one. Cancel and issue a new invoice instead.

Paid and cancelled invoices cannot be edited at all.

Extending a due date ​

The common case. Send the new date and nothing else:

bash
curl -X PATCH https://api.zevpaycheckout.com/v1/checkout/invoice/{public_id} \
  -H "x-api-key: sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{ "due_date": "2026-07-31T00:00:00Z" }'

An invoice that had already gone overdue returns to sent, or to partial if part of it is paid, as soon as the new date is in the future. The customer stops seeing a past-due invoice immediately rather than waiting for the next sweep.

The change is recorded on the invoice's activity log as "Due date extended", with the old and new dates, so there is a record of when the terms moved and what moved them.

Authentication ​

Requires a secret key (sk_*).

Path parameters ​

ParameterTypeDescription
publicIdstringThe invoice's public_id

Request body ​

All fields are optional. Only include the fields you want to change.

ParameterTypeDescription
customer_namestringCustomer's full name
customer_emailstringCustomer's email address
customer_addressstringCustomer's street address
customer_citystringCustomer's city
customer_statestringCustomer's state/region
customer_countrystringCustomer's country
line_itemsarrayReplacement line items (replaces all existing items)
line_items[].descriptionstringItem description
line_items[].quantitynumberQuantity
line_items[].unit_priceintegerUnit price in kobo
tax_ratenumberTax rate as percentage (0–100). Mutually exclusive with tax_amount
tax_amountintegerFlat tax amount in kobo (total = subtotal + tax_amount, exactly). Mutually exclusive with tax_rate
due_datestringDue date in ISO 8601 format
invoice_numberstringCustom invoice number
notestringInvoice note
return_urlstringURL the customer lands on after paying
allow_partial_paymentsbooleanWhether the customer may pay less than the remaining balance
payment_methodsstring[]Methods to offer on this invoice's checkout. Send [] to fall back to your key's configuration
crypto_assetsstring[]Narrow the crypto assets offered. Send [] to offer every eligible asset
metadataobjectCustom key-value data

Line items replacement

When you include line_items in an update, all existing line items are replaced, not merged. Always send the complete list of line items you want on the invoice.

Example request ​

bash
curl -X PATCH https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_test_your_secret_key" \
  -d '{
    "note": "Updated: payment due by end of month",
    "due_date": "2026-04-15T00:00:00Z"
  }'
javascript
const response = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78",
  {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer sk_test_your_secret_key",
    },
    body: JSON.stringify({
      note: "Updated: payment due by end of month",
      due_date: "2026-04-15T00:00:00Z",
    }),
  }
);
const data = await response.json();
python
import requests

response = requests.patch(
    "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78",
    headers={
        "Content-Type": "application/json",
        "Authorization": "Bearer sk_test_your_secret_key",
    },
    json={
        "note": "Updated: payment due by end of month",
        "due_date": "2026-04-15T00:00:00Z",
    },
)
data = response.json()

Response ​

Returns the updated invoice object (same shape as Create Invoice response).

Errors ​

StatusMessageCause
404Invoice not foundInvoice does not exist or does not belong to your account
400Cannot edit an invoice with status "paid" / "cancelled"The invoice is closed
400An invoice that has been sent cannot changeYou tried to edit line items, tax or the customer after sending. Adjust the terms instead, or cancel and re-issue
400Invoice total must be greater than zeroUpdated line items result in zero or negative total
400minimum_initial_payment cannot be more than the invoice totalThe deposit exceeds what is being billed
400minimum_initial_payment cannot be changed after the first payment has been madeThe deposit rule only applies to the first payment, which has already happened

List Invoices ​

GET /v1/checkout/invoice

Returns a paginated list of your invoices, ordered by creation date (newest first).

Authentication ​

Requires a secret key (sk_*).

Query parameters ​

ParameterTypeRequiredDescription
pageintegerNoPage number (min 1). Default: 1
page_sizeintegerNoItems per page (1–100). Default: 20
statusstringNoFilter by status: draft, sent, partial, paid, overdue, cancelled
customer_emailstringNoFilter by exact customer email address
searchstringNoSearch by invoice number, customer name, or customer email (partial match)

customer_email vs search

customer_email performs an exact match on the email field. search performs a partial match (contains) across invoice number, customer name, and email. Use customer_email when you know the exact email; use search for flexible lookups.

Example request ​

bash
# List all sent invoices
curl "https://api.zevpaycheckout.com/v1/checkout/invoice?status=sent&page=1&page_size=20" \
  -H "Authorization: Bearer sk_test_your_secret_key"

# Filter by customer email
curl "https://api.zevpaycheckout.com/v1/checkout/invoice?customer_email=chidi@example.com" \
  -H "Authorization: Bearer sk_test_your_secret_key"
javascript
// List all sent invoices
const response = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice?status=sent&page=1",
  {
    headers: { "Authorization": "Bearer sk_test_your_secret_key" },
  }
);
const data = await response.json();

// Filter by customer email
const byEmail = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice?customer_email=chidi@example.com",
  {
    headers: { "Authorization": "Bearer sk_test_your_secret_key" },
  }
);
python
import requests

# List all sent invoices
response = requests.get(
    "https://api.zevpaycheckout.com/v1/checkout/invoice",
    headers={"Authorization": "Bearer sk_test_your_secret_key"},
    params={"status": "sent", "page": 1, "page_size": 20},
)
data = response.json()

# Filter by customer email
by_email = requests.get(
    "https://api.zevpaycheckout.com/v1/checkout/invoice",
    headers={"Authorization": "Bearer sk_test_your_secret_key"},
    params={"customer_email": "chidi@example.com"},
)

Response ​

json
{
  "success": true,
  "data": {
    "items": [
      {
        "public_id": "abc123def456ghi78",
        "invoice_number": "INV-202603-001",
        "status": "sent",
        "customer_name": "Chidi Okonkwo",
        "customer_email": "chidi@example.com",
        "customer_address": "15 Admiralty Way, Lekki Phase 1",
        "customer_city": "Lagos",
        "customer_state": "Lagos",
        "customer_country": "Nigeria",
        "subtotal": 41000000,
        "tax_rate": 7.5,
        "tax_amount": 3075000,
        "total": 44075000,
        "amount_paid": 0,
        "currency": "NGN",
        "due_date": "2026-03-31T00:00:00.000Z",
        "issued_at": "2026-03-08T10:05:00.000Z",
        "paid_at": null,
        "cancelled_at": null,
        "merchant_reference": "PROJECT-2026-001",
        "metadata": { "project_id": "PRJ-123" },
        "note": "Thank you for your business!",
        "payment_url": "https://invoice.zevpaycheckout.com/pay/abc123def456ghi78?token=...",
        "created_at": "2026-03-08T10:00:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "page_size": 20,
    "total_pages": 1
  }
}

Pagination fields ​

FieldTypeDescription
itemsarrayArray of invoice objects (same fields as Create response)
totalintegerTotal number of invoices matching the filters
pageintegerCurrent page number
page_sizeintegerNumber of items per page
total_pagesintegerTotal number of pages

Get Invoice ​

GET /v1/checkout/invoice/:publicId

Returns a single invoice with its line items and payment history.

This is the endpoint to reach for when your records and ours disagree. One call tells you what the invoice is worth, what has been paid, what is still owed, and every payment that made up the difference, each with the reference of the transaction it settled as.

json
{
  "status": "partial",
  "total": 180000000,
  "amount_paid": 120000000,
  "amount_outstanding": 60000000,
  "collecting": true,
  "allow_partial_payments": true,
  "minimum_initial_payment": 60000000,
  "subsequent_payment_rule": "flexible",
  "payments": [
    {
      "payment_id": "ipy_7Bn21kFd5Qa",
      "amount": 60000000,
      "paid_at": "2026-04-02T09:15:44.000Z",
      "payer_name": "ADAEZE NWOSU",
      "payment_channel": "bank_transfer",
      "transaction": {
        "reference": "ZVP-CKOTX-3d81be",
        "fees": 200000,
        "net_amount": 59800000,
        "settlement_state": "paid_out"
      }
    }
  ]
}

amount_outstanding is served rather than left to you to subtract, because client-side arithmetic on money is where rounding bugs start.

Recovering from a missed webhook ​

Webhooks are delivered at least once and retried, but a receiver that was down, a deploy that dropped a request, or a bug in your handler all end the same way: your side thinks an invoice is unpaid when it is not.

You do not need a special endpoint for that. Fetch the invoice:

  • status and amount_outstanding tell you where it stands now.
  • payments[] is the full history. Anything in it that is not in your ledger is what you missed.
  • payments[].transaction.reference is the same reference that appeared on the invoice.payment_received webhook and on your settlement, so it is the key to match on.
  • payments[].payment_id identifies one instalment for good, and GET /v1/checkout/invoice/{public_id}/payments/{payment_id} verifies a single one.

Treat the fetch as authoritative and your webhook handler as an optimisation. A daily reconciliation pass over open invoices costs one request each and closes the gap without anyone noticing there was one. The same pattern applies to every payment type we offer: see Recovering from a missed webhook for the equivalent endpoint on sessions, static accounts, virtual accounts and PayIDs, plus a reconciliation pass you can copy.

Match on the transaction reference, not the amount

On a flexible instalment invoice a customer may pay the same amount twice. Amounts are not unique; reference and payment_id are.

Authentication ​

Requires a secret key (sk_*).

Path parameters ​

ParameterTypeDescription
publicIdstringThe invoice's public_id

Example request ​

bash
curl https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78 \
  -H "Authorization: Bearer sk_test_your_secret_key"
javascript
const response = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78",
  {
    headers: { "Authorization": "Bearer sk_test_your_secret_key" },
  }
);
const data = await response.json();
python
import requests

response = requests.get(
    "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78",
    headers={"Authorization": "Bearer sk_test_your_secret_key"},
)
data = response.json()

Response ​

json
{
  "success": true,
  "data": {
    "public_id": "abc123def456ghi78",
    "invoice_number": "INV-202603-001",
    "status": "partial",
    "customer_name": "Chidi Okonkwo",
    "customer_email": "chidi@example.com",
    "customer_address": "15 Admiralty Way, Lekki Phase 1",
    "customer_city": "Lagos",
    "customer_state": "Lagos",
    "customer_country": "Nigeria",
    "subtotal": 41000000,
    "tax_rate": 7.5,
    "tax_amount": 3075000,
    "total": 44075000,
    "amount_paid": 20000000,
    "currency": "NGN",
    "due_date": "2026-03-31T00:00:00.000Z",
    "issued_at": "2026-03-08T10:05:00.000Z",
    "paid_at": null,
    "cancelled_at": null,
    "merchant_reference": "PROJECT-2026-001",
    "metadata": { "project_id": "PRJ-123" },
    "note": "Thank you for your business!",
    "payment_url": "https://invoice.zevpaycheckout.com/pay/abc123def456ghi78?token=...",
    "created_at": "2026-03-08T10:00:00.000Z",
    "line_items": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440001",
        "description": "Website Development",
        "quantity": 1,
        "unit_price": 35000000
      },
      {
        "id": "550e8400-e29b-41d4-a716-446655440002",
        "description": "Monthly Hosting (12 months)",
        "quantity": 12,
        "unit_price": 500000
      }
    ],
    "payments": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440003",
        "amount": 20000000,
        "paid_at": "2026-03-10T14:30:00.000Z",
        "payer_name": "CHIDI OKONKWO",
        "payment_channel": "bank_transfer"
      }
    ]
  }
}

Line item fields ​

FieldTypeDescription
idstringLine item UUID
descriptionstringItem description
quantitynumberQuantity
unit_priceintegerUnit price in kobo

Payment fields ​

FieldTypeDescription
idstringPayment record UUID
amountintegerPayment amount in kobo
paid_atstringPayment timestamp (ISO 8601)
payer_namestring | nullName of the person who made the payment
payment_channelstring | nullPayment channel: "bank_transfer" or "payid"

List Invoice Payments ​

GET /v1/checkout/invoice/:publicId/payments

Every payment made against an invoice, newest first, each joined to the transaction it settled as. Requires a secret key.

GET /v1/checkout/invoice/:publicId already embeds this. Use this endpoint when you want the payments without the line items and customer detail, for example in a nightly reconciliation job over many invoices.

json
{
  "invoice": {
    "public_id": "inv_8Kd93mQx2Lp",
    "status": "partial",
    "total": 180000000,
    "amount_paid": 120000000,
    "amount_outstanding": 60000000,
    "collecting": true
  },
  "payments": [
    {
      "payment_id": "ipy_7Bn21kFd5Qa",
      "amount": 60000000,
      "paid_at": "2026-04-02T09:15:44.000Z",
      "payer_name": "ADAEZE NWOSU",
      "payment_method": "bank_transfer",
      "transaction": {
        "reference": "ZVP-CKOTX-3d81be",
        "public_id": "ctx_5Km91xRe7Yb",
        "gross_amount": 60000000,
        "fees": 200000,
        "net_amount": 59800000,
        "settlement_state": "paid_out",
        "settled_at": "2026-04-03T12:00:00.000Z"
      }
    }
  ]
}

Each instalment settles separately, on the same T+1 cycle as any other payment, which is why each carries its own settlement_state rather than the invoice carrying one.


Verify One Payment ​

GET /v1/checkout/invoice/:publicId/payments/:paymentId

The same payment object for a single instalment, plus the invoice's balance. Requires a secret key.

Use it when you have a payment_id from a webhook and want to confirm it against us before fulfilling, which is the safe pattern when a webhook arrives from an unverified source or after a retry you are unsure about.


Invoice Activity Log ​

GET /v1/checkout/invoice/:publicId/events

An append-only record of what has been done to the invoice: created, sent, terms changed with the fields that moved, due date extended, reminders, each payment, paused, resumed, cancelled, overdue. Requires a secret key.

Each entry carries an actor:

actorMeaning
apiYour integration did it
dashboardSomeone did it by hand in the dashboard
systemWe did it, such as a due date passing
customerThe payer did it

That field is the one that settles arguments. When a customer says the terms changed after they agreed them, this is where you look.


Send Invoice ​

Transitions an invoice from draft to sent. Sets the issued_at timestamp and makes the payment_url active.

POST /v1/checkout/invoice/:publicId/send

Authentication ​

Requires a secret key (sk_*).

Path parameters ​

ParameterTypeDescription
publicIdstringThe invoice's public_id

Example request ​

bash
curl -X POST https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/send \
  -H "Authorization: Bearer sk_test_your_secret_key"
javascript
const response = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/send",
  {
    method: "POST",
    headers: { "Authorization": "Bearer sk_test_your_secret_key" },
  }
);
const data = await response.json();
python
import requests

response = requests.post(
    "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/send",
    headers={"Authorization": "Bearer sk_test_your_secret_key"},
)
data = response.json()

Response ​

Returns the updated invoice object with status: "sent" and issued_at populated.

Errors ​

StatusMessageCause
404Invoice not foundInvoice does not exist or does not belong to your account
400Only draft invoices can be sentInvoice is not in draft status

Send Reminder ​

POST /v1/checkout/invoice/:publicId/remind

Re-sends the invoice email to the customer. Requires a secret key.

On a part-paid invoice the reminder chases the outstanding balance rather than the original total, so it never reads as a demand for money already paid.

Rate limited to one reminder an hour per invoice. This endpoint sends mail to your customer, so a loop in your automation is a loop in their inbox.

json
{
  "public_id": "inv_8Kd93mQx2Lp",
  "invoice_number": "INV-2026-0041",
  "reminder_count": 3,
  "last_reminder_sent_at": "2026-05-02T09:00:00.000Z"
}
StatusMessageCause
400A reminder was sent recently. Try again in N minute(s)Rate limit
400Cannot send a reminder:The invoice is paid, cancelled, draft or paused

Pause and Resume Collection ​

POST /v1/checkout/invoice/:publicId/pause
POST /v1/checkout/invoice/:publicId/resume

Stops and restarts payment on an invoice without closing it. Requires a secret key.

Pausing is not cancelling. The invoice keeps the status it earned, keeps its link, and the customer can still open it and see everything they have already paid. Only new payment stops. Use it for a dispute, a delivery problem, or a customer who asked you to hold.

bash
curl -X POST https://api.zevpaycheckout.com/v1/checkout/invoice/{public_id}/pause \
  -H "x-api-key: sk_live_your_secret_key" \
  -d '{ "reason": "On hold while we agree the revised spec" }'

reason is optional and is shown to the customer on the invoice page. Both calls are idempotent.

json
{
  "public_id": "inv_8Kd93mQx2Lp",
  "status": "partial",
  "collecting": false,
  "paused_at": "2026-05-04T10:12:00.000Z",
  "paused_reason": "On hold while we agree the revised spec"
}

Fires invoice.paused and invoice.resumed.


Cancel Invoice ​

Cancels an invoice. Only invoices with status draft, sent, partial, or overdue can be cancelled. The payment link stops accepting payment and invoice.cancelled fires.

Balance settled outside ZevPay?

If you are cancelling because part of the invoice was paid where we cannot see it, collect the remainder with a checkout session on the same secret key. See collecting a balance settled outside ZevPay.

POST /v1/checkout/invoice/:publicId/cancel

Authentication ​

Requires a secret key (sk_*).

Path parameters ​

ParameterTypeDescription
publicIdstringThe invoice's public_id

Example request ​

bash
curl -X POST https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/cancel \
  -H "Authorization: Bearer sk_test_your_secret_key"
javascript
const response = await fetch(
  "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/cancel",
  {
    method: "POST",
    headers: { "Authorization": "Bearer sk_test_your_secret_key" },
  }
);
const data = await response.json();
python
import requests

response = requests.post(
    "https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/cancel",
    headers={"Authorization": "Bearer sk_test_your_secret_key"},
)
data = response.json()

Response ​

Returns the updated invoice object with status: "cancelled" and cancelled_at populated.

Errors ​

StatusMessageCause
404Invoice not foundInvoice does not exist or does not belong to your account
400Cannot cancel an invoice with status "paid"Invoice is already fully paid

Invoice Status Lifecycle ​

draft → sent → partial → paid
  ↓       ↓       ↓
  └───────┴───────┴──→ cancelled

sent/partial → overdue (automatic, when past due date)
overdue → paid (if payment received after due date)
overdue → cancelled
StatusDescription
draftInvoice created but not yet sent to customer. Payment URL is not usable
sentInvoice sent, awaiting payment. Payment URL is active
partialCustomer has made a partial payment
paidInvoice fully paid
overduePast due date, not fully paid (set automatically by hourly check)
cancelledInvoice cancelled by merchant

Webhook Events ​

Subscribe to these events via your webhook configuration:

EventDescription
invoice.createdInvoice created via API
invoice.sentInvoice status changed to sent
invoice.payment_receivedPayment received (partial or full)
invoice.paidInvoice fully paid
invoice.overdueInvoice past due date, not fully paid
invoice.pausedCollection stopped without closing the invoice
invoice.resumedCollection restarted
invoice.reminder_sentA reminder email went out
invoice.cancelledInvoice cancelled

On an instalment invoice, invoice.payment_received fires every time

It is not a signal that the invoice is settled. Fulfil on is_fully_paid, or on invoice.paid.

See the full Webhook Events Reference for complete payload documentation.

Example webhook payload ​

json
{
  "event": "invoice.payment_received",
  "data": {
    "public_id": "abc123def456ghi78",
    "invoice_number": "INV-202603-001",
    "status": "partial",
    "customer_name": "Chidi Okonkwo",
    "customer_email": "chidi@example.com",
    "subtotal": 41000000,
    "tax_rate": 7.5,
    "tax_amount": 3075000,
    "total": 44075000,
    "amount_paid": 20000000,
    "currency": "NGN",
    "due_date": "2026-03-31T00:00:00.000Z",
    "issued_at": "2026-03-08T10:05:00.000Z",
    "paid_at": null,
    "merchant_reference": "PROJECT-2026-001",
    "metadata": { "project_id": "PRJ-123" },
    "payment_url": "https://invoice.zevpaycheckout.com/pay/abc123def456ghi78?token=...",
    "payment_amount": 20000000,
    "amount_remaining": 24075000,
    "payer_name": "CHIDI OKONKWO",
    "payment_channel": "bank_transfer"
  }
}

Error Responses ​

All error responses follow this format:

json
{
  "success": false,
  "error": {
    "code": "HTTP_ERROR",
    "message": "An invoice that has been sent cannot change lineItems."
  },
  "correlationId": "b7f3c2a1-..."
}

The readable message is at error.message. Show that to whoever is going to act on it, and quote the correlationId if you contact support.

Common errors ​

StatusMessageEndpoint
401API key is requiredAll: missing or invalid API key
403This endpoint requires a secret keyAll: using a public key instead of secret key
403Invoices are only available for business accountsCreate: personal account trying to create invoices
400At least one line item is requiredCreate: empty line_items array
400Invoice total must be greater than zeroCreate/Update: calculated total is zero or negative
400Invalid due date formatCreate/Update: invalid ISO date string
400An invoice that has been sent cannot changeUpdate: financial fields edited after sending. Terms such as due_date remain editable
400Only draft invoices can be sentSend: invoice is not in draft status
404Invoice not foundGet/Update/Send/Cancel: invalid public_id or not your invoice

Try it: Create Invoice ​

API Playground
POSThttps://api.zevpaycheckout.com/v1/checkout/invoice

Try it: List Invoices ​

API Playground
GEThttps://api.zevpaycheckout.com/v1/checkout/invoice

ZevPay Checkout Developer Documentation