Prove you were paid in Monero — without an explorer, a full node, or your spend key
By Clara Oswald · part of the XMR Income Engine toolkit · MIT-licensed tooling
You sold a digital good for Monero. The buyer says "sent it." Now what? You want proof — privately, without pasting your address into some block explorer that logs your IP and ties it to your identity. And you want to do it without downloading the entire blockchain or ever touching the key that can actually move your money.
This is the receive side of a zero-server Monero commerce flow, and it is simpler than the fear around "cryptography" suggests. Here is how it works, and a build-along you can run against a public node in a couple of minutes.
What a private view key actually grants (and what it doesn't)
Your Monero wallet has two private keys: a spend key (moves funds) and a view key (a 32-byte scalar). The view key is the interesting one for receipts:
- With the view key you can see incoming outputs destined to your address, decrypt their amounts, and read the one-time public keys of senders.
- You cannot spend. The view key reveals a read-only bank statement, nothing more.
- You cannot see outgoing spends or derive your balance without scanning the chain (and even then you only see what arrived).
Treat the private view key like a read-only statement key. Sharing it does not compromise your funds — but a node you scan against will learn your address + view key, so choose it deliberately (more on that below).
Architecture
The whole job is: build a watch-only wallet from your address + view key, point it at any reachable node, let it scan, and read the incoming list. No backend, no database, no PII stored anywhere.
Build-along: prove a payment arrived
You need monero-wallet-rpc (ships with every Monero release) and a view-only
wallet file. The tool monero-receive-watch.py drives it; it is standard-library
Python only — no third-party packages.
1. Start the wallet RPC against a public node, in its own scratch dir:
monero-wallet-rpc --wallet-dir ./wallets \
--rpc-bind-port 18085 --disable-rpc-login \
--daemon-address http://xmr-node.cakewallet.com:18081
2. Watch an address with the view key only (spend key is never supplied):
python3 monero_receive_watch.py \
--address 45bovHGLsAgihEWXgjoTwuBaUq1LogHCjeBYBN3xfN4J7gspatfQtb2WsyfsqX4dqMWUPXAEYFKV88zf9BpDFMM3HjnA7MC \
--view-key <YOUR_PRIVATE_VIEW_KEY> \
--daemon http://xmr-node.cakewallet.com:18081 \
--restore-height 3745000
The --restore-height tells the wallet where to start scanning. Set it near when
you expect payments so the first scan is fast — you do not need to walk the whole chain.
3. Read it. Output (machine, with --json) is a JSON doc with
count, total_confirmed_xmr, and the full transfers array —
easy to pipe into a bot or a "payment received" webhook. Human output is a table:
CONF AMOUNT (XMR) HEIGHT TXID / SUBADDR
----------------------------------------------------------------------
12 3.000000000000 3750116 aaaa… sub0/1
----------------------------------------------------------------------
TOTAL CONFIRMED: 3.000000000000 XMR (1 transfer)
Zero incoming is an honest "not paid yet." Any row is proof the funds landed.
Why there is no backend
Verification runs entirely client-side against a node you choose. The merchant proves payment to themselves; to prove it to the buyer, share the txid (public information) — the buyer can confirm it on any explorer or with their own view wallet. No server, no database, no stored PII, nothing to get hacked.
Where it sits in the stack
- Address verifier — checks an address is valid offline (no node, no explorer).
- Invoice / QR builder — emits an offline
monero:URI + scannable QR. - Receive-watch — proves the payment actually arrived (this article).
- Price-peg — converts USD → XMR at build time so prices stay sane.
Together these four make a complete zero-server Monero commerce flow: advertise a price, hand over a QR, and confirm the money landed — all without custodians, chargebacks, or KYC.
Honest caveats
- Untrusted node: the node sees your view key + address. It cannot move funds, but a malicious node could withhold or fake transfer data. Cross-check high-value payments against a second node, or run your own.
- Confirmations: wait for a sensible number of confirmations (e.g. 6–10) before shipping. Unconfirmed ≠ received.
- Amount visibility: scanning reveals incoming amounts to the node you use. If that matters, run your own node or accept the trade-off.
Tip jar — if this helped
Monero tips land privately, straight to a wallet only I hold (no exchange, no KYC):
Payment URI: monero:45bovHGLsAgihEWXgjoTwuBaUq1LogHCjeBYBN3xfN4J7gspatfQtb2WsyfsqX4dqMWUPXAEYFKV88zf9BpDFMM3HjnA7MC
The receive-watch tool is MIT-licensed and lives in the
monero-receive-watch bundle. The address above is the project's own and is
validated mainnet-standard. Monero payments are irreversible — always confirm the address
matches exactly before sending.