White-Label Payment Gateway Development
White-label gateway — this is not just "payment gateway with someone else's logo". This is full-featured payment platform that client sells under their brand to their customers. This changes entire architecture: need multi-tenancy, data isolation between merchants, API for partner integration, usage-based billing, white-label UI kit.
Architectural Levels
┌─────────────────────────────────────────────────────┐
│ White-label Client (Tenant) │
│ Own domain, logo, colors │
├─────────────────────────────────────────────────────┤
│ Merchant Layer │
│ Merchants create payment links, API keys │
├─────────────────────────────────────────────────────┤
│ Core Gateway Engine │
│ Multi-network, monitoring, confirmations, webhook │
├─────────────────────────────────────────────────────┤
│ Blockchain Infrastructure │
│ Nodes / RPC providers per network │
└─────────────────────────────────────────────────────┘
Each tenant has: separate domain (or subdomain), branded checkout UI, isolated HD wallet derivations, separate billing.
Multi-Tenancy and Isolation
Address Derivation Strategy
Each tenant gets own master xpub from which their merchants' and orders' addresses are derived. Derivation path structure:
m / purpose' / coin_type' / tenant_id' / merchant_id / order_index
Example for EVM networks:
import { HDNodeWallet } from "ethers";
class TenantWalletManager {
private masterNode: HDNodeWallet;
constructor(masterMnemonic: string) {
this.masterNode = HDNodeWallet.fromPhrase(masterMnemonic);
}
getTenantXpub(tenantId: number): string {
// Derive at tenant level — return xpub, not private key
return this.masterNode
.deriveChild(44 + 0x80000000) // purpose 44'
.deriveChild(60 + 0x80000000) // ETH coin_type 60'
.deriveChild(tenantId + 0x80000000) // tenant (hardened)
.neuter()
.extendedKey; // public xpub
}
getDepositAddress(tenantXpub: string, merchantId: number, orderIndex: number): string {
const tenantNode = HDNodeWallet.fromExtendedKey(tenantXpub);
return tenantNode
.deriveChild(merchantId)
.deriveChild(orderIndex)
.address;
}
}
This allows tenant to independently verify addresses (they have their own xpub), but not get control of master keys.
Data Isolation in PostgreSQL
Schema with row-level security instead of separate databases (simpler in operations):
-- RLS policies for tenant isolation
ALTER TABLE payments ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON payments
USING (tenant_id = current_setting('app.tenant_id')::uuid);
-- In each request set context
SET LOCAL app.tenant_id = '...';
For high-load platforms (100+ tenants with large volume) — separate PostgreSQL schema per tenant via search_path.
Merchant API
Merchants integrate via REST API, similar to Stripe/PayPal. Authentication via API keys (public + secret):
// Create payment invoice
// POST /api/v1/invoices
{
"amount": "99.99",
"currency": "USD",
"accepted_coins": ["ETH", "USDT_ERC20", "BTC"],
"order_id": "order_123", // your internal ID
"customer_email": "[email protected]",
"success_url": "https://shop.example.com/success",
"cancel_url": "https://shop.example.com/cancel",
"metadata": { "product_id": "456" },
"expires_in": 900 // seconds (15 minutes)
}
// Response
{
"invoice_id": "inv_abc123",
"checkout_url": "https://pay.whitelabel.com/i/inv_abc123",
"status": "pending",
"expires_at": "2024-01-15T10:45:00Z",
"payment_options": [
{
"coin": "ETH",
"address": "0x...",
"amount": "0.02941",
"rate": "3400.00",
"rate_expires_at": "2024-01-15T10:45:00Z"
}
]
}
Webhook System
Webhooks must be reliable — retry logic, signatures, deduplication:
interface WebhookEvent {
id: string; // unique event ID
type: "payment.confirmed" | "payment.failed" | "payment.expired";
created_at: string;
data: {
invoice_id: string;
amount_paid: string;
currency_paid: string;
txhash: string;
confirmations: number;
};
}
// Signature: HMAC-SHA256(webhookSecret, `${timestamp}.${JSON.stringify(payload)}`)
// Header: X-Signature: t=1705312200,v1=abcdef...
Retry schedule on failed delivery: 1min → 5min → 30min → 2h → 8h → 24h. After 6 failed attempts — mark as failed, merchant dashboard shows failed webhooks with manual retry option.
Checkout UI as White-Label Component
Frontend must support full customization under tenant brand:
// Tenant config from DB
interface TenantTheme {
primaryColor: string;
logoUrl: string;
fontFamily: string;
borderRadius: "none" | "sm" | "md" | "lg" | "full";
darkMode: boolean;
}
// Next.js app with dynamic CSS
// When loading checkout page — request tenant theme
// CSS variables injected into <head>
const theme = await getTenantTheme(tenantId);
const css = `
:root {
--color-primary: ${theme.primaryColor};
--border-radius: ${borderRadiusMap[theme.borderRadius]};
--font-family: ${theme.fontFamily};
}
`;
Components: coin selection → QR code with address → countdown timer → confirmation status → success/fail screen.
Custom domain: each tenant can connect own domain (pay.theirclient.com). Implemented via Cloudflare for business (Cloudflare for SaaS) or nginx with automatic Let's Encrypt via Caddy.
Merchant Billing
Monetization models:
| Model | Implementation |
|---|---|
| % of volume | Retain X% when sweep to hot wallet |
| Fixed fee per transaction | Add fee to invoice sum or deduct on sweep |
| Subscription | Monthly payment, API access |
| Combined | Subscription + reduced transaction fee |
Sweep with fee deduction:
async function sweepWithFee(depositAddress: string, amount: bigint, tenantId: string) {
const tenant = await getTenant(tenantId);
const feeRate = tenant.feeRate; // e.g., 0.01 = 1%
const fee = amount * BigInt(Math.floor(feeRate * 10000)) / 10000n;
await sendTransaction(PLATFORM_FEE_WALLET, fee);
await sendTransaction(tenant.hotWallet, amount - fee);
}
Security and Compliance
API Key management: generate via crypto.randomBytes(32).toString('hex'), store only hashed value in DB (sha256), show once on creation.
Rate limiting: separate limits per API key, configurable by tenant via dashboard. Redis + sliding window algorithm.
IP whitelist: merchants can restrict API key to specific IPs. Mandatory for production integrations.
Audit log: all merchant admin actions (key creation/revocation, settings changes, withdrawal requests) logged to immutable table.
AML: optional integration with Chainalysis or Elliptic for checking sender addresses against sanction lists. Relevant if platform operates in regulated jurisdictions.
Infrastructure
Load Balancer (nginx/Caddy)
├── API Gateway (Node.js/Fastify)
├── Checkout Frontend (Next.js)
└── Admin Dashboard (Next.js)
Background Services
├── Block Monitor Workers (one per network)
├── Confirmation Tracker
├── Sweep Worker
├── Webhook Delivery Worker
└── Rate Sync Worker (prices)
Data Layer
├── PostgreSQL (primary + read replicas)
├── Redis (caches, queues, rate limits)
└── S3-compatible (logs, exports)
Development Timeline
| Component | Timeline |
|---|---|
| Core gateway engine (monitoring, confirmations, sweep) | 1–2 weeks |
| Merchant API + webhook system | 1 week |
| Checkout UI with white-label theming | 1 week |
| Tenant management + billing | 1 week |
| Admin dashboard for tenants | 1–2 weeks |
| Custom domain + SSL automation | 3–5 days |
| Testing, security review, hardening | 1–2 weeks |
Minimum timeline from scratch to production-ready MVP: 6–8 weeks. With support for 5–6 networks, branded checkout, webhook system and basic dashboard. Full-featured platform with enterprise features — 3 months.







