Shipping Receipt Binding — Why Method, Path, and Expiry Matter
A developer named Swapnoneel Saha left a comment on our x402 tutorial with three words that changed how we think about receipts: "bind to method, path, and expiry." He was right. Here's what we shipped.
The Gap We Missed
Our receipt system already had HMAC-SHA256 signing over canonical receipt fields. Every receipt carried a cryptographic signature that could be verified independently. Replay protection existed at the database level — duplicate receipt IDs were rejected.
But there was a subtle gap: a receipt generated for GET /x402/token-security?address=0xABC would pass verification if replayed against GET /x402/gas. The HMAC matched because the signing string didn't include which endpoint the receipt was for.
In other words: we were signing what happened (amount, timestamp) but not where it happened (method, path).
What We Added
Three new fields in the canonical signing string:
| Field | Purpose | Example |
|---|---|---|
method | HTTP verb — prevents cross-method replay | GET, POST |
path | Request path — binds receipt to specific endpoint | /x402/x402-gas |
expires | Short TTL — rejects stale receipts | 2026-08-09T12:41:57Z (5 min) |
The updated canonical function now looks like this:
func canonical(r *store.Receipt) string {
return fmt.Sprintf("id:%s\ntype:%s\nservice_id:%s\nmethod:%s\npath:%s\nagent:%s\namount_cents:%.0f\ncurrency:%s\ntx_hash:%s\ntimestamp:%s\nexpires:%s",
r.ID, r.Type, r.ServiceID, r.Method, r.Path, r.Agent,
r.AmountCents, r.Currency, r.TxHash, r.Timestamp, r.Expires)
}
And VerifyReceipt() now enforces expiry:
func VerifyReceipt(r *store.Receipt) bool {
if r.Hmac == "" { return false }
// Reject stale receipts
if r.Expires != "" {
exp, err := time.Parse(time.RFC3339, r.Expires)
if err == nil && time.Now().UTC().After(exp) {
return false
}
}
expected := signReceipt(r)
return hmac.Equal([]byte(expected), []byte(r.Hmac))
}
Live Receipt Example
Here's a real receipt from a trial call to /x402/time, showing the new fields in production:
The Failure Table That Comes Next
Method/path/expiry binding is defense-in-depth against receipt replay. But Swapnoneel's comment pointed to something bigger: a failure table that models every state an agent payment can be in, and what the system does about it.
| State | Trigger | System Action |
|---|---|---|
AWAITING_SETTLEMENT | Payment submitted, tx pending | Poll facilitator, timeout → refund |
SETTLED_NOT_DELIVERED | Paid but response never returned | Retry with same receipt → idempotent replay |
DUPLICATE_RETRY | Agent retries with same proof | Return cached response, don't charge again |
RECEIPT_STALE | Receipt timestamp > TTL | Reject, force fresh payment |
The principle: an agent should never need to reason about "did my payment go through?" The server guarantees exactly-once delivery semantics, and the receipt is the cryptographic proof. This is the difference between "agents can pay" and "agents can pay safely."
Why This Matters Beyond Security
Every x402 discussion starts and ends with "the 402 response has an invoice." But the invoice is only step 1. Steps 2 through 10 — settlement, verification, receipt binding, expiry, failure recovery, dispute resolution — are where production readiness actually lives.
The Cloudflare x402 Foundation, the Coinbase Agent Payment Stack, the OSL AgentPay — all of these are building the payment rails. The receipt and accountability layer is the track that keeps trains from colliding on those rails.
If you're building on x402, here's the minimum you should add to your receipt system today:
- Bind to method + path — prevents cross-endpoint replay
- Short expiry (5 min) — rejects stale receipts at verification time
- DB-level uniqueness — catches duplicate receipt IDs even if HMAC matches
These three checks together mean an attacker needs to: forge the HMAC and match the method and match the path and use a fresh receipt — all within 5 minutes. That's not impossible, but it raises the bar from "trivially replayable" to "cryptographically infeasible."
Thanks to Swapnoneel Saha for the detailed technical feedback that prompted these improvements. The best security insights come from developers who read your code with fresh eyes.
minia2a is an open marketplace where AI agents discover, call, and pay for APIs in USDC. 326 services, 15 free trials per endpoint. minia2a.uk