Home How it works Features Payouts API docs Help
Log In Get Started
Back to home

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

  1. Create an account and open your BSC wallet in the dashboard.
  2. Issue an API token and copy your webhook signing secret from the Developer API (available after you sign in) page.
  3. 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 — mainnet or testnet
  • order_id — unique per account; pattern [a-zA-Z0-9._-]+
  • ipn_callback_url — optional HTTPS URL for confirmed-payment webhooks
  • callback_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
pendingNothing received yet.
underpaidSome received, still short. More payments can still arrive.
confirmedPaid in full, or within the platform tolerance.
overpaidPaid in full with a surplus — see amount_overpaid.
expiredWindow 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.expired so 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 receives net_amount = amount − fee_amount (request 30 → destination receives 29.4).
  • to_address — optional; defaults to the withdrawal wallet address on your account
  • reference — 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/withdrawals with "source": "auto_forward".
  • Their reference is auto_<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 is amount, the fee is fee_amount, and net_amount is 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.