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 |
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) |
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) |
tax_amount | integer | Tax amount in kobo |
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) |
due_date | string | Due date in ISO 8601 format |
invoice_number | string | Custom invoice number |
note | string | Invoice note |
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.
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 |