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
- Create an invoice via API with customer details, line items, and due date
- Send the invoice (transitions from draft to sent)
- Share the
payment_urlwith your customer - Customer opens the payment URL and pays via bank transfer or PayID
- Invoice status updates automatically:
partial→paid - You receive webhook events for each status change
Create Invoice
POST /v1/checkout/invoiceCreates a new invoice in draft status. You must send the invoice before customers can pay.
Authentication
Requires a secret key (sk_*).
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_name | string | Yes | Customer's full name (max 255 characters) |
customer_email | string | Yes | Customer's email address |
customer_address | string | No | Customer's street address |
customer_city | string | No | Customer's city (max 100) |
customer_state | string | No | Customer's state/region (max 100) |
customer_country | string | No | Customer's country (max 100) |
line_items | array | Yes | Array of line item objects (at least one) |
line_items[].description | string | Yes | Description of the item (max 500) |
line_items[].quantity | number | Yes | Quantity (supports decimals, min 0.01) |
line_items[].unit_price | integer | Yes | Unit price in kobo |
tax_rate | number | No | Tax rate as percentage (0–100, e.g., 7.5 for 7.5%). Default: 0. Mutually exclusive with tax_amount |
tax_amount | integer | No | Flat tax amount in kobo, taken verbatim: total = subtotal + tax_amount, exactly. Use when your system computes tax itself. Mutually exclusive with tax_rate |
due_date | string | Yes | Due date in ISO 8601 format |
invoice_number | string | No | Custom invoice number (max 64). Auto-generated if omitted (e.g., INV-202603-001) |
reference | string | No | Your unique reference for reconciliation (max 255) |
note | string | No | Note to display on the invoice payment page (max 2000) |
payment_methods | string[] | No | Methods to offer on this invoice's checkout: bank_transfer, payid, card, crypto. Omit for your key's configuration. See Choosing payment methods |
crypto_assets | string[] | No | Narrow the crypto assets offered, e.g. ["USDT:TRON"] |
metadata | object | No | Custom key-value data stored with the invoice |
Example request
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"
}
}'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();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
{
"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
| Field | Type | Description |
|---|---|---|
public_id | string | Unique public identifier for the invoice |
invoice_number | string | Human-readable invoice number (auto-generated or custom) |
status | string | Current status: draft, sent, partial, paid, overdue, cancelled |
customer_name | string | Customer's full name |
customer_email | string | Customer's email address |
customer_address | string | null | Customer's street address |
customer_city | string | null | Customer's city |
customer_state | string | null | Customer's state/region |
customer_country | string | null | Customer's country |
subtotal | integer | Subtotal in kobo (sum of line items, before tax) |
tax_rate | number | Tax rate as a percentage (e.g., 7.5). 0 when the invoice uses a flat tax_amount |
tax_amount | integer | Tax amount in kobo (derived from tax_rate, or the flat amount you supplied) |
total | integer | Total amount in kobo (subtotal + tax) |
amount_paid | integer | Cumulative amount paid so far, in kobo |
currency | string | Currency code. Always "NGN" |
due_date | string | Due date in ISO 8601 format |
issued_at | string | null | Timestamp when invoice was sent (ISO 8601). null for drafts |
paid_at | string | null | Timestamp when invoice was fully paid. null until paid |
cancelled_at | string | null | Timestamp when invoice was cancelled. null unless cancelled |
merchant_reference | string | null | Your custom reference, if provided |
metadata | object | null | Your custom key-value data, if provided |
note | string | null | Invoice note, if provided |
payment_url | string | URL for the customer to view and pay the invoice |
created_at | string | Invoice 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/:publicIdUpdates a draft invoice. Only invoices with status: "draft" can be updated. Once sent, an invoice cannot be modified.
Authentication
Requires a secret key (sk_*).
Path parameters
| Parameter | Type | Description |
|---|---|---|
publicId | string | The invoice's public_id |
Request body
All fields are optional. Only include the fields you want to change.
| Parameter | Type | Description |
|---|---|---|
customer_name | string | Customer's full name |
customer_email | string | Customer's email address |
customer_address | string | Customer's street address |
customer_city | string | Customer's city |
customer_state | string | Customer's state/region |
customer_country | string | Customer's country |
line_items | array | Replacement line items (replaces all existing items) |
line_items[].description | string | Item description |
line_items[].quantity | number | Quantity |
line_items[].unit_price | integer | Unit price in kobo |
tax_rate | number | Tax rate as percentage (0–100). Mutually exclusive with tax_amount |
tax_amount | integer | Flat tax amount in kobo (total = subtotal + tax_amount, exactly). Mutually exclusive with tax_rate |
due_date | string | Due date in ISO 8601 format |
invoice_number | string | Custom invoice number |
note | string | Invoice note |
return_url | string | URL the customer lands on after paying |
allow_partial_payments | boolean | Whether the customer may pay less than the remaining balance |
payment_methods | string[] | Methods to offer on this invoice's checkout. Send [] to fall back to your key's configuration |
crypto_assets | string[] | Narrow the crypto assets offered. Send [] to offer every eligible asset |
metadata | object | Custom 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
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"
}'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();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
| Status | Message | Cause |
|---|---|---|
| 404 | Invoice not found | Invoice does not exist or does not belong to your account |
| 400 | Only draft invoices can be edited | Invoice has already been sent, paid, or cancelled |
| 400 | Invoice total must be greater than zero | Updated line items result in zero or negative total |
List Invoices
GET /v1/checkout/invoiceReturns a paginated list of your invoices, ordered by creation date (newest first).
Authentication
Requires a secret key (sk_*).
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (min 1). Default: 1 |
page_size | integer | No | Items per page (1–100). Default: 20 |
status | string | No | Filter by status: draft, sent, partial, paid, overdue, cancelled |
customer_email | string | No | Filter by exact customer email address |
search | string | No | Search 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
# 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"// 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" },
}
);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
{
"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
| Field | Type | Description |
|---|---|---|
items | array | Array of invoice objects (same fields as Create response) |
total | integer | Total number of invoices matching the filters |
page | integer | Current page number |
page_size | integer | Number of items per page |
total_pages | integer | Total number of pages |
Get Invoice
GET /v1/checkout/invoice/:publicIdReturns a single invoice with its line items and payment history.
Authentication
Requires a secret key (sk_*).
Path parameters
| Parameter | Type | Description |
|---|---|---|
publicId | string | The invoice's public_id |
Example request
curl https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78 \
-H "Authorization: Bearer sk_test_your_secret_key"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();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
{
"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
| Field | Type | Description |
|---|---|---|
id | string | Line item UUID |
description | string | Item description |
quantity | number | Quantity |
unit_price | integer | Unit price in kobo |
Payment fields
| Field | Type | Description |
|---|---|---|
id | string | Payment record UUID |
amount | integer | Payment amount in kobo |
paid_at | string | Payment timestamp (ISO 8601) |
payer_name | string | null | Name of the person who made the payment |
payment_channel | string | null | Payment channel: "bank_transfer" or "payid" |
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/sendAuthentication
Requires a secret key (sk_*).
Path parameters
| Parameter | Type | Description |
|---|---|---|
publicId | string | The invoice's public_id |
Example request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/send \
-H "Authorization: Bearer sk_test_your_secret_key"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();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
| Status | Message | Cause |
|---|---|---|
| 404 | Invoice not found | Invoice does not exist or does not belong to your account |
| 400 | Only draft invoices can be sent | Invoice is not in draft status |
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/cancelAuthentication
Requires a secret key (sk_*).
Path parameters
| Parameter | Type | Description |
|---|---|---|
publicId | string | The invoice's public_id |
Example request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/invoice/abc123def456ghi78/cancel \
-H "Authorization: Bearer sk_test_your_secret_key"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();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
| Status | Message | Cause |
|---|---|---|
| 404 | Invoice not found | Invoice does not exist or does not belong to your account |
| 400 | Cannot 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| Status | Description |
|---|---|
draft | Invoice created but not yet sent to customer. Payment URL is not usable |
sent | Invoice sent, awaiting payment. Payment URL is active |
partial | Customer has made a partial payment |
paid | Invoice fully paid |
overdue | Past due date, not fully paid (set automatically by hourly check) |
cancelled | Invoice cancelled by merchant |
Webhook Events
Subscribe to these events via your webhook configuration:
| Event | Description |
|---|---|
invoice.created | Invoice created via API |
invoice.sent | Invoice status changed to sent |
invoice.payment_received | Payment received (partial or full) |
invoice.paid | Invoice fully paid |
invoice.overdue | Invoice past due date, not fully paid |
invoice.cancelled | Invoice cancelled |
See the full Webhook Events Reference for complete payload documentation.
Example webhook payload
{
"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:
{
"statusCode": 400,
"message": "Only draft invoices can be edited.",
"error": "Bad Request"
}Common errors
| Status | Message | Endpoint |
|---|---|---|
| 401 | API key is required | All: missing or invalid API key |
| 403 | This endpoint requires a secret key | All: using a public key instead of secret key |
| 403 | Invoices are only available for business accounts | Create: personal account trying to create invoices |
| 400 | At least one line item is required | Create: empty line_items array |
| 400 | Invoice total must be greater than zero | Create/Update: calculated total is zero or negative |
| 400 | Invalid due date format | Create/Update: invalid ISO date string |
| 400 | Only draft invoices can be edited | Update: invoice is not in draft status |
| 400 | Only draft invoices can be sent | Send: invoice is not in draft status |
| 404 | Invoice not found | Get/Update/Send/Cancel: invalid public_id or not your invoice |