How it works
A single agent.fetch(url) call triggers a full x402 payment handshake.
Your code never sees it — but understanding the wire flow is the fastest
path to debugging a misbehaving integration.
The six messages
1. agent.fetch(url) → [merchant]
2. 402 Payment Required + PAYMENT-REQUIRED header ← [merchant]
3. POST /api/x402/sign Bearer pk_… → [obscura]
(auth · cap check · Umbra mixer transfer · encode)
4. { paymentSignatureHeader } // umbra-mixer-v1 ← [obscura]
5. retry w/ PAYMENT-SIGNATURE → [merchant]
(merchant verifies on-chain proofs in envelope)
6. 200 + data + X-Payment-Response ← [merchant]What Obscura does in step 3
The agent SDK forwards the merchant's 402 challenge to Obscura's
/api/x402/sign endpoint, authenticated with your agent's API key.
Our backend runs four steps before returning a payment header:
- Authenticate. Bearer token →
agent_api_keys.key_hashlookup → load the owning agent + its budget. - Cap check. An atomic
UPDATE budgets SET spent_usdg = spent + amount WHERE spent + amount <= capincrements the counter only if the result stays under the monthly cap. Zero rows back means the call would overflow — we returnover_cap(402) without spending. - Confidential transfer. Deduct from the agent's encrypted token account (ETA) and insert a UTXO commitment in the on-chain Umbra mixer tree, addressed to the merchant's ETA. The transferred amount is encrypted; the on-chain link between sender ETA and recipient ETA passes through the mixer commitment so observers can't pair them.
- Encode. Return a base64-encoded
umbra-mixer-v1envelope carrying{queueSignature, callbackSignature, recipientEtaAddress, asset, amount, resource}for the merchant to verify on-chain before serving the resource.
Why the agent can't sign directly
The agent's Umbra-side keypair is derived server-side via HMAC-SHA-256 from a master seed and the agent's UUID; it never leaves Obscura's backend. This is deliberate:
- Bounded loss on key compromise. A leaked API key can only spend up to the remaining monthly cap — never drain the encrypted balance.
- No seed-phrase hygiene in your agent. Your agent never holds a private key, which means no rotation problem, no secrets in your container image, no KMS integration.
- Atomic spend accounting. We record every spend in a
transactionsrow before submitting to Umbra, so the ledger and chain stay in lockstep even on retry.
What the merchant does in step 5
The merchant decodes the umbra-mixer-v1 envelope and verifies the
on-chain proofs the agent attached:
- Decode the base64 envelope into
{queueSignature, callbackSignature, recipientEtaAddress, asset, amount, resource}. - Sanity-check the envelope:
recipientEtaAddressmatches the merchant's own ETA,assetmatches the configured mint,amount/resourcematch what the route requires, and thequeueSignaturewasn't seen in the past 5 minutes (replay window). - Fetch the queue + callback transactions on Solana via Helius RPC and confirm both landed without error.
- Serve the paid resource with an
X-Payment-Responseheader carrying the same proofs.
The actual encrypted-balance credit on the merchant's side happens
asynchronously — a background daemon (/api/cron/claim-daemon, every
2 minutes) scans the mixer tree for receiver-claimable UTXOs and
claims them via the Umbra relayer. By the time the merchant's
dashboard shows the inbound spend, the encrypted balance has settled.
Latency budget
The hot path is dominated by ZK-proof generation and Arcium MPC
finalization on the Umbra side: typically 10–25 seconds per spend on
devnet, with a P99 around 45 seconds on a cold prover cache. The SDK's
default timeout is 60 seconds; bump it via signTimeoutMs only after
measuring real cold-path numbers in your environment.
Verify on-chain
The X-Payment-Response header is base64-encoded JSON. Decode it to
get the queue signature:
const receipt = JSON.parse(
Buffer.from(
response.headers.get("X-Payment-Response")!,
"base64",
).toString("utf8"),
);
console.log(`https://solscan.io/tx/${receipt.queueSignature}?cluster=devnet`);Next: Error handling — every failure mode the flow above can produce.