← Work

MonkePay

Payment rails for AI agents. One line of middleware turns an API endpoint into a paid one, and agents pay per request in USDC.

Role
Designed and built solo · SDK, backend, dashboard
When
Mar 2026
Status
Live at monkepay.xyz
Links
1 lineto put a price on an endpoint
4framework adapters: Express, Hono, Fastify, Next.js
1%platform fee, swept on a schedule
3 wkfirst commit to launch
Figures from the codebase and the live product.

Context

AI agents are starting to call paid APIs, but API billing assumes a person: an account, a card, a monthly plan. An agent has none of those. It has a wallet and a single request to make.

x402 brings back HTTP’s long-unused 402 Payment Required status, so a client can pay inside the request itself. The server answers with a price, the client pays in USDC and retries, and the server serves the response. MonkePay is the layer that makes this one line for an API owner:

app.get("/v1/forecast", mp.gate({ price: "0.01" }), handler);

What I built

  • The SDK. TypeScript, shipped as ESM and CJS. A framework-agnostic core sits behind a nine-method adapter contract, with adapters for Express, Hono, Fastify and the Next.js App Router. The parity between them is documented, so a route behaves the same whichever framework it runs on. It supports per-request pricing, or one payment that unlocks permanent access with a signed token.
  • Settlement. The agent pays into a per-developer Coinbase CDP wallet on Base. The SDK reports the payment to the backend over a signed channel. The backend confirms the actual USDC transfer on-chain before it records anything, then splits gross, fee and net.
  • Money movement. Scheduled jobs sweep the 1% platform fee and pay out the rest to each developer’s address. Gas is topped up automatically before a sweep runs.
  • The dashboard. Live payments, filters by agent and endpoint, and CSV export.

What was hard

Trusting nothing the SDK says about money. Every event from the SDK is HMAC-signed over a timestamp, a nonce and the payload, compared in constant time, and replay-blocked with a per-nonce key in Redis. A valid signature still isn’t enough. The claimed amount has to match the decoded transfer log, and a unique transaction hash stops the same payment being recorded twice. If the receipt isn’t on-chain yet, the backend retries until it is. It never records on a timer.

Moving money on a schedule, safely. Sweeps and payouts run under distributed Redis locks, with release and renewal written as Lua scripts that check the lock’s token, so one worker can never release another’s lock. Each transaction is claimed before it moves, keyed for idempotency, and released if the sweep fails, so a crash halfway through can be retried safely.

A bug worth keeping. Early on, a payout drew funds across networks. Balances are now strictly per network, and a monetary-safety regression suite guards that rule.

A payment failure is not an API failure. If recording a payment or a developer’s callback fails, the paid request still succeeds. The agent paid, so it gets its response.

How it fits together
Agentcalls the API Gate402 · price BaseUSDC over x402 SDK eventHMAC · nonce Verifytransfer log Ledgergross · 1% · net Payoutper network Fee sweep locked · idempotent Agent calls the API Gate 402 · price Base USDC over x402 SDK event HMAC · nonce Verify transfer log Ledger gross · 1% · net Payout per network Fee sweep locked · idempotent
Money moves along the accent line. Nothing is recorded until the transfer is confirmed on-chain.
A decision, and the option I rejected
Decision · SettlementAccepted
Decided

Record a payment only after the USDC transfer is confirmed on-chain.

Rejected

Trusting the payment event that the SDK reports.

Because

The SDK runs on someone else's server. A signed event proves who sent it, not that money moved. The transfer log on Base does, so the backend checks the claimed amount against it and refuses any transaction hash it has already recorded.