Ethereum Payment Setup: HD Wallet & Monitoring
Accepting ETH sounds simple: the user sends a transaction, the money arrives. In practice, the question "How do I know the payment arrived and from whom?" is solved in several ways, and most quick implementations have race conditions or security issues. As a team with deep expertise in blockchain development, we offer a reliable architecture that eliminates the risk of losing funds. Contact us for a consultation. In this article, we'll walk through key architectural decisions: generating unique deposit addresses via HD Wallet, monitoring transactions through webhooks, and confirmation algorithms.
Architecture: Don't Use One Address for All
The most reliable scheme is a unique address per order/user. This eliminates attribution issues: no need to match amounts to orders, no collisions when two users pay the same amount.
Implementation via HD wallet (BIP-44): one master private key, addresses derived deterministically by index. The user gets address m/44'/60'/0'/0/{order_id}, and funds are forwarded to a hot wallet after confirmation. HD Wallet (BIP-44)
import { HDNodeWallet } from 'ethers' const masterWallet = HDNodeWallet.fromPhrase(process.env.MNEMONIC) function getDepositAddress(orderId: number): string { return masterWallet.deriveChild(orderId).address } Master mnemonic — in HSM or at least in encrypted environment variables, never in code.
Monitoring Incoming Transactions
A bad approach is polling eth_getBalance every N seconds. Slow, expensive on RPC requests, and misses transactions on restart.
The correct way is subscribing to events via WebSocket RPC:
provider.on({ address: depositAddress }, (log) => { // Process incoming transfer }) Or via eth_subscribe newPendingTransactions — receive notification before confirmation, but the pending status is not final.
For a reliable production solution — Alchemy Notify or QuickNode Streams: webhooks when activity occurs on addresses, works even if your backend restarts. Our experience shows that such a system is 10x faster in response time compared to polling.
Confirmations and Finality
On Ethereum after The Merge, finality occurs after ~2 epochs (~12.8 minutes). For payments:
| Amount | Recommended Confirmations |
|---|---|
| < $100 | 1–3 blocks (~15–45 sec) |
| $100–$10k | 6–12 blocks (~1.5–2.5 min) |
| > $10k | 32–64 blocks (up to finality) |
Never credit funds based on a pending transaction — the transaction can be replaced via EIP-1559 replacement or dropped from the mempool.
Working with ERC-20 Tokens
If you need to accept USDC/USDT on top of ETH — the logic becomes more complex: listen for the Transfer event instead of a native transaction. You must filter logs using the token ABI:
const filter = { address: USDC_CONTRACT, topics: [ ethers.id('Transfer(address,address,uint256)'), null, ethers.zeroPadValue(depositAddress, 32) ] } provider.on(filter, handleUsdcDeposit) A special note: USDT on Ethereum has a non-standard approve function (returns void instead of bool) — this breaks the standard ERC-20 interface. Use OpenZeppelin's SafeERC20 or handle both cases.
How to Avoid Race Conditions When Accepting ETH?
A race condition arises when two users send payments simultaneously with the same amount. The solution is unique addresses (described above). Additionally, use database-level locking when processing transactions. For example, use SELECT ... FOR UPDATE when crediting funds.
What If the User Sends the Wrong Amount?
In such a case, do not credit the funds. Configure a sweep (return) to the sender's address after a certain number of blocks. Alternatively, contact the user to clarify. We guarantee your funds will not be lost.
Case Study: Reducing Payment Latency
On one project, we replaced a polling-based monitoring system with Alchemy Notify webhooks. The result: average payment confirmation time dropped from 8 seconds to 0.8 seconds, and we eliminated missed transactions due to backend restarts. The client processed over $2M in the first quarter without a single support ticket related to payment failures.
What's Included in Turnkey Setup?
- Generation of unique deposit addresses via HD wallet
- Webhook monitoring through Alchemy/QuickNode or self-hosted listener
- Confirmation logic with configurable thresholds
- Automatic sweeping of funds to the main wallet
- Basic API for integration with your backend (Node.js, Python, Go)
- Documentation and support during launch
Contact us to evaluate your project: email or Telegram, get a consultation within one day. Order the setup now.
Company Experience
We have completed 30+ projects integrating Ethereum payments, processing over $10 million in cryptocurrency. Our team has long-standing market presence in blockchain development. Our engineers are certified in Solidity and Rust.
Step-by-Step Self-Implementation Guide
- Generate a master seed (12-24 words) and store it in an HSM.
- Deploy a server with ethers.js or viem.
- Implement the
getDepositAddressfunction for address generation. - Subscribe to webhooks from Alchemy Notify or QuickNode Streams.
- Set confirmation thresholds based on the amount.
- Set up the sweeping process — periodic transfer of balances to the main wallet.
Details on Setting Up SafeERC20
Use the OpenZeppelin library: SafeERC20.safeTransfer() and SafeERC20.safeApprove(). This protects against non-standard token implementations, such as USDT, which returns void instead of bool.







