Developer documentation
Accept USDT and USDC straight into your own wallets. You create an invoice through the API, send the customer to the hosted checkout, and receive a signed webhook as soon as the payment is confirmed on-chain or on Binance/Bybit.
Quickstart
- Create an account and add your receiving wallets (Dashboard → Wallets).
- Add and verify your website (Dashboard → Websites) with a DNS TXT record or a file.
- Create a test API key for that website and build your integration in the sandbox.
- Switch to a live key when everything works.
Authentication
Send your secret key as a Bearer token. Keys start with opk_live_ (real payments) or opk_test_ (sandbox). Each key belongs to one verified website: callback_url, success_url and cancel_url must be on that domain. Never put a key in browser or mobile code.
Authorization: Bearer opk_test_xxxxxxxxxxxxxxxx
Sandbox
Test keys create invoices with "livemode": false. They never match real transfers, do not count toward your plan limits and are hidden from your revenue statistics. Mark a test invoice as paid with the simulate endpoint (or the button on its checkout page) to receive a real signed invoice.paid webhook.
API reference
POST/api/v1/invoices
Creates an invoice. Send an Idempotency-Key header to make retries safe.
| Field | Type | Notes |
|---|---|---|
amount | string | Required. USD amount, max 2 decimals, e.g. "25.00" |
network | string | Optional. One of the network keys. Without it the customer chooses. |
external_order_id | string | Your order number (max 120) |
callback_url | https URL | Webhook target on your website |
success_url, cancel_url | https URL | Where the customer returns |
customer_name, customer_email | string | Optional |
ttl_minutes | integer | 5 – 1440, default 30 |
meta | object | Up to 20 keys, returned in webhooks |
curl -X POST https://oneipay.com/api/v1/invoices \
-H "Authorization: Bearer opk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001" \
-d '{"amount":"25.00","external_order_id":"1001","network":"trc20",
"callback_url":"https://shop.example/oneipay/webhook",
"success_url":"https://shop.example/thank-you"}'
Response 201:
{
"data": {
"id": "inv_8Kq2...", "livemode": false, "status": "pending",
"currency": "USDT", "amount": "25.00", "payable_amount": "25.000137",
"network": "trc20", "address": "T...", "external_order_id": "1001",
"tx_id": null, "confirmations": null,
"expires_at": "2026-11-09T10:30:00+00:00", "paid_at": null,
"checkout_url": "https://oneipay.com/pay/inv_8Kq2..."
}
}
The customer must send exactly payable_amount. The small unique cents identify the payment, so no memo is needed on-chain.
GET/api/v1/invoices/{id}
Returns the same object. Status values: pending, paid, expired, cancelled, refunded. Always re-check an invoice with this call before you deliver goods.
POST/api/v1/invoices/{id}/simulate-payment
Test keys only. Marks a pending test invoice as paid and sends the webhook.
Webhooks
We POST JSON to your callback_url. Events: invoice.paid, invoice.expired, invoice.refunded. Answer with HTTP 2xx; failed deliveries are retried with back-off and can be re-sent from Dashboard → Webhooks.
{
"id": "evt_inv_8Kq2..._invoice.paid",
"event": "invoice.paid",
"created_at": "2026-11-09T10:12:41+00:00",
"data": { "id": "inv_8Kq2...", "livemode": true, "status": "paid", "amount": "25.00", "...": "...", "meta": {} }
}
Verify the signature
Header X-OneiPay-Signature: t=<unix time>,v1=<hex>, where v1 = HMAC_SHA256(webhook_secret, t + "." + raw_body). Reject requests older than 5 minutes and compare in constant time.
// PHP
[$t, $v1] = [null, null];
foreach (explode(',', $_SERVER['HTTP_X_ONEIPAY_SIGNATURE'] ?? '') as $p) {
[$k, $v] = array_pad(explode('=', $p, 2), 2, '');
if ($k === 't') $t = (int) $v; elseif ($k === 'v1') $v1 = $v;
}
$body = file_get_contents('php://input');
$ok = $t && $v1 && abs(time() - $t) <= 300
&& hash_equals(hash_hmac('sha256', $t . '.' . $body, $secret), $v1);
// Node.js
const crypto = require('crypto');
function verify(header, rawBody, secret) {
const p = Object.fromEntries(header.split(',').map(s => s.split('=')));
if (!p.t || !p.v1 || Math.abs(Date.now() / 1000 - p.t) > 300) return false;
const mac = crypto.createHmac('sha256', secret).update(p.t + '.' + rawBody).digest('hex');
return mac.length === p.v1.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(p.v1));
}
# Python
import hmac, hashlib, time
def verify(header, raw_body: bytes, secret: str) -> bool:
p = dict(x.split('=', 1) for x in header.split(','))
if 't' not in p or 'v1' not in p or abs(time.time() - int(p['t'])) > 300:
return False
mac = hmac.new(secret.encode(), p['t'].encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, p['v1'])
Errors
| HTTP | error | Meaning |
|---|---|---|
| 401 | — | Missing, wrong or revoked API key |
| 402 | plan_restriction | Your plan does not allow this (limit reached, network not included, plan expired) |
| 403 | test_key_required | Simulation needs a test key |
| 404 | — | Invoice not found for this key's mode |
| 422 | url_not_allowed, customer_blocked, validation | Fix the request; errors lists invalid fields |
| 429 | — | Too many requests – slow down |
Networks
| Key | Name | Token |
|---|---|---|
trc20 | USDT (TRC20 · Tron) | USDT |
bep20 | USDT (BEP20 · BNB Smart Chain) | USDT |
erc20 | USDT (ERC20 · Ethereum) | USDT |
arbitrum | USDT (Arbitrum One) | USDT |
optimism | USDT (Optimism) | USDT |
polygon | USDT (Polygon PoS) | USDT |
usdc_erc20 | USDC (Ethereum) | USDC |
usdc_bep20 | USDC (BNB Smart Chain) | USDC |
usdc_polygon | USDC (Polygon PoS) | USDC |
usdc_arbitrum | USDC (Arbitrum One) | USDC |
usdc_optimism | USDC (Optimism) | USDC |
usdc_base | USDC (Base) | USDC |
usdt_ton | USDT (TON) | USDT |
usdt_sol | USDT (Solana) | USDT |
usdc_sol | USDC (Solana) | USDC |
btc | Bitcoin (BTC) | BTC |
ltc | Litecoin (LTC) | LTC |
eth | Ethereum (ETH) | ETH |
bnb | BNB (BNB Smart Chain) | BNB |
trx | TRON (TRX) | TRX |
sol | Solana (SOL) | SOL |
okx_deposit | USDT → OKX (deposit) | USDT |
bitget_deposit | USDT → Bitget (deposit) | USDT |
kucoin_deposit | USDT → KuCoin (deposit) | USDT |
payeer | Payeer (USD) | USD |
perfectmoney | Perfect Money (USD) | USD |
binance_deposit | USDT → Binance (deposit) | USDT |
bybit_pay | Bybit Pay (USDT to UID) | USDT |
bybit_deposit | USDT → Bybit (deposit) | USDT |
binance_pay | Binance Pay (USDT) | USDT |
Plugins & SDKs
Ready-made modules use the same API and verify every webhook twice (signature + API re-check).
// PHP SDK
require 'OneiPay.php';
$api = new OneiPay('https://oneipay.com', 'opk_test_YOUR_KEY', 'whsec_YOUR_SECRET');
$inv = $api->createInvoice(['amount' => '25.00', 'external_order_id' => '1001']);
header('Location: ' . $inv['checkout_url']);
// webhook endpoint – throws on a bad signature
$event = $api->verifyWebhook(file_get_contents('php://input'), $_SERVER['HTTP_X_ONEIPAY_SIGNATURE'] ?? '');
// Node.js SDK
const OneiPay = require('./oneipay');
const api = new OneiPay({ baseUrl: 'https://oneipay.com', apiKey: 'opk_test_YOUR_KEY', webhookSecret: 'whsec_YOUR_SECRET' });
const inv = await api.createInvoice({ amount: '25.00', external_order_id: '1001' });