1How a payment works
There is no payment processor in the middle. Your shop shows the customer an address and an amount; the customer's wallet sends a transaction; the transaction sits in the mempool for a moment, then lands in a block; every block after that is one more confirmation. Your job is to notice the transaction, count its confirmations, and match it to an order.
You can watch in three ways, from simplest to fastest: ask your own node's wallet, ask the public address index at services.divi.domains, or subscribe to the node's ZMQ feed. This page does all three, and finishes with a small Python script you can drop into a shop backend.
Everything here was run on Divi testnet, where addresses start with x or y and coins are free from the faucet. On mainnet the commands are the same; only the addresses (D…), the ports and the money are real.
2One address per invoice
Never reuse an address between customers. If two orders share one address you cannot tell who paid. Ask the wallet for a fresh address when the invoice is created and store it with the order. The optional account name (IronDivi calls it a label) just groups the addresses in the wallet.
divi-cli getnewaddress invoice yFA5y6f2pKUSeUMT7Lk7da3pydX2sG1WqF divi-cli getnewaddress invoice xwXtKp85QcED8wCrCRLdHJfctMk458jUr9
Show the customer the address and the exact amount in DIVI. Keep the amount in satoshis in your database (1 DIVI = 100,000,000 satoshis) so you never compare floating-point numbers; the address index below reports satoshis too. Two invoices are used on this page: 50 DIVI to yFA5y6…G1WqF and 12.5 DIVI to xwXtKp…8jUr9.
Both divid (C++ Divi v3.0.0) and IronDivi have getnewaddress, getreceivedbyaddress, listreceivedbyaddress, listsinceblock and gettransaction, so the wallet steps below work on either. The ZMQ step needs divid.
3Watch from your own wallet
You need a node with a wallet for this step: Install divid and Configuration files cover testnet=1, server=1 and the RPC user and password. The node that made the address sees the payment first, in the mempool, before any block. The simplest check is getreceivedbyaddress, which adds up everything an address has received with at least minconf confirmations. Ask twice: once with 0 to see an unconfirmed payment, once with your real threshold.
# seconds after the customer paid 12.5: in the mempool, no block yet divi-cli getreceivedbyaddress xwXtKp85QcED8wCrCRLdHJfctMk458jUr9 0 12.50000000 divi-cli getreceivedbyaddress xwXtKp85QcED8wCrCRLdHJfctMk458jUr9 1 0.00000000 # about a minute later, after the next block divi-cli getreceivedbyaddress xwXtKp85QcED8wCrCRLdHJfctMk458jUr9 1 12.50000000
To see which transaction paid, and how many confirmations it has now, use listreceivedbyaddress (every address with its txids) or gettransaction with the txid:
divi-cli listreceivedbyaddress 1
[
{
"address": "yFA5y6f2pKUSeUMT7Lk7da3pydX2sG1WqF",
"account": "invoice",
"amount": 50.00000000,
"confirmations": 24,
"bcconfirmations": 24,
"txids": ["36a1bb249bec76e38e5b4d67bcc13131b60952b782c705aea8f09df024c0f5d5"]
},
…
]
divi-cli gettransaction 36a1bb249bec76e38e5b4d67bcc13131b60952b782c705aea8f09df024c0f5d5
{
"amount": 50.00000000,
"confirmations": 1,
"txid": "36a1bb2…c0f5d5",
"details": [
{ "account": "invoice", "address": "yFA5y6f2pKUSeUMT7Lk7da3pydX2sG1WqF",
"category": "receive", "amount": 50.00000000, "vout": 0 }
]
}
If you would rather be told what is new since you last looked, listsinceblock takes the block hash you saved last time and returns every wallet transaction since, plus a lastblock hash to save for next time. The second argument is the number of confirmations you want lastblock to be behind the tip, so a short reorg cannot make you miss one.
divi-cli listsinceblock cd497a17a5355d44847ca5cc79162d4289f24fdb9cdbd2ac163e87463cfc5512 1
{
"transactions": [
{ "account": "invoice", "address": "yFA5y6f2pKUSeUMT7Lk7da3pydX2sG1WqF",
"category": "receive", "amount": 50.00000000, "confirmations": 1, … },
{ "category": "move", "amount": 12.50000000, "confirmations": 0, … }
],
"lastblock": "…"
}
On this test both invoices lived in the same wallet that paid them. A payment from a wallet to one of its own addresses is reported as category: "move" with no address. A real customer pays from their own wallet, and you get category: "receive" with the address, like the 50 DIVI above. getreceivedbyaddress counts both the same way.
4Watch from the public address index
If your shop has no node of its own (or you want a second opinion), the public testnet endpoint https://services.divi.domains/api/testnet/rpc/ runs a node with the address index on. It needs no password and only answers read-only methods; Talk to a Divi node from Python and JavaScript explains the endpoint. Three methods matter here:
| Method | Returns |
|---|---|
getaddressdeltas | Every change to the address: txid, block height, output index, satoshis |
getaddressbalance | balance and received, in satoshis |
getaddresstxids | Just the txids |
curl -s https://services.divi.domains/api/testnet/rpc/ \
-H 'Content-Type: application/json' \
-d '{"method":"getaddressdeltas","params":[{"addresses":["xwXtKp85QcED8wCrCRLdHJfctMk458jUr9"]}]}'
While the 12.5 payment was only in the mempool, the index returned nothing at all:
{"error":null,"id":null,"result":[]}
# getaddressbalance at the same moment
{"error":null,"id":null,"result":{"balance":0,"received":0}}
One block later it was there, with the height, so you can count confirmations as getblockcount − height + 1:
{"error":null,"id":null,"result":[{"address":"xwXtKp85QcED8wCrCRLdHJfctMk458jUr9",
"blockindex":3,"height":359949,"index":0,"satoshis":1250000000,
"txid":"39f86883f742c3acdbca2c0934a1e5184008233f03c3ff287ec97b2537a481c6"}]}
# getaddresstxids
{"error":null,"id":null,"result":["39f86883f742c3acdbca2c0934a1e5184008233f03c3ff287ec97b2537a481c6"]}
The address index is built from blocks, so an unconfirmed payment is invisible to it. Divi v3.0.0 has no getaddressmempool (the node answers Method not found, and the public endpoint answers Method not allowed). If you need to show "payment seen, waiting for a block", use your own wallet (step 3) or ZMQ (step 5).
5Hear about it instantly with ZMQ
Polling every 15 seconds is fine for a shop. If you want the payment the moment it reaches your node, divid can publish every new transaction hash and block hash over ZeroMQ. When the 50 DIVI payment above was broadcast, a subscriber on the node printed its hash twice: once as it entered the mempool and again when the block that holds it arrived:
hashtx 36a1bb249bec76e38e5b4d67bcc13131b60952b782c705aea8f09df024c0f5d5 #721914 … hashtx 36a1bb249bec76e38e5b4d67bcc13131b60952b782c705aea8f09df024c0f5d5 #721921 hashblock cd497a17a5355d44847ca5cc79162d4289f24fdb9cdbd2ac163e87463cfc5512 #369
On each hashtx, call gettransaction with the hash; if it is not a wallet transaction the node returns an error and you ignore it. On each hashblock, re-count confirmations for your open invoices. Setup, the message format and Python and Node subscribers are in React to new blocks and transactions with ZMQ. IronDivi has no ZMQ; use polling there.
6How many confirmations to wait for
A transaction in the mempool can still be dropped or replaced, and the newest block can, rarely, be replaced by a competing one (a reorg), which pushes its transactions back into the mempool. Each confirmation makes that less likely. Divi makes a block about every minute, so waiting costs the customer little:
| Confirmations | Roughly | Good for |
|---|---|---|
| 0 | seconds | Showing "payment seen" on the checkout page. Never release goods. |
| 1 | 1 minute | Digital goods of small value |
| 6 | 6 minutes | A sensible default for anything you cannot take back |
| more | minutes | Large amounts; the cost is only the customer's time |
Whatever number you pick, keep counting until the order is marked paid: ask with getreceivedbyaddress <address> <N> and only act when the amount is there with N confirmations. Do not mark an order paid on the 0-confirmation answer and forget about it.
Also accept over-payment (compare with >=) and decide what to do with under-payment; wallets round, and customers mistype. Keep the txid with the order so support can find it in Explorer 2.0: the 50 DIVI payment and the 12.5 DIVI payment from this page.
7A script that waits for the payment
This Python 3 script uses only the standard library. It takes an invoice address and amount, asks either your own node (getreceivedbyaddress) or the public index (getaddressdeltas plus getblockcount), prints what it sees, and exits with PAID once the amount is there with enough confirmations. The node's RPC user and password come from the environment, never from the command line.
# wait_for_payment.py import argparse, base64, json, os, sys, time, urllib.request PUBLIC = "https://services.divi.domains/api/testnet/rpc/" SATOSHI = 100_000_000 def rpc(url, method, params, user=None, password=None): body = json.dumps({"jsonrpc": "1.0", "id": "shop", "method": method, "params": params}).encode() req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json"}) if user: token = base64.b64encode(f"{user}:{password}".encode()).decode() req.add_header("Authorization", "Basic " + token) try: with urllib.request.urlopen(req, timeout=30) as r: reply = json.load(r) except urllib.error.HTTPError as e: # divid answers HTTP 500 with a JSON body on errors reply = json.load(e) if reply.get("error"): raise RuntimeError(reply["error"]) return reply["result"] def received_on_node(url, user, password, address, minconf): return float(rpc(url, "getreceivedbyaddress", [address, minconf], user, password)) def received_on_public(address, minconf): deltas = rpc(PUBLIC, "getaddressdeltas", [{"addresses": [address]}]) tip = rpc(PUBLIC, "getblockcount", []) total = 0 for d in deltas: confirmations = tip - d["height"] + 1 if d["satoshis"] > 0 and confirmations >= minconf: total += d["satoshis"] return total / SATOSHI def main(): ap = argparse.ArgumentParser() ap.add_argument("address") ap.add_argument("amount", type=float, help="invoice amount in DIVI") ap.add_argument("--confirmations", type=int, default=6) ap.add_argument("--source", choices=["node", "public"], default="node") ap.add_argument("--every", type=int, default=15, help="seconds between checks") a = ap.parse_args() url = os.environ.get("DIVI_RPC_URL", "http://127.0.0.1:51475/") user, password = os.environ.get("DIVI_RPC_USER"), os.environ.get("DIVI_RPC_PASSWORD") while True: if a.source == "node": seen = received_on_node(url, user, password, a.address, 0) paid = received_on_node(url, user, password, a.address, a.confirmations) else: seen = received_on_public(a.address, 1) paid = received_on_public(a.address, a.confirmations) print(f"{time.strftime('%H:%M:%S')} seen {seen:.8f} confirmed {paid:.8f} need {a.amount:.8f}", flush=True) if paid + 1e-9 >= a.amount: print("PAID") return 0 time.sleep(a.every) if __name__ == "__main__": sys.exit(main())
Run it against the public index for the first invoice, and against your own testnet node for the second (the node's testnet RPC port is 51475 by default; set DIVI_RPC_URL if yours differs):
python3 wait_for_payment.py yFA5y6f2pKUSeUMT7Lk7da3pydX2sG1WqF 50 --source public 20:23:54 seen 50.00000000 confirmed 50.00000000 need 50.00000000 PAID # set these from your secrets store, not by typing them into a shell that keeps history export DIVI_RPC_USER=your-rpcuser DIVI_RPC_PASSWORD=your-rpcpassword python3 wait_for_payment.py xwXtKp85QcED8wCrCRLdHJfctMk458jUr9 12.5 --confirmations 3 20:23:54 seen 12.50000000 confirmed 12.50000000 need 12.50000000 PAID
In a real shop, run one check loop over all open invoices instead of one process per invoice, and store the result with the order. If you use IronDivi, point DIVI_RPC_URL at its port (51473 on testnet); it answers the same getreceivedbyaddress call, with HTTP 200 even on errors, which the script also handles.
8Keep the money safe
- The shop's node only receives. Its wallet holds the keys to every invoice address, so treat the machine as a hot wallet: keep the balance small and sweep it to a wallet that is not on a web server at a fixed interval (a plain
sendtoaddressfrom the node to your cold address). - Never expose RPC to the internet. Leave
rpcallowipat its default (local only) and let the web backend talk to the node over localhost or a private network. The public index at services.divi.domains exists so that code outside your network never needs your node's RPC. - Keep the RPC password out of code and command lines. Read it from the environment or a secrets store, as the script does. On a shared machine, arguments show up in the process list.
- Back up the wallet file after you start handing out addresses. A lost wallet is lost money, even if the index still shows the payments.
- Lock an encrypted wallet. Receiving needs no passphrase; only the sweep does. Use
walletpassphrasewith a short timeout for that step and nothing else.
If something goes wrong
| You see | What it means |
|---|---|
getreceivedbyaddress … 0 shows the amount but … 1 shows 0 | The payment is in the mempool and no block has included it yet. Wait a minute and ask again. |
getaddressdeltas returns [] while the wallet already shows the payment | Normal: the index only sees confirmed transactions. It fills in with the next block. |
{"code":-32601,"message":"Method not allowed: getaddressmempool"} | The public endpoint only answers its allowed read-only methods, and this one does not exist in Divi v3.0.0 anyway. |
gettransaction says Invalid or non-wallet transaction id | The txid is not in this wallet. Either it pays someone else's address, or the node was restored without the address. Use getrawtransaction <txid> 1 on a node with txindex=1, or the public endpoint, to inspect it. |
category: "move" instead of "receive" | The paying wallet is the receiving wallet. Fine for testing; a customer's payment shows as receive. |
| Confirmations went down, or a payment disappeared | A reorg replaced the newest block. Keep polling; the transaction normally returns in the next block. This is why orders wait for more than 1 confirmation. |
Next: Talk to a Divi node from Python and JavaScript for the RPC basics, React to new blocks and transactions with ZMQ for the push feed, and Raw transactions and multisignature if your sweep address should need two keys. Still stuck? Ask the Divi chatbot, or see all tutorials.