ByAUJay
Summary: In this guide, we’re going to help you embed x402--the HTTP-native “Payment Required” handshake--directly into your SDKs. This is a game changer for developers, letting you charge per request with just one retry and no need for accounts. We’ve packed in detailed wire formats, header schemas, production-ready middleware patterns, and some handy code examples in TypeScript and Python. You’ll get coverage for both EVM (USDC/EIP‑3009) and Solana too!
Embedding x402 in SDKs: Making ‘Pay Required’ Developer-Friendly
Decision-makers love having clear adoption curves, and that’s exactly where x402 comes into play. It’s an API that returns a 402 Payment Required status, complete with machine-readable price details. Once the client gives the green light, they can simply retry using a single header. The server then does its thing--checks all the info, settles everything, and responds with a nice 200 OK. Your SDK should make sure this entire process flows smoothly for both humans and AI agents--no need to worry about creating a new session layer or dealing with any elaborate custom billing systems.
Here’s a great plan to help you integrate x402 into your SDKs without a hitch and keep everything secure.
What x402 Adds to HTTP and Why It Matters to SDKs
The x402 status code brings some cool new possibilities to the HTTP scene, especially when it comes to Software Development Kits (SDKs). It’s designed to make transactions easier and more secure. By offering a consistent method for managing payment requests, x402 helps developers build better user experiences while keeping everything running smoothly.
Precisely What to Parse from a 402 and What to Send Back
When you're handling a 402 error, it's super important to know what to pay attention to. Ideally, your app should pull out specific details from the response, such as error messages or hints related to the transaction. Equally important is sending back the right info, so your server can clearly convey the transaction's status and any actions the client might need to take.
Client and Server Patterns
When you’re diving into implementing this in your code, it's a good idea to think about the patterns you’re using on both the client and server sides. For the client, you might want to check out Axios or some fetch wrappers to make your request process easier. On the server side, using middleware like Express, Next, or Hono can really help you handle those x402 responses without any fuss. Remember, keeping your architecture tidy and well-organized is super important for a seamless user experience.
EVM Details (USDC/EIP‑3009) and Solana Details (Partially-Signed Transactions)
Let’s get into the nitty-gritty! When it comes to Ethereum's EVM, it's super helpful to get to know USDC and EIP-3009. These standards really streamline payment processes. Over on the Solana front, partially-signed transactions are pretty revolutionary. They give you a lot more flexibility and security when managing transactions. Grasping these details will definitely make it easier to work with various blockchains.
Settlement, Receipts, Idempotency, and Observability
Next, let’s dive into some important concepts you’ll want to wrap your head around. First up is settlement, which is all about wrapping up those transactions. Then we’ve got receipts - these are super important because they give both sides a record of what went down. And we can’t overlook idempotency; it’s a handy tool that helps avoid those pesky duplicate transactions. Finally, there’s observability, which is key for monitoring your system’s health and figuring out where things might be going off track.
Emerging Extensions (Upto Scheme, Smart Wallets) and How to Future-Proof
Lastly, make sure to keep an eye on cool new extensions like the upto scheme and smart wallets. These innovations are really setting the stage for some next-level interactions in our digital economy. To keep your work relevant, stay in the loop on these trends and see how they can boost your SDK capabilities. Embracing these changes now could save you a ton of effort later on!
What x402 actually is (and isn’t)
- The new protocol revives HTTP status 402 Payment Required, which had been just sitting there as a reserved status. Now it features a standardized machine-readable response body and introduces a client “X-PAYMENT” header for handling retries. The best part? You don’t need accounts or OAuth--just straightforward HTTP fun. (docs.cdp.coinbase.com)
- When the server throws a 402 response, it comes with a Payment Requirements object that lays out all the details you need, like the amount, asset, network, payTo info, and timeout, among other things. The client then takes this payment payload, signs it, and sends it back as a base64-encoded JSON in the X-PAYMENT header. The server checks it over, decides whether to settle locally or use a facilitator, and then fires back a 200 response. Sometimes, it even includes an optional X-PAYMENT-RESPONSE receipt. (github.com)
- The 402 status is something from the HTTP standard that’s been around for a while but is finally getting some action with this protocol. This is great news for SDKs because they can seamlessly work with any HTTP client, which means they’re “payments-aware” instead of being tied down to just one provider. (developer.mozilla.org)
Hey builders! If you're diving into a new project, you're in luck--Cloudflare and Coinbase are here to help with some awesome reference software and docs. No need to feel overwhelmed and start from scratch!
Cloudflare’s Agents SDK has got you covered with wrapFetchWithPayment and x402 middleware, making your life a bit easier. On the other hand, Coinbase has an open-source x402 repository that clearly outlines the types, headers, and facilitator endpoints you’re going to need.
Take a look at it right here: (developers.cloudflare.com).
The minimal x402 SDK contract
Your SDK should always be able to tackle these four essential tasks, regardless of where you're putting it to use:
- Intercept 402s
- Watch out for those pesky HTTP 402 responses and take a closer look at the JSON Payment Required Response. And don’t worry, we’ve got you covered with support for multiple accepts entries (multi-rail). Check it out here: (github.com)
- Choose Your Payment Method
- First off, select a payment option that fits your local guidelines. Think about things like the preferred network, token, and maximum price. Once you’ve got that squared away, you can create a Payment Payload. Check it out here: (github.com)
- Give it another go with X-PAYMENT
- First things first, you'll need to Base64-encode the Payment Payload JSON into the X-PAYMENT. After that, simply resend the original request just as it was (same URL, method, body). For all the nitty-gritty details, head over to (github.com).
- Check for success and show receipts
- If you've got the X-PAYMENT-RESPONSE, take a closer look to find the txHash, network, and outcome. Don't forget to pass this info along to the callers so they can keep their records straight, manage refunds, and sort out reconciliation. (github.com)
That's the gist of the developer experience: “I called the API, the SDK threw a 402 error my way, I processed the payment, it retried, and then finally gave me the data along with a receipt.”
Exact wire formats you must support
Server → Client (on first call):
- Status: 402 Payment Required (JSON body)
- Body: The Payment Required Response has these fields:
- x402Version, accepts: [paymentRequirements]
- paymentRequirements fields: scheme, network, maxAmountRequired (in atomic units), resource, description, mimeType, payTo, maxTimeoutSeconds, asset, extra
- Check out the example schema in the protocol README. (github.com)
Client → Server (retry):
- Header: X‑PAYMENT: base64(json(Payment Payload))
- Payment Payload fields: x402Version, scheme, network, payload (this can vary depending on the scheme) (github.com)
Server → Client (when everything is working smoothly):
- Status: 200 OK
- Optional header: X-PAYMENT-RESPONSE: base64(json({ success, txHash, networkId, error })) (github.com)
Tip: A smart move is to attach the parsed receipt directly to your SDK’s response object (like response.payment.meta). This way, your app can easily access it for auditing or display it whenever you need.
Client patterns: make it invisible but controllable
Wrapping the Platform’s Default HTTP Client:
If you want to spice up the default HTTP client that the platform gives you, it’s super easy to wrap it and throw in your own custom tweaks and features. Let’s dive into how you can kick things off:
- Create Your Custom Client
Start by making a new class that builds off the platform's standard HTTP client. This way, you get to take advantage of all the default features, plus you can throw in your own custom methods or tweak the ones already there if you need to.class CustomHttpClient(DefaultHttpClient): def custom_method(self): # Your custom logic here pass - Add Interceptors
If you're looking to tweak requests or responses--think logging, authentication, and the like--adding interceptors is the way to go. You can easily set this up in your custom client class.class CustomHttpClient(DefaultHttpClient): def intercept_request(self, request): # Modify the request here return modified_request def intercept_response(self, response): # Handle the response here return modified_response - Handle Errors Gracefully
Managing errors smoothly is super important. You have the option to override the error handling method so you can throw in your own logic or logging to better track what's going on.class CustomHttpClient(DefaultHttpClient): def handle_error(self, error): # Custom error handling logic log_error(error) raise CustomHttpError("An error occurred") - Test Your Client
Once you've set up your custom client, make sure to give it a good test run! It's a great idea to run some unit tests to check that everything is working as it should.
By following these steps, you’ll end up with a customized HTTP client that’s just right for you, all while taking advantage of the awesome features that come with the platform’s default tools. Happy coding!
- Browser/Node: We're looking for a fetch wrapper or an Axios interceptor that can do the following:
- Retry the request if we get a 402 error with the X-PAYMENT header.
- Provide hooks like
onPaymentRequired(requirements)for scenarios where we need a human to step in and give the green light. - Allow you to choose your facilitator (testnet, mainnet, or custom). (developers.cloudflare.com)
- Python: A subclass of
requests.Sessionthat throws in a Response hook to tackle those annoying 402 errors. - Go: We’ve got a custom
http.RoundTripperthat does the same retry magic.
If you're putting together something for agents, why not borrow a tip from Cloudflare? Just set up wrapFetchWithPayment(account) and paymentMiddleware(). It keeps everything nice and straightforward--just one-liners on both sides. You can find more details here: (developers.cloudflare.com).
TypeScript: Axios interceptor snippet (client)
import axios from "axios";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
// Skeleton utilities you provide in your SDK:
import { buildPaymentHeaderFrom402, decodePaymentResponse } from "./x402-sdk";
// Wallet for EVM (USDC/EIP-3009). Use baseSepolia/base in test/prod respectively.
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, transport: http() });
export function withX402(axiosInstance = axios.create()) {
axiosInstance.interceptors.response.use(async (res) => res, async (error) => {
const res = error.response;
if (!res || res.status !== 402 || !res.data) throw error;
// 1) Parse server’s Payment Required Response
const { accepts, x402Version } = res.data;
// 2) Choose an option (policy: prefer base, price <= $0.05)
const choice = accepts.find((a: any) => a.network.includes("base")) ?? accepts[0];
// 3) Build X-PAYMENT (base64) using wallet and facilitator
const xPayment = await buildPaymentHeaderFrom402({
wallet,
x402Version,
requirements: choice,
facilitator: { url: process.env.FACILITATOR_URL! } // e.g. https://x402.org/facilitator on testnets
});
// 4) Retry original request with header
const cfg = { ...res.config, headers: { ...(res.config.headers || {}), "X-PAYMENT": xPayment } };
const paid = await axios.request(cfg);
// 5) Parse optional X-PAYMENT-RESPONSE
const receipt = decodePaymentResponse(paid.headers["x-payment-response"]);
(paid as any).x402 = { receipt, requirements: choice };
return paid;
});
return axiosInstance;
}
It seems like you’ve hit that classic “intercept 402 → sign → retry” loop that your SDK is designed to handle. If you need to check out the public header schemas and facilitator endpoints, you can find all that info in the reference spec right here: (github.com)
Python: requests Session adapter (client)
import base64, json, requests
from typing import Optional
from my_x402.evm import sign_eip3009_header # your helper
from my_x402.solana import build_partial_tx # your helper
class X402Session(requests.Session):
def __init__(self, wallet, facilitator_url: str):
super().__init__()
self.wallet = wallet
self.facilitator_url = facilitator_url
def request(self, method, url, **kwargs):
r = super().request(method, url, **kwargs)
if r.status_code != 402:
return r
body = r.json()
accepts = body["accepts"]
req = self._choose(accepts)
x_payment = self._build_payment_header(req, body.get("x402Version", 1))
headers = dict(kwargs.get("headers") or {})
headers["X-PAYMENT"] = x_payment
paid = super().request(method, url, headers=headers, **{k:v for k,v in kwargs.items() if k!="headers"})
paid.x402 = {"receipt": self._decode_receipt(paid.headers.get("X-PAYMENT-RESPONSE")), "requirements": req}
return paid
def _choose(self, accepts):
# Policy example: prefer solana in dev, base in prod
return next((a for a in accepts if a["network"].startswith("base")), accepts[0])
def _build_payment_header(self, req, x402_version):
if req["network"].startswith("solana"):
payload = build_partial_tx(self.wallet, req) # base64 tx
data = {"x402Version": x402_version, "scheme": req["scheme"], "network": req["network"], "payload": {"transaction": payload}}
else:
header = sign_eip3009_header(self.wallet, req, facilitator_url=self.facilitator_url)
data = {"x402Version": x402_version, "scheme": req["scheme"], "network": req["network"], "payload": header}
return base64.b64encode(json.dumps(data).encode()).decode()
def _decode_receipt(self, maybe_b64: Optional[str]):
if not maybe_b64: return None
try: return json.loads(base64.b64decode(maybe_b64.encode()).decode())
except Exception: return None
Server patterns: middleware that “speaks 402”
On the server side, we can simplify things a bit by providing middleware for some of the most popular frameworks:
- Express/Hono/Next.js: The
paymentMiddleware(payTo, { routes… }, facilitator)does a few important things:- For those routes that need a little extra protection, it kicks back a 402 status along with a Payment Requirements body if there’s an unpaid request.
- After you’ve made a payment and decide to retry, it’ll take a quick look through
/verify(this part's optional), handle what needs to be done, settle things up via/settle, and then give you a cool 200 response packed with anX-PAYMENT-RESPONSE. (docs.cdp.coinbase.com)
Take a look at how Cloudflare and Coinbase tackle this. Your middleware should mirror this approach:
- Pricing per Route - Make sure to outline the costs for every single route.
- Set Network - Pick the network that suits your needs best.
- Description/Input/Output Schemas - Include schemas so everything’s easy to find.
- Facilitator URL for Settlement - Link up to a facilitator URL for hassle-free settlement.
If you want to learn more, check out the Cloudflare developers guide. It’s packed with great info!
TypeScript: Express middleware sketch (server)
import express from "express";
import { verifyPayment, settlePayment, build402Body } from "./x402-server";
const app = express();
app.use("/v1/report", async (req, res, next) => {
const price = "$0.01"; // or TokenAmount in atomic units
const network = process.env.NETWORK || "base"; // "base" (mainnet) or "base-sepolia" (test)
const payTo = process.env.MERCHANT_ADDR!;
// If no X-PAYMENT, return the 402 challenge
if (!req.headers["x-payment"]) {
return res.status(402).json(build402Body({
resource: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
price, network, payTo,
description: "Daily metrics report (CSV)",
mimeType: "text/csv",
maxTimeoutSeconds: 60
}));
}
// Verify first (fast and cheap)
const { isValid, invalidReason } = await verifyPayment({
paymentHeader: String(req.headers["x-payment"]),
requirements: { network, payTo, price, scheme: "exact" },
facilitator: { url: process.env.FACILITATOR_URL! }
});
if (!isValid) {
res.setHeader("X-PAYMENT-RESPONSE", /* base64({ success:false, error: invalidReason }) */ "");
return res.status(402).json(build402Body({ resource: req.originalUrl, price, network, payTo, error: invalidReason }));
}
// Do the work, then settle atomically (or use x402-exec-style router to bundle fulfillments)
const settlement = await settlePayment({
paymentHeader: String(req.headers["x-payment"]),
requirements: { network, payTo, price, scheme: "exact" },
facilitator: { url: process.env.FACILITATOR_URL! }
});
res.setHeader("X-PAYMENT-RESPONSE", settlement.base64Header);
res.type("text/csv").send("date,metric\n2025-12-01,1234\n");
});
The spec covers the /verify and /settle facilitator endpoints, plus the X-PAYMENT-RESPONSE header schema. So, don’t forget to develop your middleware according to that contract. You can take a look at it right here: github.com.
EVM details your SDK must get right (USDC/EIP‑3009)
On the EVM, the concrete v1 scheme is “exact” with EIP‑3009 TransferWithAuthorization signatures, which is set to USDC by default. So, what's the deal with EIP‑3009? It makes gasless, single-step transfers possible: the client signs off-chain, a facilitator covers the gas fees, and then everything gets executed on-chain. If you want to dive deeper, check out more details here.
- If you're working with USDC on the Base mainnet (which is pretty much the norm for production), just a heads-up that it has 6 decimals, and the contract address you’ll want to use is 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913. It's always a good idea to double-check that you're dealing with the right asset and its decimals. You can find more details here.
- Steer clear of hardcoding EIP‑712 domain values. Instead, grab the token’s EIP‑712 name and version right from the contract using functions like name() and version(). You should toss these into either requirements.extra or your signer--this way is recommended in the x402 docs. Check it out here.
- Here are a few security tips to keep in mind:
- Keep your validBefore/validAfter windows on the shorter side--aim for around 60 to 120 seconds--to help ward off replay attacks. Also, make sure to use a 32-byte random nonce. Don’t forget to check authorizationState/AuthorizationUsed before accepting any duplicates. More info can be found here.
- Make sure that authorization.to matches payTo, the value is less than or equal to maxAmountRequired, and verifyingContract is equal to the asset.
- And of course, don’t overlook checking that the chainId and network align with the specified requirements.
If you need a bit of background, L402 (Lightning’s 402+macaroons) was the pioneer of the “pay equals auth” pattern, and then x402 took that and ran with it across different rails. This can really come in handy as a concept reference when you’re going through your design reviews. Take a look here: (docs.lightning.engineering)
Solana details your SDK must get right
On Solana, they mix things up a bit by opting for a partially-signed transaction payload instead of sticking with EIP-3009:
- So, the X‑PAYMENT payload is where you'll find a base64‑encoded, partially signed transaction. Here’s the deal: the client takes the first swing at signing it, and then the facilitator steps in to add their cosign as the fee payer. You’ll usually spot
feePayerunderrequirements.extra. (solana.com) - Now, about those assets--we're talking about the SPL mint, like the USDC mint EPjFWd… on Solana. Just a quick tip: the value is in atomic units, and keep in mind that USDC has 6 decimal places. Make sure to validate those ATAs and decimals on the server side before you wrap things up. (docs.bridge402.tech)
- Your SDK is there to help with building the transaction, signing it with the user’s keypair, and then just firing off the b64 transaction in the payload. After that, the facilitator will check it out and take care of broadcasting it on /settle. (solana.com)
Facilitators: verify, settle, and abstract chains
x402 lets the resource server decide whether to handle verification or settlement on its own or to shoot over a POST request to a facilitator. Here’s a quick rundown of what the spec says:
- POST /verify →
{ isValid, invalidReason } - POST /settle →
{ success, txHash, networkId, error } - GET /supported → lists the schemes and networks that are currently advertised
This way, your server stays HTTP-native, which means you can skip any node management hassles. Just ensure your SDK uses a facilitator object and hits up its endpoints whenever necessary. Give it a look on GitHub!
Coinbase has put together a really helpful quickstart guide that points out the testnet and mainnet toggles, plus all the metadata you need for easy navigation. Meanwhile, Cloudflare has detailed how to wrap fetch in agents and manage automatic payments. It’s a clever way to mirror those APIs so developers have a more familiar environment. You can dive into it here: (docs.cdp.coinbase.com)
Receipts and observability
Don't forget to always include the X‑PAYMENT‑RESPONSE header, which is in base64 JSON format. At a minimum, make sure you include the success status, txHash, and the network. If something goes wrong, be sure to toss in error codes like insufficient_funds, invalid_signature, expired, and amount_mismatch. This little addition to your SDK response object can really enhance your analytics and support efforts. If you want to see how the header format works, take a peek at the reference README over here: (github.com).
Idempotency and retries
- Client-side: Keep track of the last X-PAYMENT for a request hash (which includes the method + URL + body) as long as it's still within the validBefore timeframe. If you hit a temporary error on the first retry, just shoot it over again using the same header.
- Server-side: Set up the EIP-3009 nonce and make use of the on-chain AuthorizationUsed to effectively filter out duplicates. For Solana, if a recent blockhash/nonce/tx signature resurfaces, it should be turned away. (eips.ethereum.org)
Pricing strategies: exact today, “upto” emerging
- exact: Here, the server decides on a price, and the client simply pays that precise amount (in atomic units). This is the usual approach and must be supported by all v1 SDKs. (github.com)
- upto (emerging): Start by checking the maximum amount, then get the job done, and finally agree on the final price based on that max. This method is a great match for AI token-metered pricing. When you're putting together your SDK APIs, be sure they can handle various pricing schemes; third-party stacks are already diving into this split-phase approach. (portal.thirdweb.com)
“Settle and execute” patterns (advanced)
A ton of apps out there have to link payments with business logic smoothly--like when you're dealing with splits, mints, or other processes that happen down the line. Make sure to watch proposals like x402‑exec (which combines the SettlementRouter with hook execution). This way, your SDK can send out "execution hints" to facilitators when everything's operational. It's a great way to keep payment and fulfillment consistent. You can take a look at it here: github.com
Discovery and agent UX
Two Key Integrations Your SDK Should Consider:
- Bazaar (discovery): When sellers partner up with a facilitator like CDP, their endpoints can be automatically listed, complete with useful description/input/output schemas. This simplifies the process for agents looking to find and engage with them. Just ensure your SDK is capable of reading these schemas so it can automatically populate tool definitions for you. (docs.cdp.coinbase.com)
- MCP/Agents: Make sure to wrap your fetching with payment in your agents (or pass it along with withX402Client for MCP). This lets you tag tools as “paid” and automatically get confirmation through an onPaymentRequired callback. If you need some guidance, Cloudflare has a great example of how to do this. (developers.cloudflare.com)
Security checklist for SDK maintainers
- Double-check everything:
- Confirm that the schemes, networks, assets, and payTo are all in sync.
- Ensure that the amount doesn’t exceed maxAmountRequired; take care when dealing with those atomic units (remember to consider BigInt/decimal).
- Enforce time windows:
- Keep your validAfter and validBefore windows pretty short, around 30-120 seconds. This can really help reduce the chances of replay attacks.
- Nonce Discipline:
- Make sure to use a 32-byte random nonce. Keep tabs on
authorizationStateand check out the nonces you’ve already used on the EVM. When it comes to Solana, just be cautious about blockhash timeouts and make sure you're using unique signers. (eips.ethereum.org)
- Make sure to use a 32-byte random nonce. Keep tabs on
- Sign exactly what the server advertised:
- Link the resource URL and possibly a server-generated nonce to cut down on header reuse across various endpoints. Just remember to add this in
requirements.extraand explain how you come up with that authorization nonce.
- Link the resource URL and possibly a server-generated nonce to cut down on header reuse across various endpoints. Just remember to add this in
- Avoid hardcoding the USDC EIP-712 name/version:
- Instead of hardcoding, pull it directly from the contract using
name()andversion()as recommended in the network support docs. (x402.gitbook.io)
- Instead of hardcoding, pull it directly from the contract using
- Handle facilitator errors gracefully:
- Clearly differentiate between verification failures and settlement issues. It's important to display the error clearly and offer a receipt for reference.
Compliance and risk notes
- If you want to take your production-grade compliance and operations on mainnet to the next level, facilitators are where it’s at! They cover everything from API keys to rate limits and monitoring. Just remember to keep your SDK defaults aligned with the facilitators you're using for production and testnets. For all the nitty-gritty details, check this out: (docs.cdp.coinbase.com).
- While things like sanctions, geofencing, fraud checks, and tax invoices aren't included in the x402 core, facilitators typically take care of those. So, it’s smart to plan for extension points that allow merchants to set up their own policies without interfering with the core handshake.
End‑to‑end example: EVM “exact” signing (USDC/EIP‑3009)
Pseudocode to Sign the TransferWithAuthorization Payload in Your SDK
Sure thing! Let's dive straight into signing the TransferWithAuthorization payload. We'll keep this on the down-low for now, but we’ll make the buildPaymentHeader function available for use. Here’s the scoop on how to get it done:
function buildPaymentHeader(transferPayload):
// Step 1: Preprocess the payload
preprocessedData = preprocessPayload(transferPayload)
// Step 2: Create a signature
signature = createSignature(preprocessedData)
// Step 3: Build the payment header
paymentHeader = {
"Authorization": "Bearer " + signature,
"Content-Type": "application/json"
}
return paymentHeader
function preprocessPayload(payload):
// Do some magic here to make sure the payload is in the right format
return formattedPayload
function createSignature(data):
// Sign the data using your secret key or whatever method you prefer
return signedData
Explanation of the Process:
- Preprocess the Payload:
- First things first, we need to get the
transferPayloadready. This usually involves giving it the proper format so it’s all set for signing.
- First things first, we need to get the
- Create a Signature:
- Now, let’s whip up a signature using the preprocessed data. This part is super important because it helps secure your payload.
- Build the Payment Header:
- In the end, we’ll put it all together into a tidy header that has the authorization token (that shiny generated signature) and also tells you about the content type.
And there you have it! This is a straightforward way to sign your TransferWithAuthorization payload while keeping everything organized in your SDK.
import { keccak256, encodeAbiParameters } from "viem"; // or use a typed-data helper
type Eip3009Auth = {
from: `0x${string}`;
to: `0x${string}`;
value: bigint; // atomic units
validAfter: bigint; // unix
validBefore: bigint; // unix
nonce: `0x${string}`; // 32 bytes
};
export async function signEip3009USDC(
wallet, // viem wallet client or ethers.js signer
tokenAddress: `0x${string}`, // USDC verifyingContract
domainName: string, // read via name()
domainVersion: string, // read via version() (often "2")
chainId: number,
auth: Eip3009Auth
) {
const typedData = {
domain: { name: domainName, version: domainVersion, chainId, verifyingContract: tokenAddress },
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" },
],
},
primaryType: "TransferWithAuthorization" as const,
message: auth,
};
const signature = await wallet.signTypedData(typedData);
return { authorization: auth, signature }; // put inside payload for X-PAYMENT
}
This connects to the ERC‑3009 spec used by x402 “exact” on EVM. Your SDK generates a Payment Payload that includes this signature, which is then base64-encoded into X-PAYMENT. You can take a closer look here: (eips.ethereum.org)
Testing matrix (what we run before a release)
- EVM: base‑sepolia (test), base (mainnet)
- We’re all set to go with USDC transferWithAuthorization through the facilitator
- The TokenAmount and price string (“$0.001”) parsing is running smoothly together
- We're nailing the ValidAfter/ValidBefore expiry handling like champs
- Solana: devnet and mainnet
- Now, there's a path for partially signed transactions; the feePayer is set via requirements.extra.
- If there's a problem with the mint or decimals, you’ll get a rejection.
- Interop:
- 402 → X‑PAYMENT’s got your back and will try again no matter what HTTP method you’re using (GET/POST/PUT).
- And hey, they’re on top of idempotency! So, if you send the same X‑PAYMENT request more than once, those duplicates will be turned away, and your receipt will just show the same error every time.
Performance and UX tips
- Minimize round‑trips: Keep a cache of the most recent accepted entries from the service. If you’re already aware of them, feel free to whip up a header and jump right into a paid call. Just a heads-up, the spec allows you to skip that initial 402 if you're already clued in on the requirements. (github.com)
- Human-in-the-loop: Make sure you set up a
confirmPaymentcallback. Your wrapper needs to send the parsed requirements--like price, network, and description--and hang tight for that thumbs-up. This step is super important, particularly for consumer interfaces and keeping tabs on corporate spending. (developers.cloudflare.com) - Metadata matters: Sellers, listen up! Adding details like description, inputSchema, and outputSchema is a game changer. It really helps agents get found and makes it easier for tools to link up (kind of like Bazaar). Don’t forget to make sure your SDK showcases these fields. (docs.cdp.coinbase.com)
Roadmap readiness: smart wallets and beyond
- There's quite a bit of excitement around smart accounts (EIP‑4337) for x402! The whole idea is to set up pluggable signing, which means your SDK can easily switch from EIP‑3009 auth to a UserOperation when the time comes. Plus, you can keep an eye on gas sponsorship by using requirements.extra. If you're curious, check out the full discussion here.
- It’s super important to stay flexible with your setup: what works today might not work tomorrow. Try not to lock your signer into specific business logic; instead, let your server's requirements shape the way things operate. You can find more details here.
Rollout plan for your organization
- Pilot on Testnets
- Jump right into base-sepolia or explore the Solana devnet facilitator (you'll find public endpoints ready for those speedy setups). Let's get at least one secure route up and running alongside one agent client. For all the nitty-gritty details, check out the docs.cdp.coinbase.com.
2) Harden
- Get started on receipt logging, idempotency keys, and price monitors. Remember to check the EIP‑712 domain reads from contracts during CI. You can find more details here.
3) Activate Mainnet
- Switch the swap facilitator settings over to mainnet, keeping the same SDK interfaces in play. Don’t forget to set the treasury wallets and reconciliation dashboards to utilize the X-PAYMENT-RESPONSE. You can find all the info you need right here: docs.cdp.coinbase.com.
4) Extend
- We’re here to support both the EVM and Solana platforms! Don't forget to highlight the rich metadata to make discovery a breeze. Also, it might be worth considering the integration of x402-exec-style hooks for those atomic “pay-and-fulfill” transactions. You can dive into more details on GitHub!
Bottom line
x402 makes payments as simple as sending an HTTP request. It’s clear-cut, stateless, and tailored for machines to digest easily. The magic lies in your SDK: just snag one status code, sign one payload, and if you don’t nail it the first time, give it another shot. You’ll end up with both the data and a receipt in hand. Just focus on those reference headers and flows, streamline the wallet and facilitator sections, and you’ll create a “pay-per-request” system that developers will truly love.
Further Reading and References:
- Swing by MDN for an in-depth look at HTTP 402 Payment Required. (developer.mozilla.org)
- Ready to dive deep? Check out the Coinbase x402 spec. It’s got everything you need to know about headers, schemas, the facilitator API, and some handy quickstart guides. You can find it all here: (github.com)
- Dive into Cloudflare Agents to discover useful tools like
wrapFetchWithPaymentand thex402middleware for Workers/MCP. Check it out here! - Make sure you check out the ERC‑3009 spec for TransferWithAuthorization (that's USDC, if you're curious). (eips.ethereum.org)
- Lastly, take a look at L402/LSAT. It’s all about linking payment to authentication, built on the 402 precedent. You can find more about it here: (docs.lightning.engineering).
Like what you're reading? Let's build together.
Get a free 30-minute consultation with our engineering team.
Related Posts
ByAUJay
Building a Donation-Based Crowdfunding Platform That Gives Tax Receipts
**Summary:** Donation-based crowdfunding that includes tax receipts has become quite the complex puzzle across different regions. You've got to navigate IRS Pub 1771/526 rules, UK Gift Aid declarations, Canada’s CRA receipting, and the new eIDAS/OpenID4VCI wallets--all while keeping everything running smoothly.
ByAUJay
Why 'Full-Lifecycle Advisory' Beats Just Coding
**Summary:** Engineering teams that focus solely on “writing Solidity” often find themselves caught off guard by shifts in protocols, the need for composable security, and the procurement hurdles that are now impacting real ROI. Our full-lifecycle advisory service bridges the gap by connecting EIP-7702 smart accounts, modular decentralized applications (DA), and ZK-based compliance solutions.
ByAUJay
Why Your Project Could Really Use a 'Protocol Economist
Summary: A lot of Web3 teams are missing a crucial player: the “protocol economist.” And you can really see the impact--value slips away through MEV routing, token incentives that are all out of whack, and those sneaky changes to wallets after Pectra that end up messing with the unit economics. In this playbook, we’ll explore what a protocol economist can do to tackle these issues head-on.

