← Back to list

How we are solving payment between agents? Stripe Won’t Help.

A look at why HTTP 402 came back from the dead, and how three teams (Coinbase, Google, and ours) ended up with three different answers.

Raahul Dutta · 2026-05-28 15:45 · 5 claps · 7.8 min read
#ai-agent #a2a-protocol #bindu #ai #generative-ai-tools
Open on Medium ↗
Wiki topics: AGT · AI Agents AI · AI · General FIN · Fintech & Banking 🏀 · Basketball

🌻 How we are solving payment between agents? Stripe Won’t Help.

A look at why HTTP 402 came back from the dead, and how three teams (Coinbase, Google, and ours) ended up with three different answers.

https://github.com/GetBindu/Bindu

https://github.com/GetBindu/Bindu

[embed]GitHub - GetBindu/Bindu: Bindu: The identity, communication, and payments layer for AI agents. Bindu: The identity, communication, and payments layer for AI agents. - GetBindu/Bindugithub.com

The real problem

                       your research agent
                                │
        ┌───────────────────────┼───────────────────────┐
        ▼                       ▼                       ▼
  ┌────────────┐          ┌────────────┐          ┌────────────┐
  │  search    │          │ document   │          │ translator │
  │  the MAS   │          │ summarizer │          │ (zh → en)  │
  │  website   │          │            │          │            │
  │ $0.005/call│          │ $0.01/page │          │ $0.002/p   │
  └────────────┘          └────────────┘          └────────────┘
   ↑ operator A           ↑ operator B            ↑ operator C

Each box is owned by someone else. Each costs money, they have LLM bills too. Today, to make this work, you (a human, ahead of time):

   1. Find the search service.      Sign up. Add card. Get API key.
   2. Find the document service.    Sign up. Add card. Get API key.
   3. Find the translator.          Sign up. Add card. Get API key.
   4. Paste all three keys into your agent's config.
   5. Pray nothing rotates.

Notice what your agent cannot do:

  Discover a better translator at runtime and switch to it.
  Pay a service it's never heard of for a one-off $0.002 page.
  Hire a more expensive specialist only when the task warrants it.
  Be hired itself, by another agent that's never heard of you.

So: how do agents pay each other? Why you cant do with stripe or normal payment gatway?

┌────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│  Stripe                                                                 │
│    min fee     ~$0.30 + 2.9%                                            │
│    setup       account, KYB, API key per recipient                     │
│    settles     T+2 days                                                 │
│    a $0.001 call would lose $0.30 to fees    <Not good>                        │
│                                                                         │
│  Subscriptions                                                          │
│    each agent pair needs a pre-existing contract                       │
│    not composable across operators           <Not good>                        │
│    can't onboard a new agent in <1 second                              │
│                                                                         │
│  Crypto on-chain (Ethereum L1)                                          │
│    gas         ~$0.50–$5 depending on congestion                       │
│    settles     12s                                                      │
│    same math as Stripe for $0.001 calls       <Not good>                       │
│                                                                         │
│  Centralized credits (like API platforms do)                            │
│    trust the platform with the float                                   │
│    not composable across operators           <Not good>                        │
│                                                                         │
└────────────────────────────────────────────────────────────────────────┘

What we actually need:

Microeconomic: $0.0001 per call has to make sense, fees < 1% of the call. Permissionless: any agent can pay any agent, no prior relationship. Machine-to-machine: no human in the loop, no captcha, no checkout page. Composable: passing money along a multi-hop call chain has to feel like passing an HTTP header. Failure-aware: programmatic refunds, retries, and reconciliation

None of the existing rails check all five boxes.

Little History

RFC 7231, §6.5.2:
  402 Payment Required
  This code is reserved for future use.

402 Payment Required has sat in the spec for 28 years. No one used it because the payment infrastructure to pair with it didn't exist.In 2024 Coinbase shipped x402, an HTTP standard that finally fills it in:

   client                    server                  facilitator         chain
     │                          │                         │                │
     │  POST /                  │                         │                │
     ├─────────────────────────►│                         │                │
     │       402 Payment Required + accepts[]             │                │
     │◄─────────────────────────┤                         │                │
     │                          │                         │                │
     │  POST / + X-PAYMENT      │                         │                │
     ├─────────────────────────►│  /verify (signed auth)  │                │
     │                          ├────────────────────────►│                │
     │                          │     {isValid: true}     │                │
     │                          │◄────────────────────────┤                │
     │                          │  (does the work)        │                │
     │                          │                         │                │
     │                          │  /settle                │                │
     │                          ├────────────────────────►│  USDC.transfer │
     │                          │                         ├───────────────►│
     │                          │     {success: true}     │       ✓        │
     │                          │◄────────────────────────┤                │
     │                          │                         │                │
     │       200 OK + artifact  │                         │                │
     │◄─────────────────────────┤                         │                │

Key properties:

Stateless facilitator: it never holds funds, just verifies and broadcasts. EIP-3009 pre-signed authorizations: payer signs once, server presents to chain Settles on Base / Solana: ~$0.0001 gas, ~2s confirmation Composable as a header pass X-PAYMENT along a call chain like you'd pass Authorization

# A real x402 client call, in 8 lines
headers = {"X-PAYMENT": b64encode(payment_payload.model_dump_json())}
resp = httpx.post("https://my-agent.example/api", headers=headers, json={...})
# That's it. Settlement happens on the server's side.

The design question every implementer hits

Once verify is fast and settle is slow, what order do you do them in relative to the actual work?There are three positions a server can take:

   ┌───────────────────────────────────────────────────────────────┐
   │  Option A: verify → run → settle                              │
   │            "buffer the response, settle on success"           │
   │                                                                │
   │  Option B: verify → settle → run                              │
   │            "debit first, then do the work"                    │
   │                                                                │
   │  Option C: verify → reserve → run → capture                   │
   │            "hold the funds, capture on success" (Stripe-like) │
   └───────────────────────────────────────────────────────────────┘

Option C is what credit cards do. It requires a reservation primitive. x402 doesn’t have one — EIP-3009 is one-shot. So everyone picks A or B.

Coinbase chose A. Google chose B. Why?

┌─────────────────────────────────────────────────────────────────────────┐
│  Coinbase x402-express (TypeScript reference middleware)                │
│  https://github.com/coinbase/x402/blob/main/typescript/packages/        │
│         legacy/x402-express/src/index.ts                                │
│                                                                          │
│      verify ──► next() (buffer response) ──► if status < 400, settle    │
│                                                                          │
│  Designed for: protected API endpoints with sub-second response times.  │
│  Failure case: if settle reverts after the response is buffered,        │
│                they catch it and refuse to send the body.               │
│  Window between verify and settle:  ~200ms                              │
│  Risk during the window:            negligible                          │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│  Google A2A x402 extension (Agent-to-Agent protocol)                    │
│  https://github.com/google-agentic-commerce/a2a-x402                    │
│                                                                          │
│      verify ──► settle ──► begin processing task (state: working)       │
│              ──► return artifact when task completes                    │
│                                                                          │
│  Designed for: long-running agent tasks (seconds to minutes).           │
│  Failure case: if settle fails, the agent never starts.                 │
│  Window between verify and settle:  ~200ms                              │
│  Risk during the window:            negligible                          │
└─────────────────────────────────────────────────────────────────────────┘

The thing that flips the answer is how long the work takes.

For Coinbase’s typical caller: a paid API endpoint -> verify-execute-settle works because the whole round trip is <500ms. Almost nothing can go wrong in that window.

For Google’s typical caller: an agent that calls a model and possibly other agents -> the window between verify and settle becomes the duration of the entire task. Seconds. Sometimes minutes. That window changes the threat model entirely.

Our framework — Bindu chose B. Here’s why.

# bindu/server/workers/manifest_worker.py
async def run_task(self, params: TaskSendParams) -> None:
    task = await self.storage.load_task(params["task_id"])
    payment_context = params.get("payment_context")

    # ─── settle BEFORE doing any work ────────────────────────────
    if payment_context:
        settlement_metadata = await self._settle_payment(payment_context)
        settled_ok = (
            settlement_metadata.get("x402.payment.status")
            == "payment-completed"
        )
        if not settled_ok:
            await self._handle_settlement_failure(task, settlement_metadata)
            return   # ← LLM never runs
    # ─────────────────────────────────────────────────────────────

    await self.storage.update_task(task["id"], state="working")
    raw_results = self.manifest.run(message_history)   # ← only on settle success
    artifact = build_artifact(raw_results)
    await deliver(artifact)

The reasoning, in three lines:

  agent work is expensive (LLM tokens, $0.30+ per call)
  agent work is slow      (seconds to minutes)
  agent work is irreversible (once the model output exists, it exists)

If those three hold, the only sound ordering is settle-first. Otherwise every failed settle subsidizes the caller with real LLM cost.

What this looks like for each failure mode

We came up with four canonical things that can go wrong. Each one looks different under settle-first.

Scenario 1: the drain attack

   payer wallet: 1 USDC
       │
       │  T+0     signs auth: 1 USDC, nonce=0xfa11..., valid 300s
       │
       │  T+200   POST / with X-PAYMENT  ──► verify ✅
       │
       │  T+300   server starts /settle  ──┐
       │                                   │
       │  T+800   payer drains wallet      │ (parallel)
       │          via DEX swap             │
       │  T+2800  swap confirms; balance=0 │
       │                                   │
       │  T+3000  facilitator submits tx ──┘
       │          ▼
       │          chain: USDC.transferFrom revert (insufficient balance)
       │
       │  T+3200  /settle returns {success: false}
       ▼
   ┌──────────────────────────────────────────────────────┐
   │  Under settle-first:                                  │
   │    LLM never ran        → $0 wasted                  │
   │    task state           → failed                     │
   │    artifact             → not generated              │
   │    EIP-3009 metadata    → persisted for audit        │
   └──────────────────────────────────────────────────────┘

Scenario 2: the facilitator timeout

   payer wallet: 1 USDC, no funny business
       │
       │  T+0     signs auth
       │  T+200   verify ✅
       │  T+300   /settle starts ──► facilitator broadcasts tx
       │
       │  T+10s   facilitator times out (HTTP timeout = 10s)
       │          ╳ returns failure
       │
       │  T+25s   chain actually confirms the tx
       │          payer's USDC IS gone
       │
       ▼
   ┌──────────────────────────────────────────────────────┐
   │  Under settle-first:                                  │
   │    LLM never ran        → $0 wasted                  │
   │    task state           → failed                     │
   │    BUT the payer is debited on-chain                 │
   │    ──► orphan payment, requires reconciliation       │
   │    Recovery metadata    → nonce, auth, network       │
   │                           all persisted in task.meta │
   └──────────────────────────────────────────────────────┘

This is the case where settle-first does not save you. The race between facilitator-timeout and chain-confirmation is a structural property of Base under congestion, not something the agent framework can fix on its own.

Scenario 3 — the parallel-nonce race

   payer wallet: 1 USDC

        ┌──── Request A: nonce=0xa1a1...    ────►  verify ✅ ─┐
        │                                                       │
        │     both verifies see                                │
        │     the same 1 USDC                                  │
        │                                                       │
        └──── Request B: nonce=0xb2b2...    ────►  verify ✅ ─┘
              (sent in parallel,
               different nonces from same wallet)

         ┌──► settle A ──► chain ✓  (debits 1 USDC)
         │
         └──► settle B ──► chain ╳  (insufficient balance)
                                                ▼
   ┌──────────────────────────────────────────────────────┐
   │  Under settle-first:                                  │
   │    Task A: completed, artifact delivered, paid       │
   │    Task B: failed, NO LLM call, NO artifact          │
   │    Net: agent paid for exactly what it delivered     │
   └──────────────────────────────────────────────────────┘

Under verify-execute-settle, both LLM calls would burn for one settled payment. Under settle-first, the loser of the chain race exits before the LLM is even invoked.

Scenario 4 — the replay

   T+0   Mallory captures a valid X-PAYMENT header somehow

   T+1   POST / + X-PAYMENT  ──► nonce_store.claim(nonce)  ✅ first
                              ──► verify  ✅
                              ──► settle  ✅
                              ──► artifact delivered

   T+2   POST / + X-PAYMENT  ──► nonce_store.claim(nonce)  ✗ already used
              (same header)
                              ──► HTTP 402: "Payment nonce already used (replay)"
                                            (verify never even runs)

The trade-off, plain:

┌────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  Settle-first gives you:                                            │
│    + zero LLM cost on failed settles                               │
│    + zero LLM cost on parallel-nonce races                         │
│    + no information leak through artifacts on failed payments      │
│                                                                     │
│  Settle-first costs you:                                            │
│    − a new "orphan payment" failure mode when work fails after     │
│      a successful settle (LLM provider 500, agent bug)             │
│      → must be handled by operator reconciliation                   │
│    − doesn't save you from the facilitator-timeout / chain-        │
│      confirmation race (Scenario 2) — that's structural            │
│                                                                     │
│  The orphan-payment cost is contained because:                      │
│    • payment metadata persists nonce + authorization + receipts     │
│    • operators can issue manual refunds out of band                │
│    • the case is rare (real bugs, not adversarial)                 │
│                                                                     │
└────────────────────────────────────────────────────────────────────┘

End-to-end, on a laptop

$ uv run python tests/e2e/x402_scenarios/run_e2e.py

  ► launching mock facilitator
  ► launching bindu agent
  ✓ facilitator ready
  ✓ agent ready

══════════════════════════════════════════════════════════════════════════════
Scenario 1 — Mallory drains wallet between verify and settle
══════════════════════════════════════════════════════════════════════════════
  message/send → HTTP 200
  task result:
    state:             failed
    artifacts:         0
    metadata.x402.payment.status: payment-failed
    metadata.x402_nonce: 0xfa110000...
    last agent msg: Payment settlement failed; task not executed.

══════════════════════════════════════════════════════════════════════════════
Scenario 3 — two parallel requests, second nonce loses the settle race
══════════════════════════════════════════════════════════════════════════════
  request A → HTTP 200
  request B → HTTP 200
  task A (good settle):
    state: completed,  artifacts: 1,  artifact: "PAID JOB DONE — ..."
  task B (failed settle):
    state: failed,     artifacts: 0,  no LLM call burned

══════════════════════════════════════════════════════════════════════════════
Scenario 4 — Mallory replays the same X-PAYMENT header
══════════════════════════════════════════════════════════════════════════════
  first request  → HTTP 200
  second request → HTTP 402
  second request body.error → Payment nonce already used (replay)

The full code lives at Bindu. Its open source btw.

Use it — review it — send me your critic feedback.

Thanks for reading it.

  • Raahul

메타데이터
post_id
1846bcd588fe
slug
how-we-are-solving-payment-between-agents-stripe-wont-help-1846bcd588fe
url
https://medium.com/@raahul_rahl/how-we-are-solving-payment-between-agents-stripe-wont-help-1846bcd588fe
canonical_url
https://medium.com/@raahul_rahl/how-we-are-solving-payment-between-agents-stripe-wont-help-1846bcd588fe
author_url
https://medium.com/@raahul_rahl
status
ok
fetched_at
2026-07-13 20:46:58