Accept payment with hosted invoice
Create an order and redirect your customer to the invoice URL. Commerce handles the payment flow, OTP verification, and receipt generation.
Recommended approach. See Hosted Invoices for why this works better than custom checkout.
Step 1: Create an order
Call /orders/new with customer details and line items. Commerce generates invoice URLs automatically—one for web payment, one for PDF download. Store the order ID for status tracking.
Create order
curl https://api.zebo.dev/orders/new \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "order_inv_001",
"customer_data": {
"name": "Gloria Kesewaa",
"email_address": "gloria@example.com",
"phone_number": "+233544998605"
},
"line_items": [
{
"type": "product",
"product": {
"type": "physical",
"name": "Utility Sneakers",
"quantity": 1,
"price": { "currency": "ghs", "value": 20000 }
}
}
]
}'
Response
Partial response. See complete Order object.
{
"order": {
"id": "ord_XyZ9kL2mQrTfVqYz1wNb",
"number": "INV-2025-001",
"status": "requires_payment",
"invoice": {
"id": "inv_AbC123XyZ",
"format": {
"web": {
"url": "https://pages.zebo.dev/invoices/ord_XyZ9kL2mQrTfVqYz1wNb"
},
"pdf": {
"url": "https://pages.zebo.dev/invoices/ord_XyZ9kL2mQrTfVqYz1wNb/pdf"
}
}
}
}
}
Step 2: Direct customer to payment
Choose how to get the customer to the payment page:
Extract invoice.format.web.url from the response and return it to your client. The client redirects immediately—customer lands on the payment page while they're still engaged. This works for in-app checkouts where the customer initiated the purchase.
// Server: Return URL to client
return {
invoiceURL: order.invoice.format.web.url,
}
// Client: Redirect to invoice
const response = await fetch('/api/create-order', { ... })
const { invoiceURL } = await response.json()
window.location.href = invoiceURL
Step 3: Track payment status
Use /orders/lookup to check payment status. Poll periodically or check when the customer returns to your app. When status becomes "paid", funds are in your Commerce balance.
Check status
curl https://api.zebo.dev/orders/lookup \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"order_id": "ord_XyZ9kL2mQrTfVqYz1wNb"}'
Customization
Set due date
{
"due_date": "2025-05-15T23:59:59Z"
}
Shows prominently on invoice. Order becomes "overdue" after this date but payment still works.
Add line item details
{
"product": {
"name": "Utility Sneakers",
"about": "Size: 42 | Color: Black"
}
}
Appears below product name on invoice.
Update branding
Change logo, business name, and colors in the Commerce dashboard under Settings → Branding. Applies to all new invoices instantly.
See Invoice customization for more options.
Common patterns
Subscriptions: Create new order each cycle, send new invoice URL.
Deposits: Create separate orders for deposit and balance. Link via reference field.
Multi-currency: Create one order per currency.
See Common patterns for implementation details.
Next steps
- Hosted Invoices — Why hosted invoices work better than custom checkout
- Orders — Complete order API reference
- Accept a payment — Build custom checkout (advanced)