Detect a payment

Lesson 7 of 11 25 min read Your First Dogecoin App

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:

  1. Create an invoice: generate a fresh address with getnewaddress, store (invoice id, address, amount due, status).
  2. Show it to the payer (address string + a QR code).
  3. Watch the address until the received amount at your required confirmation depth covers the amount due.
  4. 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_at column 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 minconf your entire detection API.
  • Persist invoice state; flip pending → paid with 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, walletnotify when you scale, GigaWallet when you'd rather not own the plumbing.
On the map

Built something on Dogecoin? Put it on the map.

The directory is the page people link when someone says Dogecoin has no developers. If you maintain a library, wallet, tool, or app – even a small one – it belongs here.