Oxin Pay Integration Guide

Complete guide for integrating Oxin Pay into any project — e-commerce, SaaS, donations, POS, and more.

Base URL: https://pay.oxinchain.io

Complete reference in Markdown

The authoritative version of this guide (with full code samples, migration guides, and all 22 sections) is available as a Markdown file in the repository.

/var/www/oxinchain/oxin-pay/docs/OXIN_PAY_INTEGRATION_GUIDE.md

1. Introduction

Oxin Pay accepts cryptocurrency payments on behalf of your customers. You create an invoice via API; the customer pays on a hosted checkout page; Oxin Pay sends a webhook when the payment confirms on-chain; you fulfill the order.

Oxin Pay is custodial — funds are held in Oxin Pay's custody and your merchant balance is credited (minus fee). You withdraw to your own wallets through the dashboard. You do not manage private keys.

Supported chains: BTC, LTC, DOGE, ETH, BSC, Polygon, Avalanche, Cronos, Tron, Solana, Oxin Chain — 39+ tokens total.

2. Quickstart (5 Minutes)

Step 1 — Get your API key

Sign up at pay.oxinchain.io → verify email → your key is emailed once. It starts with oxp_live_. New accounts get 100 test calls in pre-approval mode.

Step 2 — Create an invoice

USDT on BSC has token ID 20. Use it for testing:

curl -X POST https://pay.oxinchain.io/v1/invoices \
  -H "Authorization: Bearer oxp_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "tokenId": 20,
    "amountFiat": 1.00,
    "fiatCurrency": "USD",
    "merchantReference": "test-001",
    "redirectUrl": "https://example.com/thank-you",
    "cancelUrl": "https://example.com/cancel"
  }'

Step 3 — Redirect the customer

The response contains checkoutUrl. Redirect the customer there. The hosted page shows a QR code and deposit address.

Step 4 — Watch the status

curl https://pay.oxinchain.io/v1/invoices/INVOICE_PUBLIC_ID \
  -H "Authorization: Bearer oxp_live_YOUR_KEY_HERE"

Status moves: createdpendingconfirmingcompleted. See webhooks to be notified automatically.

3. Prerequisites for Production

  • Merchant account approved by the Oxin Pay team
  • API key approved (no pre-approval banner in responses)
  • Chain and token access requested and approved (Settings → Payment methods)
  • Withdrawal addresses whitelisted for each chain (24–48h review)
  • Webhook endpoint registered at an HTTPS URL
  • TOTP 2FA enabled on your merchant account (recommended)

4. Authentication

All merchant API calls use Bearer token authentication:

Authorization: Bearer oxp_live_YOUR_FULL_KEY_HERE
StateAPI calls
pending_approvalUp to 100 lifetime test calls
activeUnlimited
disabledRejected (403)
revokedRejected (401)

🔒 Store your API key in environment variables, never in source code. Never log it.

5. Supported Chains and Tokens

Fetch the live catalog — do not hardcode token IDs:

# All chains
curl https://pay.oxinchain.io/v1/public/chains

# All tokens (optional chainId filter)
curl "https://pay.oxinchain.io/v1/public/tokens?chainId=5"

# Your approved tokens
curl https://pay.oxinchain.io/v1/currencies \
  -H "Authorization: Bearer oxp_live_YOUR_KEY_HERE"

💡 For predictable pricing, use USDT or USDC on a fast-finality chain (BSC token ID 20, TRON token ID 36, Solana token ID 39). Confirmations in 1–12 seconds.

6. Core Concepts

Invoice lifecycle

created → pending → confirming → completed
                               ↘ underpaid

⚠️ Fulfill orders only on completed. Deposits in pending or confirming states are unconfirmed and can be reversed on some chains.

Fees

Default: 0.1% (10 bps) deducted from received amount. Each invoice shows feeBps, feeAmount, and netToMerchant.

Rate locking

Exchange rate is locked at invoice creation. amountCrypto does not change if the market moves. Rate risk is absorbed by the merchant.

7. Webhooks

Register a webhook URL in the dashboard (Settings → Webhooks). Oxin Pay POSTs to it when payment events occur.

Event types

EventWhen it fires
invoice.completedPayment confirmed, merchant balance credited
invoice.underpaidDeposit below required amount (after tolerance)
invoice.overpaidCustomer paid more than required; invoice still fulfilled

Headers Oxin Pay sends

HeaderValue
X-Oxinpay-Evente.g. invoice.completed
X-Oxinpay-Signaturesha256=<hex HMAC>
X-Oxinpay-TimestampUnix epoch string
X-Oxinpay-DeliveryUnique delivery ID for idempotency

Signature verification (Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, sigHeader) {
  // rawBody must be Buffer — read before JSON parsing
  const [algo, provided] = (sigHeader ?? '').split('=');
  if (algo !== 'sha256' || !provided) return false;

  const expected = crypto
    .createHmac('sha256', process.env.OXIN_PAY_WEBHOOK_SECRET)
    .update(rawBody)
    .digest('hex');

  const a = Buffer.from(provided, 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

🔒 Always use constant-time comparison (timingSafeEqual, secrets.compare_digest, hash_equals, etc.). Standard string equality (==) is vulnerable to timing attacks.

Retry policy

Up to 10 attempts with exponential backoff: min(5 × 2^(attempt−1), 86400) seconds. Timeout per attempt: 10 seconds. After 10 failures the delivery is marked dead.

📝 Oxin Pay guarantees at-least-once delivery. Deduplicate by X-Oxinpay-Delivery header before processing to avoid double fulfillment.

8. API Reference

All responses: { ok: true, data: {...}, request_id: "..." } or { ok: false, error: { code, message } }

GET
/v1/health

Service health check

None
GET
/v1/public/chains

All enabled chains

None
GET
/v1/public/tokens

All enabled tokens (optional ?chainId=N)

None
GET
/v1/public/rates

USD prices for all tokens

None
GET
/v1/currencies

Your approved chains and tokens

Bearer
GET
/v1/rates

Exchange rates for your tokens

Bearer
GET
/v1/balances

Your merchant balance per token

Bearer
GET
/v1/invoices

List invoices (paginated, filterable)

Bearer
POST
/v1/invoices

Create invoice, returns checkoutUrl

Bearer
GET
/v1/invoices/:publicId

Get single invoice detail

Bearer

📝 Webhook management and withdrawals are handled through the merchant dashboard, not via the API. See Settings → Webhooks and Balances → Withdraw.

POST /v1/invoices — request fields

FieldRequiredDescription
tokenIdYesInteger token ID from /v1/public/tokens
amountFiatYesPayment amount in fiat (e.g. 49.00)
fiatCurrencyNo3-letter code, default USD
descriptionNoShown on checkout page, max 512 chars
merchantReferenceNoYour internal order ID, max 128 chars
redirectUrlNoURL after successful payment
cancelUrlNoURL if customer cancels
metadataNoArbitrary JSON object stored on the invoice
ttlSecondsNoInvoice lifetime in seconds (default ~3600)

9. Integration Patterns

The full guide covers 9 patterns with working code snippets:

  • E-commerce checkout (one-time purchase)
  • SaaS subscription / account unlock (pay-per-period)
  • Donation widget (variable amount)
  • Marketplace / multi-vendor
  • POS / in-person terminal
  • Pay-per-API / metered credits
  • Recurring / subscription-style (manual renewal reminder)
  • Tipping / content monetization
  • Game top-up / virtual currency

See the full Markdown guide for complete code per pattern: docs/OXIN_PAY_INTEGRATION_GUIDE.md § 9

10. Frontend Integration

Redirect flow (recommended)

// Your backend creates the invoice and returns checkoutUrl
document.getElementById('pay-btn').addEventListener('click', async () => {
  const { checkoutUrl } = await fetch('/checkout/crypto', { method: 'POST' })
    .then(r => r.json());
  window.location.href = checkoutUrl;
});

Success page — do not fulfill immediately on redirect

// Poll status — fulfillment happens via webhook
async function pollStatus(publicId) {
  for (let i = 0; i < 60; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const { status } = await fetch(`/api/status/${publicId}`).then(r => r.json());
    if (status === 'completed') { showSuccess(); return; }
    if (status === 'underpaid') { showUnderpaid(); return; }
  }
  showPendingLong(); // "We'll email you when it confirms"
}

11. Code Examples (5 Languages)

Runnable examples are in the repository at examples/:

Node.js

examples/nodejs/

npm install && node server.js

Python

examples/python/

pip install -r requirements.txt && python app.py

PHP

examples/php/

composer install && php -S localhost:8000 index.php

Go

examples/go/

go run main.go

Ruby

examples/ruby/

bundle install && ruby app.rb

Each example includes a complete webhook handler with constant-time signature verification.

12. Error Reference

HTTPCodeMeaning
400VALIDATION_ERRORMissing or invalid field — check message for field name
401UNAUTHENTICATEDNo or malformed Authorization header
401api_key_not_foundKey does not exist
401api_key_revokedKey permanently revoked
403api_key_disabledKey is disabled
403ip_blockedCaller IP not in whitelist
403quota_exceededPre-approval 100-call cap reached
403FORBIDDENNot permitted (e.g. unapproved token)
404NOT_FOUNDResource does not exist
400TOKEN_DISABLEDToken disabled system-wide
400INSUFFICIENT_BALANCEWithdrawal exceeds balance
429RATE_LIMITEDToo many requests — check Retry-After header
500INTERNAL_ERRORUnexpected error — retry with backoff

13. Rate Limits

Rate limiting is per source IP. When exceeded, the response is 429 with a Retry-After header. Implement exponential backoff on 429 responses.

14. Security Best Practices

  • Store API key and webhook secret in environment variables, never in source code
  • Always verify webhook signatures — reject requests without a valid signature
  • Read raw body bytes before JSON parsing, then verify, then parse
  • Use constant-time comparison for signatures in every language
  • Use HTTPS-only webhook URLs
  • Whitelist your server's IPs on your API key
  • Wrap order fulfillment in a DB transaction (atomic mark-paid + provision)
  • Deduplicate webhook deliveries by X-Oxinpay-Delivery
  • Rotate keys quarterly or after team member departures

15. Testing

There is no sandbox. Test with real small amounts — TRON USDT (token ID 36) costs ~$0.01 in fees and confirms in ~3 seconds.

Local webhook testing

ngrok http 3000
# Register the ngrok URL in Settings → Webhooks

Replay a webhook manually

SECRET="your_secret"
BODY='{"event":"invoice.completed","data":{"invoiceId":"TEST"},"timestamp":"2026-06-22T12:00:00Z"}'
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -X POST http://localhost:3000/webhook \
  -H "X-Oxinpay-Signature: sha256=$SIG" \
  -H "X-Oxinpay-Event: invoice.completed" \
  -H "X-Oxinpay-Delivery: test-001" \
  -H "Content-Type: application/json" \
  -d "$BODY"

16. Going Live Checklist

  • API key approved (no X-Oxin-Pay-Approval: pending header)
  • Chain and token access approved in dashboard
  • Webhook endpoint registered with HTTPS URL
  • Signature verification implemented and tested with bad signatures
  • Webhook idempotency implemented (same delivery → processed once)
  • Webhook handler responds within 10 seconds
  • DB transaction wraps order fulfillment
  • At least one real-money end-to-end test passed
  • Withdrawal addresses whitelisted for each chain
  • IP whitelist configured on API key
  • Monitoring on webhook delivery failures
  • Customer support process for 'paid but order not showing'

17. Common Pitfalls

  1. Parsing JSON before signature verification (breaks HMAC byte-for-byte match)
  2. Using == instead of constant-time comparison for signatures
  3. Not deduplicating webhook deliveries → double fulfillment
  4. Fulfilling on pending or confirming → unconfirmed deposit
  5. Not handling underpaid → stuck invoices requiring support
  6. Polling for status instead of using webhooks → hits rate limits
  7. Hardcoding token IDs → breaks when tokens are added
  8. Skipping the real-money test → misses chain-specific issues

18. FAQ

Q: What is the difference between pending and completed?

A: pending = deposit detected, zero confirmations. completed = confirmation threshold reached, balance credited. Only fulfill on completed.

Q: My customer says they paid but the invoice is still pending. What do I do?

A: Check the blockchain explorer for the deposit address. If the transaction is on-chain, it is still accumulating confirmations — it will update automatically. Ask the customer for their transaction hash.

Q: Can I refund a customer?

A: Oxin Pay does not process automated refunds. Withdraw funds from your balance and send the customer their crypto manually, or offer store credit.

Q: Can I create an invoice without specifying a token?

A: No. Each invoice targets one specific token. Show a token picker in your UI using GET /v1/currencies, then create the invoice with the customer's chosen tokenId.

Q: What if my webhook server is down?

A: The dispatcher retries up to 10 times over ~21 minutes with exponential backoff. After that, deliveries are marked dead. You can replay them from the webhook delivery log in the dashboard, or check invoice status via the API.

Q: Do I need to KYC my customers?

A: Oxin Pay does not impose customer KYC. Your own compliance obligations depend on your jurisdiction. Consult a legal advisor.

See the full guide for 15+ additional FAQ entries, migration guides from Stripe / Coinbase Commerce / NOWPayments / BTCPay / PayPal, a complete glossary, and the versioning policy.

19–22. Migration, Glossary, Versioning, Support

The full Markdown guide covers migration from Stripe, Coinbase Commerce, NOWPayments, BTCPay Server, and PayPal; a 30-term glossary; the versioning policy (v1 is current, breaking changes introduce v2); and support contacts.

docs/OXIN_PAY_INTEGRATION_GUIDE.md

Full guide · ~12,000 words · 22 sections

Ready to integrate?

Start with the 5-minute quickstart, then register a webhook to receive real-time payment events.