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

CodeMeaning
200Request succeeded
400Invalid body or unsupported value
401Missing or invalid API key
404Invoice not found
500Unexpected 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 string
  • invoice.currencyFromUSD only
  • invoice.currencyTo — accepted, ignored; customer picks from the coins the merchant enabled in Settings
  • notifyUrl — 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.