Monero Payment Acceptance Setup

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Monero Payment Acceptance Setup
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Monero Payment Acceptance Setup

Monero (XMR) is not just another crypto payment method. Its privacy protocol is built into the protocol level and is not optional: every transaction uses stealth addresses, Ring Signatures, and RingCT. This makes receiving XMR fundamentally different from Bitcoin or USDT — standard "look at the address on the blockchain" approaches don't work here.

Cryptographic Foundation: Why Monero is Complex

Each Monero address consists of two key pairs: (public spend key, private spend key) and (public view key, private view key). The public address encodes both public parts.

Stealth addresses: the sender never transfers directly to your public address. They generate a one-time stealth address using your public view key and a random scalar. Only the owner of the private view key can compute that this is an incoming transaction for them.

Ring Signatures: each input in a transaction is signed by a ring consisting of a real UTXO and several decoys (15 by default in Monero since HF v15). An external observer cannot determine which ring participant is the true sender.

RingCT (Ring Confidential Transactions): transaction amounts are hidden using Pedersen commitments. Only sender and recipient know real amounts.

What This Means for Payment Acceptance

You can't simply look at the blockchain and see incoming payments — transactions to your address are only visible if you have the private view key. For monitoring incoming, you must either run a full node with monero-wallet-rpc, or use the view key on the server (which lets you see incoming but not spend).

Architecture: monero-wallet-rpc

The standard tool for integration is monero-wallet-rpc from the official Monero daemon. It provides a JSON-RPC interface for all wallet operations.

Node Deployment

First you need a synchronized monerod (Monero daemon). Blockchain size ~180 GB (pruned ~60 GB), sync from scratch — 12–48 hours depending on hardware.

# Run monerod with pruning
monerod --data-dir /var/lib/monero \
        --prune-blockchain \
        --db-sync-mode fast \
        --rpc-bind-ip 127.0.0.1 \
        --rpc-bind-port 18081 \
        --no-igd \
        --detach

# Run monero-wallet-rpc
monero-wallet-rpc \
  --daemon-address 127.0.0.1:18081 \
  --rpc-bind-port 18083 \
  --wallet-file /etc/monero/payment-wallet \
  --password-file /etc/monero/wallet.pass \
  --rpc-login payment_server:$(cat /etc/monero/rpc.pass) \
  --disable-rpc-login false \
  --trusted-daemon \
  --non-interactive

For production — separate wallet for each environment, wallet-rpc behind nginx with TLS, authentication via HTTP Basic.

Subaddresses: Correct Architecture for Payments

Monero supports subaddresses — derived addresses from the main wallet that are fully independent at the blockchain level. This is a key feature for payment processing.

Create a subaddress for each order:

import requests

RPC_URL = "http://127.0.0.1:18083/json_rpc"
AUTH = ("payment_server", "rpc_password")

def create_payment_address(order_id: str) -> dict:
    # Create new subaddress in account 0
    response = requests.post(RPC_URL, auth=AUTH, json={
        "jsonrpc": "2.0",
        "id": "0",
        "method": "create_address",
        "params": {
            "account_index": 0,
            "label": f"order_{order_id}"
        }
    })
    result = response.json()["result"]
    return {
        "address": result["address"],
        "address_index": result["address_index"]
    }

def check_incoming_transfers(min_amount_xmr: float) -> list:
    response = requests.post(RPC_URL, auth=AUTH, json={
        "jsonrpc": "2.0",
        "id": "0",
        "method": "get_transfers",
        "params": {
            "in": True,
            "pending": False,
            "min_height": 0  # can specify last checked block
        }
    })
    transfers = response.json()["result"].get("in", [])
    return [t for t in transfers if t["amount"] / 1e12 >= min_amount_xmr]

Why subaddresses are better than one address with payment_id: Payment ID (old method) — deprecated. Integrated addresses exist for backward compatibility but have privacy issues: they reveal that multiple transactions go to one recipient. Subaddresses look like independent addresses — better for privacy and de facto standard since 2018.

Confirmation Monitoring

Monero uses the concept of unlocked balance — funds become available after 10 confirmations (~20 minutes with average blocktime 2 minutes). For payment system:

def poll_payments(expected_payments: dict) -> None:
    """
    expected_payments: {address_index: {"order_id": str, "amount_xmr": float}}
    """
    response = requests.post(RPC_URL, auth=AUTH, json={
        "jsonrpc": "2.0",
        "id": "0",
        "method": "get_transfers",
        "params": {"in": True, "pending": True}
    })
    
    for transfer in response.json()["result"].get("in", []):
        addr_idx = transfer["subaddr_index"]["minor"]
        confirmations = transfer["confirmations"]
        amount_xmr = transfer["amount"] / 1e12  # atomic unit piconero = 1e-12 XMR
        
        if addr_idx in expected_payments:
            expected = expected_payments[addr_idx]
            if amount_xmr >= expected["amount_xmr"] * 0.99:  # 1% tolerance for rounding
                if confirmations >= 10:
                    mark_order_paid(expected["order_id"], amount_xmr)
                else:
                    update_order_status(expected["order_id"], "pending_confirmations", confirmations)

Sweep and Security

Private spend key must remain in an isolated environment. For automatic payouts — separate hot wallet with minimal balance. Main funds — in cold wallet, periodic manual sweep.

View-only wallet (public spend key + private view key only) can be safely kept on server for monitoring without risk of fund theft:

monero-wallet-cli --generate-from-view-key view-only-wallet \
  --address <main_address> \
  --viewkey <private_view_key>

What's Included

  • Deployment and synchronization of monerod (full node or pruned)
  • Configuration of monero-wallet-rpc with authentication and TLS
  • Subaddress-based payment flow implementation
  • Polling service for monitoring incoming transactions with confirmation logic
  • Sweep automation and hot/cold storage separation
  • Integration with existing payment system via webhook or callback