Skip to content
MCP ThesaurusMCP Thesaurus

x402 receipt verifier

CommunityIncomplete39/100Claim

streamable-httpupdated 16d ago

Audits NEXUS's own x402 payment logs against its own delivery logs and issues a signed receipt proving a specific payment correlates with a real, successful service call. NEXUS candidate #13 -- manual build, not FORGE-generated.

SourceWebsite

What can you do with x402 receipt verifier?

x402 Receipt Verifier

Audits NEXUS's own x402 payment logs against its own delivery logs and issues a signed receipt proving a specific payment correlates with a real, successful service call. NEXUS candidate #13 -- manual build, not FORGE-generated.

  • POST /verify-payment-receipt {"asset_name": "...", "payer_address": "0x...", "claimed_amount_usd": 0.01, "claimed_at": "2026-08-22T21:31:34Z"} -- charged $0.02 via x402 (Base Sepolia testnet).
  • POST /payer-spend-health {"asset_name": "...", "payer_address": "0x..."} -- charged $0.02 via x402.
  • POST /verify-receipt-signature {"receipt": {...}, "signature": "..."} -- free, confirms a previously-issued receipt is authentic and unmodified.
  • MCP tools verify_payment_receipt / payer_spend_health at /mcp -- currently free, see "Known limitations".
  • GET /health, GET /.well-known/agent-card.json, GET /openapi.json (has x-payment-info).

What it actually does (and why it's not just a log dump)

revenue_events (x402 payments settled) and traffic_events (HTTP requests served) are two separate, uncorrelated tables -- neither insert stores a shared ID linking a specific payment to the specific request it paid for. This asset does the correlation NEXUS itself doesn't otherwise do anywhere: given a claimed (asset_name, payer_address, claimed_amount_usd, claimed_at), it finds the real matching revenue_events row (if any) within window_seconds, then checks whether a successful (2xx) traffic_events row for that same asset landed shortly after that real payment timestamp. The verdict (VERIFIED_DELIVERY / PAYMENT_NO_DELIVERY / PAYMENT_NOT_FOUND) plus a signed receipt is the product -- not raw access to either table. Both tables are read through two Postgres SECURITY DEFINER RPC functions (nexus_verify_payment_receipt, nexus_payer_spend_health) that return only the computed verdict object, never a row dump -- consistent with this codebase's existing INSERT-only RLS policy on both tables (see CLAUDE.md SS5). Migration: add_x402_payment_receipt_verification_rpcs (Supabase project ieduhdgfjdeffvzxvihf).

Scope, on purpose: only covers NEXUS's own already-deployed x402 assets (whatever is actually in our own revenue_events/traffic_events). Auditing a third party's payment claims against a third party's logs was the original, broader idea (opportunity list item #3, "recibo/prueba de ejecucion verificable para pagos entre agentes") and was explicitly flagged there as carrying legal/dispute-liability risk from acting as an arbiter between two other parties. Narrowing scope to our own already-public asset catalog sidesteps that entirely -- there is no third party whose claim we're adjudicating, only our own already-settled data.

Known asset_name spellings (found while building this, real data)

revenue_events.asset_name is not consistently kebab-case across the existing catalog -- e.g. the similarity-search asset's real stored value is "Similarity Search API" (display-cased), not similarity-search-api. Callers must pass the exact string as stored, or the RPC correctly (not a bug) returns PAYMENT_NOT_FOUND. As of this writing, real values seen in revenue_events: document-conversion-api, live-entity-verification, agent-verification-api, url-metadata-api, Similarity Search API, ws.

The signed receipt

signature is an HMAC-SHA256 (hex) over the canonical JSON encoding of receipt, keyed by NEXUS_RECEIPT_SIGNING_KEY. This is not an offline-verifiable signature (that would need asymmetric crypto + a published public key -- deliberately left out, see "Known limitations"): a holder proves a receipt is authentic by calling this asset's own free POST /verify-receipt-signature, which re-checks the HMAC server-side. Rotating NEXUS_RECEIPT_SIGNING_KEY invalidates every receipt issued under the old key.

Deploy target: Cloud Run

Same pipeline as candidates #4/#3/#6 -- see skills/infra-deploy-ops.

# 1. First deploy -- PUBLIC_DOMAIN not known yet, every real request 421s until step 2.
./scripts/deploy_cloud_run.sh x402-receipt-verifier manual_assets/x402-receipt-verifier

# 2. Grab the printed *.run.app URL, then (only if it differs from env-vars.deploy.yaml's guess):
gcloud run services update x402-receipt-verifier --region us-central1 --project nexus-505016 \
    --update-env-vars PUBLIC_DOMAIN=<the-real-domain>

Known limitations (left unfixed on purpose -- CLAUDE.md SS3, no gate without evidence it's needed)

  • MCP tool calls are not charged. Same in-process-call pattern as every other manual asset in this codebase (url-metadata-api, agent-verification-api, document-conversion-api).
  • Receipt signature requires an online check, not offline asymmetric verification -- see above.
  • Correlation is a time-window heuristic, not a hard link. Neither revenue_events nor traffic_events stores a shared correlation ID at insert time, so VERIFIED_DELIVERY means "a successful request to this asset landed within the window after this payment", not "this exact request was paid for by this exact transaction". On a low-traffic asset this is effectively exact; on a hypothetical high-traffic asset with many concurrent callers it would be ambiguous -- candidate_successful_calls (surfaced directly on the receipt, see PaymentReceipt in main.py) would show >1 in that case. None of the 6 assets covered had concurrent traffic dense enough for this to matter as of 2026-08-23.
  • Payment settles before the Supabase RPC runs... Corrected 2026-08-31: false positive, see below. The 2026-08-23 quality gate claimed a Supabase failure would settle payment anyway; nobody had actually read x402's (2.15.0, the pinned version) PaymentMiddlewareASGI source before writing that. It already implements verify→execute→settle-only-on-2xx: verify_payment (no funds move) runs before the handler via call_next(request); when the handler returns ≥400 or raises (exactly what happens here -- _verify_payment_receipt_core re-raises _NexusRpcError as HTTPException(502/503/504) on a Supabase failure), the middleware calls dispatcher.cancel() and never calls process_settlement() -- no charge. Confirmed empirically (real app + real PaymentMiddlewareASGI + mocked facilitator, forcing an HTTPException(504)): 0 settle()/on_after_settle calls on failure, 1 on a 200. No code change needed -- the architecture was already correct.
  • Anon-key RPC bypass. The two Postgres RPC functions are granted to anon (required for PostgREST to expose them at all) -- a leaked SUPABASE_ANON_KEY lets someone call them directly at /rest/v1/rpc/nexus_verify_payment_receipt, bypassing this asset's x402 charge. Accepted: the RPCs only return correlation verdicts about NEXUS's own already-public asset catalog, nothing sensitive is exposed by the bypass itself, only the paywall is bypassed. Same risk category as every other Supabase anon-key use in this codebase.
  • No per-caller rate limiting. Fine for a 7-day disposable measurement window.

Quality gate (2026-08-22 deploy, gate completed 2026-08-23 after a spend-limit interruption)

Same 2-agent process as candidates #3/#4/#6 (security lens; functional+quality+buyer-experience lens), run post-deploy this time -- the review was still in progress when the account's monthly spend limit cut the session overnight, resumed and finished the next session. Real findings, applied:

  • Security (1 finding, low): POST /verify-receipt-signature had no x402 gate and no size/depth bound, so it hashed an arbitrary caller-supplied receipt dict for free -- a cheap cost/availability nuisance, not a serious vuln. Fixed: rejects >4096-byte or >10-level-deep bodies with 400/413 before any hashing (_validate_receipt_shape, _MAX_RECEIPT_BYTES/_MAX_RECEIPT_DEPTH in main.py).
  • Functional/buyer-experience (1 finding, medium) -- retracted 2026-08-31: originally reported as "payment settles before the RPC runs, so a Supabase failure leaves the buyer charged with no recourse." This was a misreading of PaymentMiddlewareASGI, not a real bug -- see "Known limitations" above for the empirical proof. No code change was needed.
  • Everything else checked (SSRF class from candidate #3, zip-bomb/thread-leak class from candidate #6, anon-key RPC over-return, HMAC correctness, self-payment payTo class from candidate #4, IP truncation, injection surface) came back clean -- confirmed, not just claimed, by re-reading the relevant code paths.
  • Two very-low cosmetic items were left as-is on purpose: an unused ctx: Context = None MCP-tool param (matches the same unused-param convention already present in agent-verification-api/main.py, not a deviation worth fixing here) and silent clamping of window_seconds/lookback_days on the MCP path only (REST already rejects out-of-range via Pydantic; the MCP path echoes the substituted value back in the receipt, so it's discoverable, just not an explicit error -- no evidence yet that this needs a gate).

Measurement (candidate #13, 7-day window)

7-day window from first real deploy (2026-08-23 -> decision point 2026-08-30). Source of truth: traffic_events/revenue_events/mcp_call_events (asset_name = 'x402-receipt-verifier'), not Cloud Run logs. Day 7: if zero real traffic (filtering crawlers), pause/delete the Cloud Run service (gcloud run services delete x402-receipt-verifier --region us-central1 --project nexus-505016).