API documentation
Integrate USDT (BEP-20) deposit flows on Binance Smart Chain: create invoices, poll status, and verify webhooks.
All API routes are versioned under /api/v1.
Get started
- Create an account and open your BSC wallet in the dashboard.
- Issue an API token and copy your webhook signing secret from the Developer API (available after you sign in) page.
- Call the endpoints below with
Authorization: Bearer <your_token>.
Base URL
https://novapayments.online/api/v1
Use HTTPS in production. Requests must send Accept: application/json where applicable.
Authentication
Personal access tokens (Laravel Sanctum). Example header:
Authorization: Bearer <token_shown_once_when_created>
Endpoints
GET /api/v1/me
Returns the authenticated user id, name, email, and wallet address (if a wallet exists).
POST /api/v1/invoices
Create a deposit invoice. Body (JSON):
{
"amount": "100.50",
"order_id": "order_123",
"network": "mainnet",
"ipn_callback_url": "https://yoursite.com/webhooks/nova"
}
network—mainnetortestnetorder_id— unique per account; pattern[a-zA-Z0-9._-]+ipn_callback_url— optional HTTPS URL for confirmed-payment webhookscallback_url— also supported as backward-compatible alias
Each invoice is issued its own single-use pay_address. Quote that address to your customer — never your own wallet address, and never an address from a previous invoice. Funds paid there are swept to your NovaPayments wallet automatically once the order settles.
Because no two open invoices share an address, a payment is attributed to exactly one order: two customers paying the same amount at the same time can no longer be confused for one another. Payments still accumulate against the invoice, so a partial payment is recorded rather than ignored — see Partial and over payments below.
GET /api/v1/invoices
Paginated list of invoices (data + meta).
GET /api/v1/invoices/{invoice_id}
invoice_id is the public id (e.g. inv_…). Refreshes chain sync for your account when called.
Includes amount_received, amount_remaining, amount_overpaid, and a payments array listing each on-chain transfer applied to the invoice.
pay_address is fixed for the life of the invoice, so it is safe to cache alongside the order or render into a QR code.
Partial and over payments
A customer who sends the wrong amount does not silently fall through the cracks. Every transfer is applied to the invoice and the total so far is tracked on amount_received.
| Status | Meaning |
|---|---|
pending | Nothing received yet. |
underpaid | Some received, still short. More payments can still arrive. |
confirmed | Paid in full, or within the platform tolerance. |
overpaid | Paid in full with a surplus — see amount_overpaid. |
expired | Window closed. May still carry an amount_received. |
- Two partial payments that add up to the total confirm the invoice.
- A small shortfall within the platform tolerance counts as paid in full — this covers wallets that deduct a fee from the transfer.
- An expired invoice that took money still fires
invoice.expiredso you can refund the payer. - Because all invoices share one pay address, a payment that settles an invoice exactly is matched to it; otherwise it goes to your oldest open invoice.
By default a deposit that leaves an invoice underpaid is held in your wallet rather than auto-forwarded, so it can be refunded. It is released automatically once the invoice is topped up or expires.
Withdrawal endpoints
POST /api/v1/withdrawals
Request a USDT payout from your NovaPayments wallet. Body (JSON):
{
"amount": "50.00",
"network": "mainnet",
"to_address": "0x…",
"reference": "payout_456",
"callback_url": "https://yoursite.com/webhooks/nova"
}
amount— gross amount debited from your wallet. The platform fee (2.00%) is taken out of it, so the destination receivesnet_amount=amount−fee_amount(request30→ destination receives29.4).to_address— optional; defaults to the withdrawal wallet address on your accountreference— unique per account; pattern[a-zA-Z0-9._-]+callback_url— optional HTTPS URL for payout webhooks
Returns 201 with the payout in pending. Nothing is broadcast during the request — the scheduler picks it up within a minute. Reposting the same reference returns 200 with the original payout instead of sending twice, so retries are safe.
GET /api/v1/withdrawals
Paginated list of payouts (data + meta). Optional ?status= and ?network= filters.
GET /api/v1/withdrawals/{withdrawal_id}
Accepts the public id (wd_…) or your own reference. Refreshes the on-chain receipt when called.
Status flow: pending → processing → sent → confirmed or failed. A payout still pending can be cancelled.
POST /api/v1/withdrawals/{withdrawal_id}/cancel
Cancels a payout that has not been broadcast yet. Returns 409 once it has reached processing or later.
Auto-withdrawal (auto-forward)
Turn off custody on the wallet page and set a withdrawal wallet address. Every incoming USDT deposit then creates a payout to that address automatically — no API call needed.
- Auto-forwarded payouts appear in
GET /api/v1/withdrawalswith"source": "auto_forward". - Their
referenceisauto_<deposit tx hash>, so each deposit forwards exactly once. - They have no
callback_url; poll the list endpoint to track them. - The platform withdrawal fee of
2.00%applies to every auto-forwarded deposit, exactly as it does to a payout you create yourself. The deposit amount isamount, the fee isfee_amount, andnet_amountis what reaches your withdrawal address.
Webhooks
Invoices fire invoice.confirmed, invoice.underpaid, invoice.overpaid and invoice.expired. Each event is delivered at most once per invoice, so an invoice that is underpaid and later topped up sends two. We POST JSON to callback_url with headers:
X-Nova-Signature: <hex HMAC-SHA256 of raw body> X-Nova-Event: <event name>
Use your account webhook signing secret as the HMAC key (same as on the Developer API page).
Example payload:
{
"event": "invoice.underpaid",
"invoice_id": "inv_…",
"order_id": "order_123",
"status": "underpaid",
"amount": "100.50",
"amount_received": "40.00",
"amount_remaining": "60.50",
"amount_overpaid": "0",
"currency": "USDT",
"network": "mainnet",
"tx_hash": null,
"pay_address": "0x…",
"paid_at": null
}
Withdrawals fire the same way once they reach a final state — withdrawal.confirmed, withdrawal.failed or withdrawal.cancelled:
{
"event": "withdrawal.confirmed",
"withdrawal_id": "wd_…",
"reference": "payout_456",
"status": "confirmed",
"amount": "50.00",
"fee_amount": "1.00",
"net_amount": "49.00",
"currency": "USDT",
"network": "mainnet",
"to_address": "0x…",
"tx_hash": "0x…",
"failure_reason": null,
"completed_at": "2026-03-28T12:05:00+00:00"
}
Rate limits & reliability
API routes use per-endpoint throttles: 30/min for creating a payout, 60/min for creating an invoice, 300/min for reads.
For production, run php artisan schedule:run every minute. It drives invoices:sync-pending (matches deposits), withdrawals:process (broadcasts and confirms payouts) and wallets:sync-incoming (auto-forward), so both deposits and withdrawals work even when nobody opens the web app.