API documentation & developer guide
v1 · liveQuickstart
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.
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.
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).
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,cancelledpending→confirming,partially_paid,expired,cancelledconfirming→paid,overpaid,failedpaid⇄overpaid— 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.
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)
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.pendinginvoice.confirminginvoice.partially_paidinvoice.paidinvoice.overpaidinvoice.expiredinvoice.cancelledpayment.reverteddeposit.unmatchedcredit.lowcredit.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_bpsfield of theGET /v1/creditresponse (basis points: 10 = 0.1%, 100 = 1%). - The fee is charged only on invoices that complete successfully (
paidandoverpaid). - 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.
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/autoor 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.
| Network | Asset | Decimals | Collection model | Contract address |
|---|---|---|---|---|
| TRON | TRX | 6 | Per-invoice dynamic address | Native coin |
| TRON | USDT | 6 | Static address + unique amount | TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t |
| BSC | BNB | 18 | Per-invoice dynamic address | Native coin |
| BSC | USDT | 18 | Static address + unique amount | 0x55d398326f99059fF775485246999027B3197955 |
| TON | GRAM | 9 | Per-invoice dynamic address | Native coin |
| TON | USDT | 6 | Static address + unique amount | EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs |
API endpoints
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /healthz | Service health check | public |
| GET | /readyz | Dependency and readiness status | public |
| GET | /v1/networks | Supported blockchain networks and assets | public |
| POST | /v1/invoices | Create a payment invoice | invoices:write |
| GET | /v1/invoices | List invoices (paginated) | invoices:read |
| GET | /v1/invoices/{invoice_id} | Fetch invoice details | invoices:read |
| POST | /v1/invoices/{invoice_id}/cancel | Cancel an invoice | invoices:write |
| GET | /v1/rates | Get a lockable rate quote | invoices:read |
| GET | /v1/balances | Wallet balances by network and asset | invoices:read |
| GET | /v1/deposits/unmatched | List unmatched incoming transfers | invoices:read |
| POST | /v1/deposits/{deposit_id}/attach | Attach an incoming transfer to an invoice manually | invoices:write |
| POST | /v1/addresses | Register a static settlement wallet | invoices:write |
| GET | /v1/addresses | List registered static addresses | invoices:read |
| POST | /v1/sweep-addresses | Register a native sweep wallet | invoices:write |
| GET | /v1/credit | Current fee credit balance | invoices:read |
| GET | /v1/credit/entries | Credit ledger entries and history | invoices:read |
| POST | /v1/credit/deposit-addresses | Get a credit top-up wallet address | invoices:write |
| PUT | /v1/credit/auto | Update the automatic top-up setting | invoices:write |
| PUT | /v1/merchants/{merchant_id}/credit/fee | Update a merchant's fee rate | admin |
| POST | /v1/webhooks | Register a webhook endpoint | webhooks:write |
| GET | /v1/webhooks/deliveries | Webhook notification and delivery history | webhooks:read |
| POST | /v1/webhooks/deliveries/{delivery_id}/replay | Replay a failed notification | webhooks:write |