API documentation & developer guide

v1 · live

Quickstart

Accepting crypto on your site takes three steps: create an API key, register your settlement wallet addresses, and open your first invoice. Because nothing is custodial, settlements move straight to a wallet you control.

  • POST /v1/merchants/{merchant_id}/api-keys — issues the API key pair you sign requests with.
  • POST /v1/addresses — registers the static wallet address for token settlements such as USDT (one per network and asset).
  • POST /v1/sweep-addresses — registers the main wallet that native coin settlements (TRX, BNB, GRAM) are swept into.
  • POST /v1/webhooks — registers the URL we notify the moment a payment completes.
Your API secret is encrypted at rest and, for security, shown exactly once. If you lose it, issue a new key from the dashboard and revoke the old one in a single click.

Request signing (HMAC)

Every HTTP request is signed with HMAC-SHA256. The signature is computed over the raw request body, so it stays valid even if the JSON key order changes.

Canonical signing template
TEMPLATE
METHOD \n PATH \n TIMESTAMP \n NONCE \n SHA256(raw_body)
X-Api-Key
API key id (pk_…)
X-Timestamp
Unix timestamp in seconds (server clock tolerant)
X-Nonce
Unique random string (max 128 characters, single use)
X-Signature
hex(hmac_sha256(secret, canonical))

Two layers protect the request: the timestamp must fall inside the tolerance window, and each nonce may be used only once. Replaying the same request is rejected automatically (HTTP 401).

Example signed request client in Python
PYTHON
import hashlib, hmac, os, time, json, urllib.request
KEY_ID = os.environ["COINZEN_KEY_ID"]
SECRET = os.environ["COINZEN_SECRET"]
BASE = "https://coinzen.cerceyn.com"
def request(method: str, path: str, payload: dict | None = None) -> bytes:
body = json.dumps(payload).encode() if payload is not None else b""
ts = str(int(time.time()))
nonce = os.urandom(16).hex()
canonical = "\n".join(
[method.upper(), path, ts, nonce, hashlib.sha256(body).hexdigest()]
)
signature = hmac.new(SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request(
BASE + path,
data=body or None,
method=method.upper(),
headers={
"X-Api-Key": KEY_ID,
"X-Timestamp": ts,
"X-Nonce": nonce,
"X-Signature": signature,
"Content-Type": "application/json",
},
)
with urllib.request.urlopen(req) as resp:
return resp.read()

Invoice lifecycle

Creating an invoice starts a state machine. If no network and asset were given, the invoice opens as selecting. When the customer picks a network and coin on the payment page it moves to pending and a payment address is allocated.

  • selecting pending, expired, cancelled
  • pending confirming, partially_paid, expired, cancelled
  • confirming paid, overpaid, failed
  • paidoverpaid — if the customer sends another transfer against a paid invoice, the amount updates automatically.

paid, overpaid, expired, cancelled and failed are terminal. On a chain reorg or a late payment the system updates the state itself and tells your system over a webhook.

When amount_exact is true, the customer has to send exactly the stated amount so we can match the transfer instantly and without ambiguity.

Webhook notifications

Every payment status change is pushed to your registered webhook endpoint. Each payload is signed with HMAC-SHA256, so you can verify that the data is intact and really came from Coinzen.

X-Coinzen-Signature
t=<unix>,v1=<hex hmac_sha256(secret, "<t>.<raw_body>")>
X-Coinzen-Delivery
Unique delivery id (UUID)
X-Coinzen-Event
Event name that fired (e.g. invoice.paid)
Webhook verification example in Python
PYTHON
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
"""X-Coinzen-Signature: t=<unix>,v1=<hex>"""
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = parts["t"], parts["v1"]
if abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(
secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, v1)

Supported webhook events

  • invoice.pending
  • invoice.confirming
  • invoice.partially_paid
  • invoice.paid
  • invoice.overpaid
  • invoice.expired
  • invoice.cancelled
  • payment.reverted
  • deposit.unmatched
  • credit.low
  • credit.exhausted

Retry schedule

The first attempt fires immediately after the payment. If your server doesn't answer 200 OK, we retry across seven steps at 30 · 120 · 600 · 3600 · 21600 · 86400 second intervals. If every attempt fails the record moves to dead_letter and can be re-queued from the dashboard in one click.

Fees and credit

Coinzen never takes its fee out of the customer's payment. USDT and every other crypto payment goes straight to your address, and only you hold that wallet's keys. The platform fee comes out of credit you topped up beforehand, so what you collect stays whole.

  • The standard rate is 1%. New accounts get 0.1% for their first 45 days. Your current rate is in the fee_bps field of the GET /v1/credit response (basis points: 10 = 0.1%, 100 = 1%).
  • The fee is charged only on invoices that complete successfully (paid and overpaid).
  • Cancelled, expired and underpaid invoices are never charged.
  • When your credit runs out, open invoices keep settling normally; only new invoice requests get a balance warning (HTTP 402).

Two-step charging

When an invoice is paid, the fee entry opens immediately as pending so chain tracking isn't slowed down, and it settles without waiting on any external service. The rate conversion happens in the background and the net amount comes off your balance.

If a chain reorg pulls the payment back, the fee is voided at once; if it had already been deducted, a reversal entry refunds it.

Topping up credit

POST /v1/credit/deposit-addresses returns a permanent USDT top-up address of your own. Anything you send there is added to your credit the moment it confirms on chain — no manual approval, no waiting.

The top-up address is your fee funding address; do not hand it to customers as a settlement address.

Automatic top-up

With this on, your sales keep running even if your credit hits zero. The proceeds of your next small invoice are routed into your credit balance instead, and service continues uninterrupted.

  • No separate fee is charged on an invoice routed into credit.
  • A cap applies for your protection (25 USD by default). Invoices above the cap are never routed, so large orders aren't put at risk.
  • Toggle it and set your own cap at any time through PUT /v1/credit/auto or the dashboard.

Balance alerts

When your credit drops below the threshold a credit.low event fires, and credit.exhausted when it runs out — so you hear about it before it bites.

Supported networks and assets

The table below summarises the live mainnet registry. For the runtime list, call GET /v1/networks.

NetworkAssetDecimalsCollection modelContract address
TRONTRX6Per-invoice dynamic addressNative coin
TRONUSDT6Static address + unique amountTR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
BSCBNB18Per-invoice dynamic addressNative coin
BSCUSDT18Static address + unique amount0x55d398326f99059fF775485246999027B3197955
TONGRAM9Per-invoice dynamic addressNative coin
TONUSDT6Static address + unique amountEQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs

API endpoints

MethodPathWhat it doesScope
GET/healthzService health checkpublic
GET/readyzDependency and readiness statuspublic
GET/v1/networksSupported blockchain networks and assetspublic
POST/v1/invoicesCreate a payment invoiceinvoices:write
GET/v1/invoicesList invoices (paginated)invoices:read
GET/v1/invoices/{invoice_id}Fetch invoice detailsinvoices:read
POST/v1/invoices/{invoice_id}/cancelCancel an invoiceinvoices:write
GET/v1/ratesGet a lockable rate quoteinvoices:read
GET/v1/balancesWallet balances by network and assetinvoices:read
GET/v1/deposits/unmatchedList unmatched incoming transfersinvoices:read
POST/v1/deposits/{deposit_id}/attachAttach an incoming transfer to an invoice manuallyinvoices:write
POST/v1/addressesRegister a static settlement walletinvoices:write
GET/v1/addressesList registered static addressesinvoices:read
POST/v1/sweep-addressesRegister a native sweep walletinvoices:write
GET/v1/creditCurrent fee credit balanceinvoices:read
GET/v1/credit/entriesCredit ledger entries and historyinvoices:read
POST/v1/credit/deposit-addressesGet a credit top-up wallet addressinvoices:write
PUT/v1/credit/autoUpdate the automatic top-up settinginvoices:write
PUT/v1/merchants/{merchant_id}/credit/feeUpdate a merchant's fee rateadmin
POST/v1/webhooksRegister a webhook endpointwebhooks:write
GET/v1/webhooks/deliveriesWebhook notification and delivery historywebhooks:read
POST/v1/webhooks/deliveries/{delivery_id}/replayReplay a failed notificationwebhooks:write
API documentation & developer guide · Coinzen