A merchant accepting KAS faces a problem Bitcoin solved years ago — and a harder one Bitcoin never had. Kaspa transactions carry no memo field, no OP_RETURN, no way to embed an order reference inside the transfer itself. At the same time, Kaspa's BlockDAG produces blocks every 100 ms, which means "confirmation" does not map to Bitcoin's linear chain of block heights. A payment system for Kaspa must solve two problems at once: attribution (which invoice does this payment belong to?) and finality (when is it safe to ship the goods?).
This article traces the full path of a KAS payment — from the moment a unique address is derived to the moment a signed webhook fires a business event — using a real production system as the reference. The design is general enough to apply to any Kaspa merchant integration.
The Attribution Problem: One Address Per Invoice
In Bitcoin, merchants sometimes reuse a single address and distinguish payments by OP_RETURN data or amount. Kaspa has no such field. The clean solution is HD (hierarchical deterministic) address derivation: generate a unique deposit address for every invoice from a single extended public key.
The flow:
1. The merchant holds an xpub (extended public key). The corresponding private seed stays offline or in a separate secure environment — the payment server never sees it. 2. For each new order, the server calls a derivation function with the next sequential index:
address_0 = derive(xpub, index=0) → kaspa:qz...
address_1 = derive(xpub, index=1) → kaspa:qp...
address_2 = derive(xpub, index=2) → kaspa:qr...
3. The index is stored alongside the order. Because each index produces a unique address, any UTXO appearing on that address is unambiguously payment for that specific order.
This is the same family of derivation used by Bitcoin HD wallets (BIP-32/44), adapted for Kaspa's address format. The critical property: the server can generate addresses but cannot spend received funds, because it never holds the private key. This is non-custodial by construction.
xpub (extended public key): A public key from which child public keys and addresses can be derived in a deterministic sequence. The corresponding private key is not required for derivation, so a payment server can generate unlimited deposit addresses without ever holding spending authority.
Watching the BlockDAG: Polling vs. Subscribing
Once an address is issued, the system needs to detect incoming payments. Two approaches exist:
- WebSocket subscription to transaction events — reactive, low-latency, but fragile on reconnects. If the socket drops and a transaction arrives during the gap, it can be missed unless the watcher has a recovery path.
- Polling
getUtxosByAddresses— the watcher asks the node for current UTXOs on a set of addresses at a regular interval (every few seconds). This is stateless and idempotent: even if a poll is missed, the next one returns the full current picture.
The production design uses polling. The watcher maintains a list of active addresses (the "watchlist"), each tagged with an order ID, expected amount, and expiry time. Every cycle, it queries the node's utxoindex — an index the Kaspa node maintains of all unspent transaction outputs keyed by address. The utxoindex must be enabled on the node; it is not on by default in all configurations.
# Pseudocode: watcher loop
for entry in watchlist:
utxos = node.getUtxosByAddresses([entry.address])
received = sum(utxo.amount for utxo in utxos)
confirmations = current_blue_score - utxo.block_blue_score
if received >= entry.expected and confirmations >= THRESHOLD:
fire_callback(entry)
The trade-off is latency: polling every N seconds means detection is at worst N seconds behind the actual BlockDAG event. For a system processing invoices with 20-minute windows, this is acceptable. For a real-time exchange, a hybrid approach (subscription plus periodic reconciliation) would be more appropriate.
What "Confirmation" Means in a BlockDAG
This is where Kaspa diverges fundamentally from Bitcoin.
In Bitcoin, a transaction is confirmed when it appears in a block, and each subsequent block adds one confirmation. The chain is linear; depth is unambiguous.
In Kaspa's GHOSTDAG protocol, blocks form a directed acyclic graph (DAG), not a single chain. Each block has a selected parent (chosen by the GHOSTDAG algorithm), and the sequence of selected parents forms the "selected chain." But many other blocks exist in parallel — these are merge blocks that the selected chain eventually absorbs.
The Kaspa wiki defines two key metrics:
- Blue score: the count of *blue* (well-connected) blocks in a block's past. A block is "blue" if it is well-connected within the DAG — it contributes to consensus security. Blue score is the DAG-native analog of Bitcoin's block height.
- DAA score: blue score plus the count of red (poorly-connected) blocks that were successfully merged and rewarded. This controls the emission schedule.
For Kaspa payment confirmations, the relevant metric is blue score. When a transaction's UTXO appears in a block at blue score B, and the current virtual (tip) blue score is V, the number of confirmations is V - B. The system waits until this difference reaches a configurable threshold before considering the payment final.
Why blue score and not total block count? Because blue blocks are the ones the GHOSTDAG algorithm considers well-connected and reliable. Red blocks — those that arrived late or were poorly propagated — are merged but carry less consensus weight. Counting only blue blocks for confirmation gives a more accurate measure of how deeply the transaction is buried in the DAG's consensus structure.
At Kaspa's current block rate of 10 blocks per second, even a modest confirmation threshold of 10–30 blue-score confirmations resolves in 1–3 seconds. This is dramatically faster than Bitcoin's ~10-minute blocks, but the threshold must still be nonzero to guard against the small DAG reorganizations that GHOSTDAG permits.
DAG reorganization: Unlike Bitcoin's rare chain reorgs, Kaspa's BlockDAG experiences frequent small reorgs where the selected tip changes. GHOSTDAG guarantees these are shallow and stabilize quickly, which is why a small blue-score confirmation threshold is sufficient for payment finality.
The Webhook: From Detection to Business Event
Once the watcher determines a Kaspa payment confirmation has been reached, it must notify the business layer. This is the KAS payment webhook — an HTTP callback carrying the payment details.
The webhook payload includes:
{
"order_id": "ord_abc123",
"address": "kaspa:qz...",
"txid": "7342e267...",
"received_sompi": 199000000000,
"daa_score": 12345678
}
Three design decisions make this reliable:
1. HMAC signature. The webhook includes a cryptographic signature (HMAC-SHA256) over the payload, using a shared secret known only to the watcher and the business server. The receiver verifies the signature before processing. This prevents spoofed callbacks from triggering false fulfillments.
2. Idempotency by order ID. The callback endpoint is designed so that receiving the same order_id twice produces the same result — the second call returns a dedup: true response and does not issue a second license or send a second email. This matters because in a dual-instance deployment (see below), both watchers may detect the same payment independently.
3. User-Agent identification. A practical lesson: some reverse proxies and CDNs block requests with default library User-Agent strings. The webhook sender sets a custom User-Agent header to avoid silent 403 rejections at the network edge.
Dual-Instance Resilience
A single watcher instance creates a single point of failure. If the machine hosting the watcher goes offline, payments still arrive on-chain but no business events fire until recovery.
The solution is active-active dual instances with partitioned derivation indices:
Instance A: derives addresses at odd indices (1, 3, 5, 7, ...)
Instance B: derives addresses at even indices (0, 2, 4, 6, ...)
Both instances share the same xpub but use a stride and offset to ensure they never generate the same address. Both register their addresses in the business layer's watchlist. If either instance dies, the other continues processing its own addresses without interruption.
The business layer's webhook endpoint is idempotent by order ID, so if both instances happen to detect and report the same edge-case payment, the duplicate is harmlessly absorbed.
This pattern — partitioned indices, active-active watchers, idempotent callbacks — turns a fragile single-watcher setup into a resilient payment rail without requiring leader election or shared state between instances.
Partial Payments and Overpayments
Kaspa transactions have no "invoice amount" field. The customer sees an address and an expected amount, but nothing enforces that they send exactly that amount. The system must handle three cases:
- Exact payment:
received ≥ expected(within a small tolerance for rounding) → confirm and fulfill. - Underpayment:
received < expected→ report the shortfall. The customer can send a second transaction to the same address. The watcher sums all UTXOs on the address, so multi-transaction payments work naturally. - Overpayment:
received > expected→ fulfill the order. The excess is not refunded automatically (Kaspa transactions are irreversible; refunds require manual intervention).
The tolerance threshold accounts for minor rounding differences between the displayed KAS amount and the actual sompi (the smallest unit) the customer's wallet sends.
How Kaspa Forge Uses This Pattern
The payment confirmation infrastructure described above powers Kaspa Forge's own sales pipeline — accepting KAS for products alongside traditional payment channels. The architecture follows the same non-custodial philosophy that runs through every Kaspa Forge product:
- Kaspa Safe uses on-chain covenant contracts where the user's keys never leave their device.
- Kaspa Escrow locks funds in an on-chain contract rather than with a custodial intermediary.
- Deposit applies the same covenant-based collateral pattern.
- Desk keeps all keys in an encrypted browser profile; the server never sees them.
The payment system extends this principle to the merchant layer: the server holds only an xpub, derives addresses, watches the BlockDAG, and fires webhooks. At no point does it control the received funds. If the payment server disappears, the funds remain spendable by whoever holds the seed. The same two-service architecture — a thin business server and a crypto-aware gateway — keeps the compilable Rust wheel and all key material isolated from the general application stack.
The same non-custodial, own-node architecture that powers Kaspa payment confirmations underpins every Kaspa Forge product — from Safe covenant vaults to Escrow P2P deals. Explore the full architecture to see how on-chain contracts replace custodial trust at every layer.
Trade-offs and Honest Limitations
No design is without compromises. Here are the real ones:
Polling latency. The watcher polls every few seconds, not on every block. Detection lags behind the BlockDAG by up to the poll interval. For invoice-style payments with 20-minute windows, this is fine. For high-frequency trading or instant retail point-of-sale, it is not.
No push from the node. The Kaspa node's wRPC interface supports subscriptions, but the production system avoids them because reconnection handling adds complexity and the risk of missed events during socket drops. Polling is simpler and self-healing, at the cost of latency.
Manual refunds. Kaspa transactions are irreversible. If a customer overpays or pays an expired invoice, the system flags it for manual resolution. There is no on-chain refund mechanism.
Rate volatility. The KAS/USD rate is locked at order creation with a 2% buffer. If Kaspa's price moves more than 2% during the confirmation window, the merchant absorbs the difference. This is a conscious trade-off: a larger buffer would overcharge customers; a smaller one would expose the merchant to risk.
Address quarantine after expiry. An expired invoice's address is placed in quarantine and never reused. This prevents confusion if a late payment arrives on an address that has been reassigned to a new order. Late payments are still detected and flagged for manual handling.
utxoindex dependency. The entire watcher depends on the node's utxoindex being enabled and fully synced. During initial block download (IBD), the index is not available, so the payment system cannot go live until the node is fully caught up with the network.
Summary: The Full Lifecycle
Customer clicks "Pay with KAS"
→ Server derives unique address from xpub (next index)
→ Locks KAS/USD rate, sets 20-min expiry
→ Returns address + amount + QR code (kaspa: URI)
→ Customer sends KAS from their wallet
Watcher polls getUtxosByAddresses every N seconds
→ Detects UTXO on the invoice address
→ Sums received amount across all UTXOs
→ Checks blue-score confirmation threshold
→ If received ≥ expected AND confirmations ≥ threshold:
→ Fires HMAC-signed POST webhook to business server
→ Business server verifies signature
→ Issues license / fulfills order (idempotent by order_id)
The entire flow — from HD address derivation through BlockDAG observation to idempotent webhook — is a general pattern for accepting Kaspa payments without custodial risk. The BlockDAG's high block rate makes confirmations fast; the GHOSTDAG blue-score metric makes them meaningful; and the HD derivation pattern solves attribution without any in-transaction metadata. For Kaspa merchant payments, this is the production-grade foundation: no third-party processors, no custodial risk, no protocol-level hacks — just a node, a watcher, and a signed callback.
FAQ
How many confirmations does a Kaspa payment need?
The threshold is configurable per deployment. At Kaspa's 10 blocks per second, even 10–30 blue-score confirmations resolve in 1–3 seconds. The exact number depends on your risk tolerance.
What is a blue score and why does it matter for payments?
Blue score counts the number of blue (well-connected) blocks in a block's past within the GHOSTDAG structure. It replaces Bitcoin's linear block height as the confirmation metric in Kaspa's BlockDAG.
What happens if a customer sends less than the invoice amount?
The system detects partial payment, reports the shortfall, and waits for a top-up to the same address. The watcher sums all UTXOs on the address, so multi-transaction payments work naturally.
Does Kaspa support payment memos or OP_RETURN?
No. Kaspa transactions carry no memo field. The standard approach is to derive a unique address per invoice from an extended public key (xpub), so any payment to that address is unambiguously attributed.
Can I build a similar confirmation webhook for my own project?
Yes. The pattern — xpub derivation, UTXO polling via utxoindex, blue-score threshold, HMAC-signed callback — is a general design. You need a Kaspa node with utxoindex enabled and a small watcher service.
Is the payment system custodial?
No. Only the extended public key (xpub) is held by the server. The private seed never leaves the operator's secure environment. The server derives addresses but cannot spend received funds.
Put your KAS where theft can be cancelled
A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.
Create a vault
