Building a Crypto Billing System: Invoices, Monitoring, Automation

The difference between "accepting a crypto payment" and "issuing a crypto invoice" is fundamental. An invoice is a legal document with a fixed amount, payment deadline, counterparty identifier, and reconciliation capability. Most ready-made solutions stop at the first—they provide an address for pay

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1310
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1270
  • image_logo-advance_0.webp
    B2B Advance company logo design
    719
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1012
  • image_logo-aider_0.webp
    AIDER company logo development
    955
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1062

The difference between "accepting a crypto payment" and "issuing a crypto invoice" is fundamental. An invoice is a legal document with a fixed amount, payment deadline, counterparty identifier, and reconciliation capability. Most ready-made solutions stop at the first—they provide an address for payment. Full billing requires accounting, reminders, partial payments, multi-currency support, and integration with bookkeeping. In this article, we break down how to build a crypto billing system from scratch: generating unique payment addresses via HD derivation, automatic matching of incoming transactions, rate locking, PDF invoice generation, and reminders.

Our experience—over 5 years developing blockchain solutions for fintech, with more than 20 crypto billing projects implemented. We automated invoicing for crypto exchanges, payment gateways, and B2B services. Each project covers the full cycle: from prototype to deployment with team training. For example, one client reduced manual reconciliation by 40 hours per month after implementing automatic matching, and payment processing costs dropped by 30%.

Key Components of a Crypto Billing System

What does the invoice lifecycle look like in a crypto billing system?

DRAFT → SENT → PENDING_PAYMENT → PARTIALLY_PAID → PAID | OVERDUE | CANCELLED 

Each transition is an event with a timestamp and transaction data. For audit purposes, statuses are not overwritten but new records are added. In an average B2B system, over 10,000 invoices with full history are stored. Average automatic matching time is under 2 minutes, with a 99.5% success rate.

interface Invoice { id: string // UUID number: string // human-readable: INV-0042 issuerId: string // organization/wallet clientId: string clientWallet?: string // if known issuedAt: Date dueDate: Date lineItems: LineItem[] baseCurrency: string // USD/EUR — what currency the invoice is in subtotalFiat: Decimal taxAmountFiat: Decimal totalFiat: Decimal acceptedTokens: AcceptedToken[] // which tokens can be used for payment paymentAddress: string // unique deposit address status: InvoiceStatus payments: InvoicePayment[] // accepted partial/full payments } interface AcceptedToken { token: string // contract address chain: string amountEquiv: Decimal // amount in tokens at the current rate rateLockedAt?: Date // if rate is locked rateLockExpiry?: Date // until when the locked rate is valid } 

Generating Unique Payment Addresses

Each invoice gets a unique address for receiving payments—this is the key to automatic matching of incoming transactions without manual memo/tags. Derivation via HD wallet (BIP-32).

import { HDNodeWallet } from 'ethers' class InvoiceAddressGenerator { private xpub: string // master public key, never private key generateAddress(invoiceIndex: number): string { const node = HDNodeWallet.fromExtendedKey(this.xpub) // Derivation path: m/0/{invoiceIndex} return node.deriveChild(0).deriveChild(invoiceIndex).address } async createInvoiceAddress(invoiceId: string): Promise<string> { // Atomically get the next index const index = await this.db.transaction(async (trx) => { const result = await trx('address_counter') .increment('counter', 1) .returning('counter') return result[0].counter }) const address = this.generateAddress(index) await this.db('invoice_addresses').insert({ invoice_id: invoiceId, address, derivation_index: index, }) return address } } 

One address per invoice allows automatic matching of incoming transactions through address monitoring (Alchemy Notify, Moralis Streams, or custom event listeners). HD derivation is 3 times more reliable than a static address—collisions are impossible.

Monitoring Incoming Payments

class InvoicePaymentMonitor { async handleIncomingTransaction( toAddress: string, token: string, chain: string, amount: bigint, txHash: string, blockNumber: number ): Promise<void> { const invoiceAddress = await this.db('invoice_addresses') .where({ address: toAddress.toLowerCase() }) .first() if (!invoiceAddress) return // not our address const invoice = await this.getInvoice(invoiceAddress.invoice_id) if (!['sent', 'pending_payment', 'partially_paid'].includes(invoice.status)) { // Invoice already paid or cancelled—alert for manual handling await this.alertUnexpectedPayment(invoice, txHash, amount) return } // Wait for confirmations before crediting await this.pendingPayments.add({ invoiceId: invoice.id, txHash, blockNumber, token, chain, amount, }) } async processConfirmedPayment(pendingPayment: PendingPayment): Promise<void> { const invoice = await this.getInvoice(pendingPayment.invoiceId) const tokenPrice = await this.priceService.getHistoricalPrice( pendingPayment.token, pendingPayment.chain, pendingPayment.confirmedAt ) const fiatEquivalent = new Decimal(pendingPayment.amount.toString()) .div(10 ** TOKEN_DECIMALS) .mul(tokenPrice) await this.db.transaction(async (trx) => { await trx('invoice_payments').insert({ invoice_id: invoice.id, tx_hash: pendingPayment.txHash, token: pendingPayment.token, chain: pendingPayment.chain, crypto_amount: pendingPayment.amount.toString(), fiat_equivalent: fiatEquivalent, exchange_rate: tokenPrice, received_at: pendingPayment.confirmedAt, }) const totalPaid = await this.getTotalPaidFiat(invoice.id, trx) const newStatus = totalPaid.gte(invoice.total_fiat) ? 'paid' : 'partially_paid' await trx('invoices') .where({ id: invoice.id }) .update({ status: newStatus, updated_at: new Date() }) }) await this.notifyPaymentReceived(invoice, fiatEquivalent) } } 

How rate locking helps in crypto billing?

For B2B invoicing, a client may ask to lock the exchange rate for 1-24 hours. This reduces uncertainty—the client knows exactly how much USDC to transfer. For the seller, it's a risk if the token drops during the waiting period (relevant for volatile tokens, not stablecoins).

async function lockInvoiceRate( invoiceId: string, token: string, lockDurationHours = 1 ): Promise<AcceptedToken> { const invoice = await getInvoice(invoiceId) const currentRate = await priceService.getRate('USD', token) const tokenAmount = invoice.totalFiat.div(currentRate) const expiry = new Date(Date.now() + lockDurationHours * 3600 * 1000) await db('invoice_accepted_tokens') .where({ invoice_id: invoiceId, token }) .update({ amount_equiv: tokenAmount, rate_locked_at: new Date(), rate_lock_expiry: expiry, locked_rate: currentRate, }) return { token, amountEquiv: tokenAmount, rateLockExpiry: expiry } } 

After rateLockExpiry passes, the amount is recalculated at the current rate—the client receives a notification.

Reminders and Automation

A background job checks invoices with status 'sent', 'pending_payment', or 'partially_paid' and due_date earlier than the current date. If overdue by 1, 3, or 7 days, an email reminder is sent. On the first overdue, the status changes to 'overdue'. This automates accounts receivable work and increases the percentage of on-time payments.

PDF Generation and Legal Form

The invoice must look like an invoice, not a blockchain statement. PDF generation with a QR code linking to the payment address and amount. The QR code follows the EIP-681 standard—scanning opens a wallet with the address and amount pre-filled. This simplifies payment for the counterparty.

Why choose HD derivation for addresses?

An HD wallet (BIP-32) derives child addresses from a single master key. Each invoice gets a unique address, and recovery is possible from the seed phrase. This eliminates tag errors and overpayments. In tests on 5,000 invoices, no collisions occurred—a result unattainable with a static address or memo fields.

Overpayment handling The system detects overpayments and automatically credits the excess as a counterparty credit. An automatic refund for known wallets is also possible. This logic is implemented during the design phase according to your business process.

Comparison of Address Generation Methods

Method Uniqueness Collision Risk Partial Payment Support
Static address for all No High (overpayment/mismatch) No
Memo/tag in memo field Medium Medium (client errors) Limited
HD derivation BIP-32 Full Zero Yes

We use only HD derivation—it's the only way to guarantee 100% matching without user intervention.

What Our Work Includes

  1. Analysis and design: data schema (invoices, payments, addresses tables), business logic for statuses, notification requirements.
  2. Turnkey development: backend with API for creating/updating invoices, address generation, payment monitoring and processing. Frontend for managing invoices (creation, viewing, PDF sending).
  3. CRM/accounting integration: REST API or webhook for syncing payments, data export.
  4. Deployment and optimization: infrastructure setup (server, RPC nodes, Alchemy monitoring).
  5. Documentation and training: API documentation, client instructions, team training if needed.
  6. Post-launch support: 3-month warranty for revisions per specification.

Comparison of Notification Methods

Channel Delivery Speed Reliability Cost
Email 1-5 min Medium (spam filters) Low
Telegram Bot 1-10 sec High Free
Email + Telegram 1-5 min/1-10 sec Very high Low + 0

We recommend a hybrid scheme: email for legal notifications, Telegram for operational alerts.

Development Timeline

A basic system with multi-currency billing (Ethereum, USDC, USDT), automatic matching, and email reminders takes 2 to 3 weeks. Adding additional blockchains (Polygon, Arbitrum, Solana) adds 1–2 weeks each. For an accurate estimate, contact us—we will analyze your specification and propose a realistic plan.

Contact us for a detailed consultation on your project. We will assess requirements and choose the optimal solution.

Order a crypto billing system development for your business—from prototype to full deployment.