Coinflo
Intro to the Coinflo API
The Coinflo Merchant API is the integration surface for creating invoices, reading their status, and receiving signed webhooks. The payload shape is Confirmo-compatible so existing Confirmo clients can be pointed at Coinflo with minimal changes.
HTTPS required in production. Clients must ignore unknown fields — this API can grow without breaking existing integrations.
Base URL: /api/v3
Authentication
API keys are the only auth model. Create them under Settings → API keys. The token is shown once. Send it on every request:
curl https://YOUR_HOST/api/v3/invoices \
-H "Authorization: Bearer ck_live_..." \
-H "Content-Type: application/json"A missing or invalid key returns HTTP 401.
Status codes
| Code | Meaning |
|---|---|
| 200 | Request succeeded |
| 400 | Invalid body or unsupported value |
| 401 | Missing or invalid API key |
| 404 | Invoice not found |
| 500 | Unexpected server error |
Create invoice
POST /api/v3/invoices creates a prepared invoice. The customer picks a coin on the hosted checkout. invoice.currencyFrom must be USD. Settlement is USDC.
POST /api/v3/invoices
{
"invoice": { "amount": "49.00", "currencyFrom": "USD" },
"product": { "name": "Order 1042", "description": "" },
"reference": "{\"orderId\":\"1042\"}",
"returnUrl": "https://shop.example/thanks",
"notifyUrl": "https://shop.example/webhooks/coinflo",
"customerEmail": "buyer@example.com",
"settlement": { "currency": "USDC" }
}Fields:
invoice.amount— USD amount, number or stringinvoice.currencyFrom—USDonlyinvoice.currencyTo— accepted, ignored; customer picks from the coins the merchant enabled in SettingsnotifyUrl— webhook endpoint. Falls back to merchant default.returnUrl— redirect after payment. Falls back to merchant default.reference— opaque string, often JSON. Echoed on webhooks and the ledger.customerEmail,notifyEmail,language
Response is the full invoice object (same shape as GET). Status starts as prepared. Send the customer to url.
Create email invoice
Same machine as a normal invoice — prepared, no deposit address yet. The customer still picks a coin on hosted checkout. Extra wrap is targetEmail. We do not send mail yet; you get paymentLink to deliver yourself. Settlement is USDC only.
POST /api/v3/email-invoices
{
"invoice": {
"invoice": { "amount": 100, "currencyFrom": "USD" },
"product": { "name": "example product" },
"settlement": { "currency": "USDC" },
"returnUrl": "https://shop.example/thanks",
"notifyUrl": "https://shop.example/webhooks/coinflo"
},
"targetEmail": "customer@example.com"
}GET /api/v3/email-invoices/:emi… returns the wrapper. GET /api/v3/invoices/:inv… is still the source of truth after payment.
Get invoice
GET /api/v3/invoices/:id is the source of truth. After a paid webhook, always re-fetch. Do not trust the webhook body alone.
GET /api/v3/invoices/invSZfmkLxTc9xv
Authorization: Bearer ck_live_...List invoices
GET /api/v3/invoices?limit=25 returns newest first. limit max 100.
Invoice lifecycle
prepared— customer has not picked a method. Expires if idle.active— address + quoted crypto amount. Default window 15 minutes (Settings → invoice TTL).confirming— payment seen, waiting for chain confirmations.paid— confirmed and credited to your USDC ledger.expired— no payment or underpay past the window. Merchant can accept an exception from the invoice detail page.error— confirmations did not arrive within 96 hours.blocked— compliance hold.
unhandledExceptions is true on material over/underpay. overUnderPaidAmount is the crypto delta vs the quoted amount.
Webhooks
Every status change POSTs the full invoice JSON to notifyUrl with Content-Type: application/json and bp-signature.
Signature: SHA-256 hex of rawBody + callbackPassword. Callback password lives in Settings. Delivery retries up to 20 times with exponential backoff (1s, 2s, 4s, … cap 1h). Return HTTP 200 to stop retries.
const crypto = require("crypto");
function valid(rawBody, signature, callbackPassword) {
const expected = crypto
.createHash("sha256")
.update(rawBody + callbackPassword, "utf8")
.digest("hex");
return expected.toLowerCase() === String(signature).toLowerCase();
}
// After verifying signature + status === "paid":
// GET /api/v3/invoices/:id and fulfill only if still paid.Payment methods
Hosted checkout currently offers:
- BTC, LTC
- USDC / USDT on Ethereum, Polygon, BNB Smart Chain
- USDT on Tron (TRC-20)
Checkout URL format: /pay/{invoiceId}?m={merchantPublicId}
Errors
Error bodies match Confirmo:
{
"errors": [
{ "message": "Invoices must be in USD", "type": "request" }
]
}Auth failures use type: "auth". Ignore unknown fields on both success and error payloads.
