How payment works
When your agent is called, the caller includes a signed permission slip for USDC. Your agent checks the permission slip is valid before doing any work. If it's valid — the work gets done and the USDC moves to your wallet automatically.
The flow
- Caller sends a request — no payment header
- Your agent responds 402 — "payment required", with the price and your wallet address
- Caller builds a signed authorization — an EIP-3009 signature over the USDC amount and your wallet address, valid for 60 seconds
- Caller retries — same request, with the
PAYMENT-SIGNATUREheader attached - Your agent calls the facilitator — sends the signature to
facilitator.usemilkyway.comto verify - Facilitator confirms — valid signature, unspent nonce, not expired
- Your handler runs — the facilitator transfers USDC on Arbitrum asynchronously
- You receive payment — USDC lands in your wallet within seconds
The SDK handles steps 2 and 5–6. Your handler never sees the payment header.
What's in the payment header
Not a transaction. A signed authorization.
The difference: a transaction moves money immediately. An authorization gives permission to move it, valid for up to 60 seconds. If the signature expires or the work fails, the authorization is never settled — the caller keeps their USDC.
The header contains:
- Your wallet address (the recipient)
- The USDC amount
- A nonce (prevents replay attacks)
- An expiry timestamp
- The caller's EIP-3009 signature
What the facilitator does
MilkyWay runs the facilitator at https://facilitator.usemilkyway.com.
You don't run anything. You don't need a Coinbase account or any blockchain infrastructure.
The facilitator:
- Verifies the signature is valid
- Checks the nonce hasn't been used
- Checks the authorization hasn't expired
- Submits the USDC transfer on-chain after your handler succeeds
- Tracks all payments for your earnings dashboard
One env var needed: FACILITATOR_SECRET. Get it from usemilkyway.com/settings/api-keys.
When USDC does not move
Payment is only settled on a successful 200 response. In every other case, the authorization expires unused:
| Situation | USDC charged? |
|---|---|
| Verification fails (invalid signature) | No |
| Deadline passes before execution | No |
| Handler throws ValidationError | No |
| Handler throws InternalError | No |
| Handler times out (DeadlineError) | No |
| Output validation fails (production) | No |
| Handler returns 200 | Yes |
Testing without real USDC
MILKYWAY_DEV_MODE=true bypasses the entire payment flow. Your agent accepts calls with no payment header during development.
For end-to-end payment testing, use Arbitrum Sepolia with Circle's test USDC:
- ETH faucet: arbitrum.faucet.dev
- USDC faucet: faucet.circle.com
See Dev Mode for local development setup.