Detect a payment
Every Dogecoin store, donation page, game, and bot is, at its core, the same small machine: issue a unique address, watch it, credit exactly once when enough confirmations arrive. Build this pattern once, properly, and you can bolt anything on top of it. This is the most important lesson on the site.
The invoice pattern
Never ask two customers to pay the same address – you'd have no reliable way to tell their payments apart. Instead:
- Create an invoice: generate a fresh address with
getnewaddress, store (invoice id, address, amount due, status). - Show it to the payer (address string + a QR code).
- Watch the address until the received amount at your required confirmation depth covers the amount due.
- Credit exactly once, then stop watching.
The RPC that makes this trivial is getreceivedbyaddress ADDRESS MINCONF – the total ever received by that address, counting only payments with at least MINCONF confirmations. Because each address belongs to exactly one invoice, "how much has invoice 42 been paid?" is one call.
A complete, honest implementation
Small enough to read in one sitting, correct enough to grow. SQLite keeps state so a restart never double-credits:
import sqlite3, time
from quickstart import DogeRPC # the wrapper from lesson 5
rpc = DogeRPC("dogeuser", "CHANGE_ME_long_random_string")
CONFIRMATIONS = 2 # tips/small goods; use 6+ for real orders
db = sqlite3.connect("invoices.db")
db.execute("""CREATE TABLE IF NOT EXISTS invoices(
id INTEGER PRIMARY KEY,
address TEXT UNIQUE,
due REAL,
status TEXT DEFAULT 'pending')""") # pending -> paid
def create_invoice(amount_doge):
addr = rpc.call("getnewaddress")
db.execute("INSERT INTO invoices(address, due) VALUES(?,?)",
(addr, amount_doge))
db.commit()
print(f"invoice: send {amount_doge} DOGE to {addr}")
return addr
def check_pending():
rows = db.execute(
"SELECT id, address, due FROM invoices WHERE status='pending'").fetchall()
for inv_id, addr, due in rows:
received = rpc.call("getreceivedbyaddress", addr, CONFIRMATIONS)
if received >= due:
# status flip is the idempotency lock: WHERE status='pending'
# means even a concurrent double-run credits at most once
cur = db.execute(
"UPDATE invoices SET status='paid' "
"WHERE id=? AND status='pending'", (inv_id,))
db.commit()
if cur.rowcount:
fulfill(inv_id, received) # your business logic goes here
def fulfill(inv_id, amount):
print(f"invoice {inv_id} PAID ({amount} DOGE) - ship the goods!")
if __name__ == "__main__":
create_invoice(50.0)
while True: # poll loop: fine at small scale
check_pending()
time.sleep(15)
Fund the printed address from your other wallet (or a faucet) and watch it flip to PAID two confirmations later. That moment is the whole business.
The three rules that make it production-grade
- Idempotency: the conditional
UPDATE ... WHERE status='pending'guarantees one credit even if two workers race or the process restarts mid-check. Never fulfill outside that guard. - Underpayment and overpayment: humans send 49.9 or 55 against a 50 invoice. Decide your policy up front – typical: credit when
received >= due, treat small shortfalls as unpaid with a "top up" UI, and log overpayments for manual refund. - Expiry: add an
expires_atcolumn and stop watching stale invoices, or your poll loop grows forever. (An expired-then-paid invoice should alert a human, not auto-ship.)
When polling isn't enough: walletnotify
Polling every 15 seconds is perfectly fine for tens of open invoices. When you outgrow it, Dogecoin Core will push instead: add walletnotify to dogecoin.conf and the node runs your command the moment a wallet transaction appears and again at first confirmation:
# dogecoin.conf
walletnotify=/usr/local/bin/notify-payment.sh %s # %s = txid
Your script can enqueue the txid (write to a file, hit a local webhook) and your app checks just that transaction with gettransaction. Same state machine, event-driven trigger. And if you'd rather not build any of this plumbing, that's exactly the job GigaWallet was written to do.
Key takeaways
- One fresh address per invoice makes
getreceivedbyaddress addr minconfyour entire detection API. - Persist invoice state; flip
pending → paidwith a conditional update – that's your double-credit lock. - Decide under/overpayment policy and invoice expiry before launch, not after the first weird payment.
- Poll first,
walletnotifywhen you scale, GigaWallet when you'd rather not own the plumbing.