We develop turnkey crypto donation systems for streamers, bloggers, and charitable foundations. In five years of work, we've encountered dozens of nuances: from Ethereum reorgs to dust attacks on Solana. At first glance, the task is simple: accept crypto from a donor. But in practice, several non-trivial questions arise. How to show status in real time? How to work with different networks and tokens? What to do with dust after gas? And how not to lose a donation during a network reorg? Below is a proven architecture that we've implemented in 15+ projects. It allows accepting donations in USDC, USDT, ETH, and other tokens with fees 30% lower than the market average, thanks to gas optimization and L2 selection.
Why the minimal scheme doesn't scale?
For a one-time or low-volume donation flow, complex infrastructure is not needed. A single-address scheme:
- Publish one address (ETH/BTC/SOL) on the page
- Webhook monitoring via a provider (Alchemy Notify, Moralis, QuickNode)
- Webhook triggers the backend → updates the donor's UI in real-time
// Alchemy Notify webhook handler app.post('/webhook/donations', express.raw({type: 'application/json'}), async (req, res) => { const isValid = verifyAlchemySignature(req.body, req.headers['x-alchemy-signature'], WEBHOOK_SECRET) if (!isValid) return res.sendStatus(401) const payload = JSON.parse(req.body.toString()) for (const activity of payload.event?.activity || []) { if (activity.toAddress.toLowerCase() === DONATION_ADDRESS.toLowerCase() && activity.value > 0) { await db.donations.create({ txHash: activity.hash, fromAddress: activity.fromAddress, amount: activity.value, asset: activity.asset, status: 'pending' }) io.emit('new_donation', { amount: activity.value, asset: activity.asset }) } } res.sendStatus(200) }) Webhooks are more reliable than polling: providers retry delivery on failure, and you don't miss a transaction. But this scheme breaks under high load: without queues and a state machine, you can lose a donation to a reorg or duplication. For example, during an Ethereum reorg (depth 1–2 blocks), donations worth hundreds of dollars may be reversed — without a state machine, you'll credit them and can't roll back.
How to avoid losing donations during a reorg?
We build a state machine with four states: detected (transaction in mempool), confirming (included in block, waiting for confirmations), confirmed (required number of confirmations reached), failed (reorg or dropped). For ETH mainnet — 6 confirmations, for L2 (Arbitrum, Base, Optimism) — 1–2 is enough (L2 finality is faster, reorgs rare). For BTC — 1 confirmation for small amounts, 3+ for large. This system protects against donation loss and requires no manual intervention.
How we build a reliable crypto donation system?
We design the system from scratch for your scenario. Main components:
| Component | Technology | Purpose |
|---|---|---|
| Blockchain monitoring | Alchemy Notify / QuickNode Streams | Real-time transaction reception |
| Backend | Node.js (Express) + Bull queue | Processing, deduplication, state machine |
| Database | PostgreSQL | Store donations, users, widgets |
| Real-time | Socket.io | Send notifications to widgets and dashboard |
| Widget | HTML/CSS/JS (Webpack assembly) | Customizable pop-up for OBS |
| Dashboard | React + Recharts | Donation statistics, CSV export |
Confirmations: when to consider a donation received
Do not consider a donation final on the first notification. We implement a state machine with configurable number of confirmations — this guarantees you won't credit a reversed transaction.
Widget for streamers / real-time notifications
The most common scenario: a streamer wants to receive crypto donations with a pop-up notification in OBS. Architecture:
Blockchain → Webhook Provider → Backend API → WebSocket (Socket.io) → OBS Browser Source Widget — an HTML page with a WebSocket connection:
<!-- OBS Browser Source URL: https://yourapp.com/widget?streamer_id=123 --> <script> const socket = io('wss://yourapp.com') socket.on('donation', (data) => { showDonationAlert(data.sender, data.amount, data.currency, data.message) }) </script> Multi-currency without unnecessary complexity
For a small system — accept USDC/USDT on several networks plus native ETH. This covers 90% of the audience. Give the donor a choice of network — ETH mainnet, Arbitrum, Base, Polygon — this lowers the barrier (gas on L2 is cheaper).
Identification of donation by network + address + token:
def identify_donation(network: str, token: str, amount: float, tx_hash: str) -> Donation: usd_value = convert_to_usd(token, amount, network) return Donation( tx_hash=tx_hash, network=network, token=token, raw_amount=amount, usd_value=usd_value, confirmed=False ) How to add a new network to the system?
Step-by-step instruction for your administrator:
- In the dashboard, select "Add Network" and specify the RPC endpoint (Infura, Alchemy, QuickNode).
- Specify the wallet address for receiving donations in that network.
- Set up a webhook to the same URL as for other networks (the system automatically detects the network by chainId).
- Activate the network — within a minute, donations will start processing.
What's included in the work
| Document / Result | Description |
|---|---|
| Technical specification | Architecture description, provider and stack choice |
| Source code | Repository with backend, widget, and dashboard |
| Deployment | Server setup, domain, SSL, CI/CD |
| Provider accesses | Alchemy/QuickNode, database, hosting |
| Operations guide | How to add networks, tokens, configure widget |
| Team training | 2 hours of admin consultation |
| Support | 1 month after launch (bugs, questions) |
Timeline and cost
Basic system with one wallet, webhook, and widget — 3–5 days development. Full-featured with multi-currency, dashboard, and multi-network support — 1–2 weeks. Cost is calculated individually after a brief. Contact us for an estimate — we'll consult for free and suggest the optimal architecture. Our experience: 15+ implementations, gas cost reduction up to 40% through L2 usage and batch transaction processing.







