Invoices
Invoices are the core checkout object. Each invoice represents a payment request with a USD amount, one or more payment options, and a payment timing model.
The invoice model uses paymentOptions[] as the source of truth. The hosted page and merchant views render one active rail at a time. Invoice creation includes the payment options that are available at build time. If one configured payment method is temporarily unavailable, the invoice is still created with the remaining successful payment options. If no payment options can be built, creation fails.
Invoice creation is also subject to billing enforcement. FinCobra may return 403 if the account is blocked because an internal billing invoice remained unpaid past the grace window.
Create an invoice
POST /api/checkout/invoicesRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
amountUsd | number | Yes | Amount in USD (minimum 0.01) |
paymentTiming | object | No | Override the config default timing for this invoice. Omit it to use defaultPaymentTiming from checkout config. |
productName | string | No | Product or service description (max 200 chars) |
issuedBy | string | No | Organization issuing the invoice. Defaults to config invoiceIssuerName when omitted. Max 120 chars. |
billTo | string | No | Customer or company name shown on the hosted invoice. Max 120 chars. |
merchantReference | string | No | Merchant order or internal reference (max 200 chars) |
customerId | string | No | Your internal customer identifier |
customerEmail | string | No | Customer email for reconciliation and export |
redirectUrl | string | No | Override the config-level redirect URL for this invoice |
metadata | object | No | Arbitrary key-value data attached to the invoice. If you use metadata.notes for the internal merchant note, it must be a string up to 140 characters. |
Example:
curl -X POST \
-H "X-Api-Key: fc_live_..." \
-H "Content-Type: application/json" \
-d '{
"amountUsd": 49.99,
"paymentTiming": {
"mode": "immediate",
"expiresAfterMinutes": 20
},
"productName": "Pro Plan - Monthly",
"issuedBy": "Acme, Inc.",
"billTo": "Example Name",
"merchantReference": "order_123",
"customerEmail": "buyer@example.com",
"metadata": {
"orderId": "order_123",
"notes": "VIP renewal"
}
}' \
https://fincobra.com/api/checkout/invoicesResponse (HTTP 201):
{
"id": "a1b2c3d4-...",
"amountUsd": 49.99,
"status": "pending",
"confirmations": 0,
"productName": "Pro Plan - Monthly",
"issuedBy": "Acme, Inc.",
"billTo": "Example Name",
"merchantReference": "order_123",
"customerId": null,
"customerEmail": "buyer@example.com",
"redirectUrl": "https://yoursite.com/thank-you",
"metadata": {
"orderId": "order_123",
"notes": "VIP renewal"
},
"paymentSummary": {
"paymentState": "unpaid",
"receivedAmount": 0,
"remainingAmount": 49.99,
"overpaidAmount": 0
},
"selectedPaymentOptionId": "payment-option-1",
"paymentOptions": [
{
"id": "payment-option-1",
"railType": "wallet_contract",
"assetCode": "USDT",
"network": "ethereum",
"quotedAmount": 49.99,
"quoteRate": 1,
"destinationAddress": null,
"paymentUri": null,
"qrCode": null,
"isDefault": true,
"optionPayload": {
"railType": "wallet_contract",
"chainId": 1,
"contractAddress": "0x...",
"tokenContract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"recipient": "0x1111111111111111111111111111111111111111",
"decimals": 6,
"requiredConfirmations": 14,
"merchantIdHash": "0x...",
"invoiceIdHash": "0x...",
"paymentIdHash": "0x...",
"paymentAmountAtomic": "49990000",
"paymentCall": {
"merchantId": "0x...",
"invoiceId": "0x...",
"paymentId": "0x...",
"token": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"recipient": "0x1111111111111111111111111111111111111111",
"amount": "49990000"
}
}
}
],
"paymentTiming": {
"mode": "immediate",
"expiresAfterMinutes": 20,
"payableUntilAt": "2025-01-15T10:50:00.000Z"
},
"paidAt": null,
"confirmedAt": null,
"createdAt": "2025-01-15T10:30:00.000Z",
"lastPaymentObservedAt": null
}Address-transfer rails include paymentUri and qrCode. Wallet-contract rails instead include their executable payload in optionPayload.
Payment timing shapes
Immediate checkout invoice:
{
"paymentTiming": {
"mode": "immediate",
"expiresAfterMinutes": 20
}
}Due-date invoice:
{
"paymentTiming": {
"mode": "due_date",
"dueAfterDays": 7,
"gracePeriodDays": 14
}
}If you use due-date timing, the invoice remains payable until the final payable deadline (dueAt + gracePeriodDays). Checkout keeps the invoice open until that deadline and then transitions it to expired.
List invoices
GET /api/checkout/invoicesQuery parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status: pending, paid, confirmed, expired, cancelled, underpaid |
search | string | — | Free-text search across invoice ID, address, tx hash, product name, and reference fields |
merchantReference | string | — | Exact match on merchant reference |
customerId | string | — | Exact match on customer ID |
customerEmail | string | — | Exact match on customer email |
exceptionStatus | string | — | Filter open vs resolved exceptions: open, resolved |
exceptionType | string | — | Filter exception type: late_payment, underpaid, overpaid, duplicate_payment |
transactionHash | string | — | Exact match on any observed transaction hash |
invoiceId | uuid | — | Exact match on invoice ID |
limit | integer | 50 | Max results (1–200) |
offset | integer | 0 | Pagination offset |
Export invoices as CSV
GET /api/checkout/invoices/exportUses the same filters as the invoice list endpoint and returns a CSV download.
Get invoice detail
GET /api/checkout/invoices/:idReturns full invoice data including QR code and the resolved payment timing.
The response includes:
paymentSummarypaymentTimingselectedPaymentOptionIdpaymentOptions[]payments- merchant and customer reference fields
Get invoice payment events
GET /api/checkout/invoices/:id/paymentsReturns the normalized payment-event rows observed for this invoice.
Get invoice status
GET /api/checkout/invoices/:id/statusLightweight endpoint for polling. Returns only status-related fields:
{
"status": "paid",
"confirmations": 0,
"paymentSummary": {
"paymentState": "exact",
"receivedAmount": 49.99,
"remainingAmount": 0,
"overpaidAmount": 0
},
"exceptionType": null,
"exceptionStatus": null,
"resolutionAction": null,
"resolutionNote": null,
"resolvedAt": null,
"resolvedBy": null,
"paymentTiming": {
"mode": "immediate",
"expiresAfterMinutes": 20,
"payableUntilAt": "2025-01-15T10:50:00.000Z"
},
"lastPaymentObservedAt": "2025-01-15T10:36:00.000Z",
"payments": [
{
"id": "payment-1",
"invoiceId": "a1b2c3d4-...",
"assetCode": "USDT",
"network": "ethereum",
"destinationAddress": "0x...",
"transactionHash": "abc123...",
"amountReceived": 49.99,
"observedTotalAmount": 49.99,
"confirmations": 1,
"observedAt": "2025-01-15T10:35:00.000Z",
"confirmedAt": "2025-01-15T10:42:00.000Z"
}
]
}Get invoice stats
GET /api/checkout/invoices/statsReturns aggregate counts across all your invoices.
Resolve an invoice exception
POST /api/checkout/invoices/:id/exception/resolveUse this after reviewing an open exception.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
resolutionAction | string | Yes | One of accept_late_payment, accept_overpayment, write_off_underpayment, mark_duplicate_payment_reviewed, other |
resolutionNote | string | No | Optional operator note |
Response:
{
"ok": true,
"invoiceId": "a1b2c3d4-..."
}Invoice status lifecycle
pending ──→ paid ──→ confirmed
│
├──→ expired (no payment received before the final payable deadline)
│
└──→ underpaid (partial payment received, then the final payable deadline elapsed)