Building Payvex — A Native Crypto Payment Gateway 🪙

21/03/2026 21/03/2026 Development 8 mins read
Table Of Contents

Why I built Payvex

Most businesses in Latin America that want to accept crypto payments face the same problem — existing solutions are either too complex, too expensive, or simply not available in their region. Stripe doesn’t support crypto. Coinbase Commerce has limited customization. And setting up your own blockchain integration from scratch is a nightmare.

I wanted to build something different — a payment gateway that feels as simple as Stripe but is natively built on blockchain. No banks, no intermediaries, no monthly fees. Just a 0.75% fee per successful transaction.

That’s how Payvex was born.

What Payvex does

Payvex is a SaaS crypto payment gateway that lets any business accept ETH, USDT, USDC, BNB, and USDT TRC-20 on their website in minutes. Merchants integrate via a simple widget, payment links, or a REST API — and customers choose which network they want to pay with.

The platform handles everything:

  • Generating unique wallet addresses per transaction
  • Detecting payments in real time via Alchemy Webhooks
  • Crediting the merchant’s balance after deducting the platform fee
  • Sending email confirmations
  • Triggering webhooks to the merchant’s server

Architecture overview

The entire application runs as a single Next.js project deployed on Vercel — no separate backend, no servers to maintain.

Next.js 14 (App Router)
├── API Routes → REST endpoints for merchants
├── Alchemy Webhooks → Real-time payment detection
├── TronGrid Polling → USDT TRC-20 detection (Tron network)
├── Supabase → PostgreSQL database via Prisma ORM
└── Resend → Transactional emails

The key insight was using Alchemy Webhooks instead of a background worker. Rather than polling the blockchain every few seconds (which would require a persistent server), Alchemy monitors the chain 24/7 and calls our endpoint the moment a payment arrives.

Customer pays → Alchemy detects TX →
POST /api/webhooks/alchemy →
Confirm order → Email merchant

For Tron (USDT TRC-20), which Alchemy doesn’t support, I implemented client-side polling — the payment page queries TronGrid every 10 seconds while the customer is waiting. It’s a clean serverless solution that works perfectly within Vercel’s free tier.

HD Wallet system

Every order gets a unique wallet address. This is critical — you can’t reuse addresses because you lose the ability to track which payment belongs to which order.

I implemented a BIP-44 HD (Hierarchical Deterministic) wallet system using ethers.js. The master seed derives unique wallets for each merchant and order combination:

// Path: m/44'/60'/merchantIndex'/0/orderIndex
const getMaster = () =>
ethers.HDNodeWallet.fromPhrase(process.env.MASTER_SEED!, undefined, "m/44'/60'")
export const deriveWallet = (merchantIndex: number, orderIndex: number) => {
const path = `${merchantIndex}'/0/${orderIndex}`
const wallet = getMaster().derivePath(path)
return { address: wallet.address, derivationPath: `m/44'/60'/${path}` }
}

For Tron wallets I had to implement the Base58Check address encoding manually since tronweb doesn’t work in Vercel’s serverless environment:

const hexToTronAddress = (hexAddress: string): string => {
const addressHex = '41' + hexAddress.slice(2)
const addressBytes = Buffer.from(addressHex, 'hex')
const hash1 = ethers.sha256(addressBytes)
const hash2 = ethers.sha256(hash1)
const checksum = Buffer.from(hash2.slice(2, 10), 'hex')
const fullAddress = Buffer.concat([addressBytes, checksum])
return bs58.encode(fullAddress)
}

Multi-network payment selection

One of the most interesting UX challenges was letting customers choose which network to pay with. The merchant selects which cryptos they accept when creating a payment link, and the customer sees a selector on the payment page:

// Customer sees this on the payment page
const CRYPTO_INFO = {
ETH: { label: 'ETH', network: 'Ethereum', feeNote: 'Fee ~$2-10' },
USDT: { label: 'USDT', network: 'Ethereum ERC-20', feeNote: 'Fee ~$5-20' },
USDT_TRC20: { label: 'USDT', network: 'Tron TRC-20', feeNote: 'Fee ~$0.01' },
BNB: { label: 'BNB', network: 'BNB Chain', feeNote: 'Fee ~$0.10' },
}

The wallet is only generated when the customer picks a network — not at order creation time. This avoids generating unnecessary wallets and keeps the derivation path consistent.

Webhook detection for dual networks

Alchemy covers Ethereum and BSC. Each network gets its own webhook with its own signing key. The endpoint verifies both signatures and accepts whichever matches:

export async function POST(req: NextRequest) {
const alchemySig = req.headers.get('x-alchemy-signature')
const bodyText = await req.text()
if (alchemySig) {
const keyETH = process.env.ALCHEMY_WEBHOOK_AUTH_TOKEN_ETH!
const keyBSC = process.env.ALCHEMY_WEBHOOK_AUTH_TOKEN_BSC!
const hmacETH = crypto.createHmac('sha256', keyETH).update(bodyText).digest('hex')
const hmacBSC = crypto.createHmac('sha256', keyBSC).update(bodyText).digest('hex')
if (hmacETH !== alchemySig && hmacBSC !== alchemySig) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
}
}
// process payment...
}

When a payment is detected the platform:

  1. Finds the pending order matching the wallet address
  2. Calculates the 0.75% fee
  3. Credits the net amount to the merchant’s balance
  4. Sends a confirmation email via Resend
  5. Fires a HMAC-signed webhook to the merchant’s server

Admin sweep system

All funds accumulate in the HD wallets until the owner manually sweeps them to a cold wallet. The sweep function iterates over all confirmed orders, connects to each derived wallet, and transfers the balance:

for (const order of orders) {
const wallet = getWalletFromPath(order.derivationPath).connect(provider)
const balance = await provider.getBalance(wallet.address)
const feeData = await provider.getFeeData()
const gasCost = feeData.gasPrice! * 21000n
const sendAmount = balance - gasCost
if (sendAmount <= 0n) continue
const tx = await wallet.sendTransaction({
to: process.env.OWNER_COLD_WALLET,
value: sendAmount,
})
txHashes.push(tx.hash)
}

Merchant integration options

I designed three integration paths for different levels of technical sophistication:

Widget — 2 lines of HTML, zero configuration:

<script src="https://cdn.payvex.io/widget.js"></script>
<div
data-payvex-key="pk_live_..."
data-amount="49.99"
data-description="Pro Plan">
</div>

Payment Links — no code at all. Merchants create a link from the dashboard and share it anywhere — WhatsApp, email, Instagram bio.

REST API — for full control:

const { paymentUrl } = await fetch('/api/orders', {
method: 'POST',
headers: { Authorization: 'Bearer sk_live_...' },
body: JSON.stringify({
amount: 49.99,
acceptedCryptos: ['USDT_TRC20', 'USDT', 'ETH'],
webhookUrl: 'https://mystore.com/webhook'
})
}).then(r => r.json())

Tech stack

LayerTechnology
FrameworkNext.js 14 (App Router)
DatabaseSupabase (PostgreSQL)
ORMPrisma
Blockchain ETH/BSCethers.js v6 + Alchemy
Blockchain TronTronGrid API
Authjose (JWT)
EmailResend
StylesTailwind CSS
DeployVercel

Challenges

Tronweb in serverless — The official tronweb library uses Node.js APIs that aren’t available in Vercel’s Edge Runtime. I ended up implementing the Tron address derivation manually using ethers.js for the HD wallet math and a custom Base58Check encoder. Zero external dependencies, works perfectly serverless.

HD wallet depth error — ethers.js v6 throws an error if you call derivePath with a full m/... path on a node that’s already been derived. The fix was to initialize the master node at m/44'/60' and then derive relative paths from there.

Dual network webhook verification — Having two Alchemy webhooks (ETH + BSC) hitting the same endpoint required trying both HMAC keys instead of just one. Simple fix but easy to miss.

What’s next

  • Tron webhooks — TronGrid is adding webhook support. When available, the polling approach will be replaced
  • Testnet mode — Toggle for merchants to test integrations without real funds
  • Analytics dashboard — Charts for transaction volume, conversion rates, top-performing payment links
  • Referral program — Merchants who refer others earn a fee reduction

Conclusion

Payvex taught me a lot about building real-world blockchain integrations — the gap between “accepting crypto” as a concept and actually implementing it reliably in production is significant. HD wallets, network fees, confirmation times, multi-chain support, serverless constraints — each piece has its own complexity.

The result is a platform I’m genuinely proud of: clean merchant UX, reliable payment detection, and a business model that only charges when value is delivered.

If you want to try it: payvex.vercel.app