Les clés d'API ont été conçues pour des humains. Un développeur crée un compte, lit la page tarifaire, saisit une carte bancaire, copie un secret dans un fichier .env, et dès lors la clé identifie qui appelle. Tout ce rituel suppose qu'une personne se trouve à l'autre bout, capable de remplir un formulaire d'inscription.
Les agents IA font voler cette hypothèse en éclats. Un agent qui découvre votre API météo à l'exécution ne peut pas créer de compte, ne peut pas passer un KYC, et ne peut pas attendre trois jours la réponse de votre équipe commerciale. En revanche, il peut signer un paiement et rejouer une requête en une seconde environ.
x402 est le protocole ouvert qui rend cela possible. Il réveille le code de statut HTTP 402 Payment Required, resté dormant pendant des décennies, et en fait une véritable couche de paiement : le serveur répond à une requête non payée par un 402 accompagné d'exigences de paiement lisibles par machine, le client signe un transfert en stablecoin, puis rejoue la requête avec la signature attachée. Sans compte, sans session, sans clé d'API.
Ce tutoriel construit les deux moitiés — le vendeur (une API Next.js facturée à l'appel) et l'acheteur (un agent qui paie sans demander la permission à personne) — puis branche l'endpoint payant sur un serveur MCP pour que Claude l'utilise comme outil.
Note de version : tout ce qui suit cible x402 v2, qui a introduit les en-têtes PAYMENT-SIGNATURE et PAYMENT-RESPONSE, les identifiants réseau CAIP-2 et la famille de paquets scopés @x402/*. Si vous tombez sur des tutoriels plus anciens utilisant x402-next (sans scope) et un en-tête X-PAYMENT, il s'agit de la v1 — la migration est modeste, mais les API ne sont pas interchangeables.
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ et pnpm (ou npm)
- Next.js 15 ou 16 avec l'App Router et TypeScript
- De l'aisance avec async/await, les route handlers et le middleware
- Une compréhension basique de ce qu'est une adresse de portefeuille — nul besoin d'avoir écrit un smart contract
- Environ 5 USDC sur le testnet Base Sepolia (gratuits via n'importe quel faucet Base) pour le côté acheteur
- Facultativement, Claude Desktop ou un autre client MCP pour la dernière étape
Vous n'avez besoin ni d'un compte Coinbase, ni d'une clé Coinbase Developer Platform, ni d'aucun produit Coinbase. x402 est sous licence Apache-2.0 et le facilitateur testnet à l'adresse https://x402.org/facilitator est ouvert à tous.
Ce que vous allez construire
À la fin de ce tutoriel, vous disposerez de :
- Une configuration partagée de serveur de ressources capable de vérifier et de régler les paiements
- Un route handler Next.js protégé par
withX402facturé 0,001 $ l'appel - Un proxy basé sur le middleware qui tarifie plusieurs routes d'un coup, palier premium compris
- Un client acheteur — un script autonome qui appelle l'endpoint, reçoit un 402, signe et rejoue
- Un serveur MCP qui expose l'API payante comme outil Claude, avec plafonds de dépense
- Une checklist pour passer de Base Sepolia au mainnet Base
Étape 1 : comprendre la poignée de main 402
Avant d'écrire du code, il est utile de savoir exactement ce qui circule sur le fil. Le flux complet tient en six mouvements :
- Le client demande
GET /api/weathersans paiement. - Le serveur répond
402 Payment Requiredavec un en-têtePAYMENT-REQUIREDcontenant du JSON encodé en Base64. À l'intérieur se trouve un tableauaccepts— une entrée par option de paiement acceptée (réseau, actif, prix, adresse de destination, schéma). - Le client choisit une entrée qu'il peut honorer, construit une charge utile de paiement pour le schéma de cette entrée, et la signe avec la clé de son portefeuille. Pour le schéma
exactsur une chaîne EVM, il s'agit d'une signatureTransferWithAuthorizationconforme à EIP-3009 — une autorisation hors chaîne de déplacer un montant précis d'USDC, sans approbation on-chain préalable de l'acheteur. - Le client rejoue la requête à l'identique, portant cette fois un en-tête
PAYMENT-SIGNATUREavec la charge utile signée encodée en Base64. - Le serveur transmet la charge utile à un facilitateur — un service qui vérifie que la signature correspond aux exigences (
POST /verify) puis diffuse le transfert on-chain (POST /settle). Le facilitateur ne détient jamais les fonds ; il ne fait que relayer une signature déjà produite par l'acheteur. Un facilitateur qui altère le montant produit une signature invalide et le transfert échoue. - Le serveur renvoie
200 OKavec la ressource, plus un en-têtePAYMENT-RESPONSEcontenant le reçu de règlement encodé en Base64.
Trois propriétés découlent de cette conception et méritent d'être intégrées :
- C'est sans état. Le serveur ne stocke rien sur l'acheteur d'un appel à l'autre. Ni session, ni compteur de rate limit rattaché à un compte, ni table utilisateurs.
- L'adresse du portefeuille fait office d'identité. Si vous voulez des analytics par appelant ou des listes d'autorisation, c'est l'adresse du payeur qui sert de clé.
- Le règlement est un push irréversible. Le schéma
exactignore la rétrofacturation. Les remboursements, si vous en proposez, sont un second transfert que vous déclenchez depuis votre logique métier.
Étape 2 : mise en place du projet
Partez d'une application Next.js utilisant l'App Router. Installez les paquets côté vendeur :
pnpm add @x402/next @x402/core @x402/evmLa découpe est délibérée : @x402/core contient la mécanique du protocole, @x402/evm implémente le schéma exact pour les chaînes EVM, et @x402/next fournit les liaisons Next.js. Si vous souhaitez aussi accepter les paiements Solana, ajoutez @x402/svm.
Il vous faut maintenant une adresse de portefeuille pour recevoir les paiements. Côté vendeur, seule l'adresse est nécessaire — publique, sans risque dans .env.local et même exposable dans un bundle client. Le vendeur ne signe rien : aucune clé privée n'est requise sur votre serveur.
Si vous n'avez pas encore d'adresse, générez une paire de clés jetable pour le testnet :
node -e "const {generatePrivateKey,privateKeyToAccount}=require('viem/accounts');const k=generatePrivateKey();console.log('PRIVATE_KEY=',k);console.log('ADDRESS=',privateKeyToAccount(k).address)"Ajoutez l'adresse à .env.local :
# .env.local
NEXT_PUBLIC_EVM_ADDRESS=0xYourReceivingAddressHereAttention : la clé privée affichée ci-dessus appartient au côté acheteur, plus loin dans ce tutoriel, et ne doit jamais atteindre un bundle navigateur ni un commit Git. Seule l'adresse est publique.
Étape 3 : configurer le serveur de ressources
Toutes les routes protégées partagent une même instance x402ResourceServer. Elle réunit le client facilitateur et les schémas de paiement que vous acceptez. Créez x402.ts à la racine du projet :
// x402.ts
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";
// Facilitateur testnet — gratuit, ouvert, sans identifiants.
// Remplacez l'URL par un facilitateur mainnet au lancement (étape 9).
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.org/facilitator",
});
export const server = new x402ResourceServer(facilitatorClient);
// Enregistre le schéma "exact" pour toutes les chaînes EIP-155.
// Le joker couvre Base, Base Sepolia et tout autre réseau EVM
// que vous listerez plus tard dans le tableau accepts d'une route.
server.register("eip155:*", new ExactEvmScheme());
export const evmAddress = process.env.NEXT_PUBLIC_EVM_ADDRESS as `0x${string}`;
if (!evmAddress) {
throw new Error("NEXT_PUBLIC_EVM_ADDRESS is not set — payments cannot be received");
}Deux détails comptent ici.
Les réseaux utilisent des identifiants CAIP-2, pas des noms conviviaux. Base Sepolia est eip155:84532 ; le mainnet Base est eip155:8453. Se tromper là-dessus est de loin la première cause du fameux « ma signature est rejetée » — l'identifiant de chaîne fait partie de ce que l'acheteur signe, si bien qu'un décalage invalide la charge utile au lieu de produire une erreur explicite.
L'enregistrement du schéma côté serveur ne prend aucun signataire. Comparez avec l'acheteur à l'étape 6, où ExactEvmScheme reçoit un signataire. Même nom de classe, chemin d'import opposé (/server contre /client), responsabilité opposée : le serveur vérifie, l'acheteur signe.
Étape 4 : facturer une route unique
La façon la plus propre de tarifer un endpoint est withX402, qui enveloppe directement un route handler :
// app/api/weather/route.ts
import { NextRequest, NextResponse } from "next/server";
import { withX402 } from "@x402/next";
import { server, evmAddress } from "@/x402";
const handler = async (req: NextRequest) => {
const city = req.nextUrl.searchParams.get("city") ?? "Tunis";
// Votre vrai travail ici — requête en base, appel de modèle,
// ou API tierce que vous revendez.
const report = await getForecast(city);
return NextResponse.json({ city, report }, { status: 200 });
};
export const GET = withX402(
handler,
{
accepts: [
{
scheme: "exact",
price: "$0.001",
network: "eip155:84532", // Base Sepolia
payTo: evmAddress,
},
],
description: "Current weather forecast for a city",
mimeType: "application/json",
},
server,
);Voilà toute l'intégration. Un GET /api/weather non payé renvoie désormais un 402 avec les exigences ; un appel payé exécute handler et renvoie les prévisions.
Pourquoi withX402 plutôt que le middleware ? À cause du moment du règlement. withX402 ne règle le paiement qu'après le retour d'une réponse réussie par votre handler — statut inférieur à 400. Si getForecast lève une exception, ou renvoie un 503 parce que le fournisseur amont est indisponible, l'acheteur n'est pas débité. L'interception par middleware règle avant l'exécution de votre handler et ne peut offrir cette garantie. Pour tout ce qui peut échouer, enveloppez le handler.
À propos du format de prix : utilisez toujours la forme chaîne préfixée du dollar, "$0.001". Omettre le $ déclenche une erreur de validation plutôt que d'être interprété comme un montant brut de token. En interne cela se résout en USDC — six décimales — donc $0.001 vaut 1000 unités de base. Le plancher pratique se situe autour de $0.0001 ; en dessous, les arrondis commencent à mordre.
Étape 5 : tarifer plusieurs routes d'un coup
Quand vous avez une famille d'endpoints, déclarer accepts sur chaque handler devient répétitif. paymentProxy vous permet d'écrire la grille tarifaire une seule fois et de l'appliquer via le middleware Next.js.
Étendez x402.ts :
// x402.ts (suite)
import { paymentProxy } from "@x402/next";
const priced = (price: string, description: string) => ({
accepts: [
{
scheme: "exact" as const,
price,
network: "eip155:84532" as const,
payTo: evmAddress,
},
],
description,
mimeType: "application/json",
});
export const proxy = paymentProxy(
{
"/api/weather": priced("$0.001", "Current weather forecast"),
"/api/forecast/extended": priced("$0.01", "14-day extended forecast"),
"/api/historical": priced("$0.05", "Historical weather archive, per query"),
},
server,
);Puis branchez-le dans middleware.ts :
// middleware.ts
export { proxy as middleware } from "@/x402";
export const config = {
matcher: [
"/api/weather",
"/api/forecast/:path*",
"/api/historical/:path*",
],
runtime: "nodejs",
};Notez runtime: "nodejs". La vérification des paiements s'appuie sur des primitives cryptographiques indisponibles sur le runtime Edge : le matcher doit donc opter pour Node.
Les paliers ci-dessus illustrent un principe à reprendre : différenciez par coût de service, pas par une échelle d'abonnements arbitraire. Une consultation des conditions actuelles servie depuis le cache ne coûte presque rien : facturez un dixième de centime. Une requête sur l'archive historique parcourt du stockage réel : facturez cinquante fois plus. Comme il n'y a aucun forfait à négocier, les agents se dirigent vers le palier que leur budget supporte — vous n'imposez pas un engagement à 99 $/mois à un appelant qui veut neuf requêtes.
Mélanger les deux approches est parfaitement valable, et souvent judicieux : paymentProxy pour les endpoints bon marché, fiables et en lecture seule, withX402 pour les coûteux dont vous voulez conditionner le règlement au succès. Veillez simplement à ce qu'une route ne soit pas couverte par les deux, sans quoi l'acheteur paie deux fois.
Étape 6 : construire l'acheteur
Passons à l'autre côté. Créez un petit projet séparé — ou un dossier scripts/ dans le même dépôt — pour l'agent qui consomme l'API :
pnpm add @x402/fetch @x402/core @x402/evm viem dotenvL'acheteur a besoin d'un signataire : c'est ici que vit la clé privée. Placez-la dans le .env propre à l'acheteur, jamais dans l'application Next.js :
# buyer/.env
EVM_PRIVATE_KEY=0xthe_key_you_generated_in_step_2
RESOURCE_SERVER_URL=http://localhost:3000La configuration du client reflète celle du serveur, avec un signataire en plus :
// buyer/client.ts
import { wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import { config } from "dotenv";
config();
const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = new x402Client();
client.register("eip155:*", new ExactEvmScheme(signer));
export const fetchWithPayment = wrapFetchWithPayment(fetch, client);
export const httpClient = new x402HTTPClient(client);
export const payerAddress = signer.address;Et l'appel lui-même ressemble à un fetch ordinaire :
// buyer/main.ts
import { fetchWithPayment, httpClient, payerAddress } from "./client";
const base = process.env.RESOURCE_SERVER_URL!;
async function main() {
console.log("Paying from", payerAddress);
const response = await fetchWithPayment(`${base}/api/weather?city=Tunis`, {
method: "GET",
});
const result = await httpClient.processResponse(response);
console.log("Data:", result.body);
if (result.paymentStatus === "settled") {
console.log("Settlement receipt:", result.header);
}
}
main().catch((error) => {
console.error("Request failed:", error);
process.exit(1);
});Exécutez-le et vous ne verrez qu'une ligne de log, alors que quatre événements HTTP se sont produits : la requête initiale, le 402, la signature et le rejeu. wrapFetchWithPayment absorbe le tout. processResponse décode ensuite l'en-tête PAYMENT-RESPONSE en reçu de règlement — hash de transaction, montant, réseau — que vous persisterez pour la comptabilité.
Où l'argent circule : l'acheteur a signé une autorisation EIP-3009 ; le facilitateur l'a soumise et a payé le gas. Sur le L2 Base, ce gas avoisine 0,001 $, absorbé par le facilitateur, et le facilitateur de Coinbase ne prélève actuellement aucune commission par-dessus. Les USDC de l'acheteur arrivent dans le portefeuille du vendeur en une seconde environ, sans intermédiaire pour les retenir entre-temps.
Étape 7 : imposer des plafonds de dépense
Un agent muni d'une clé privée et d'une boucle while est un moyen rapide de perdre de l'argent. Ne déployez jamais un acheteur sans plafond.
Le premier garde-fou est structurel : n'approvisionnez le portefeuille de l'agent qu'à hauteur de ce qu'il a le droit de dépenser. Un hot wallet contenant 20 $ d'USDC impose une limite dure et incontournable de 20 $, quel que soit le bug dans votre code. Rechargez-le périodiquement depuis un portefeuille de trésorerie hors de portée de l'agent. Cette seule mesure surpasse tous les garde-fous logiciels, car elle ne dépend pas de la justesse de votre code.
Le second est un budget par processus. Enveloppez le fetch payant :
// buyer/budget.ts
import { fetchWithPayment, httpClient } from "./client";
const BUDGET_USD = Number(process.env.SESSION_BUDGET_USD ?? "0.50");
const MAX_PER_CALL_USD = Number(process.env.MAX_PER_CALL_USD ?? "0.01");
let spent = 0;
export class BudgetExceededError extends Error {}
export async function paidFetch(url: string, init?: RequestInit) {
if (spent >= BUDGET_USD) {
throw new BudgetExceededError(
`Session budget of $${BUDGET_USD} exhausted after $${spent.toFixed(4)}`,
);
}
// Sonder d'abord : une requête non payée révèle le prix sans s'y engager.
const probe = await fetch(url, init);
if (probe.status === 402) {
const requirements = httpClient.parsePaymentRequired(probe);
const quoted = Math.max(
...requirements.accepts.map((a) => Number(String(a.price).replace("$", ""))),
);
if (quoted > MAX_PER_CALL_USD) {
throw new BudgetExceededError(
`Endpoint quoted $${quoted}, above the per-call cap of $${MAX_PER_CALL_USD}`,
);
}
if (spent + quoted > BUDGET_USD) {
throw new BudgetExceededError(
`Call would cost $${quoted}, exceeding the remaining budget`,
);
}
spent += quoted;
}
return fetchWithPayment(url, init);
}
export const spentSoFar = () => spent;Le sondage coûte un aller-retour supplémentaire : un échange équitable pour connaître le prix avant de l'autoriser. Sans lui, un agent signera volontiers ce qu'un serveur malveillant ou mal configuré lui annonce — et un serveur peut annoncer n'importe quoi.
Trois garde-fous supplémentaires à ajouter en production :
- Journalisez chaque règlement. Conservez le hash de transaction, le montant, l'endpoint et l'horodatage. Le règlement étant on-chain et irréversible, vos journaux sont le seul endroit où subsiste la raison d'une dépense.
- Restreignez les destinations par liste d'autorisation. Un agent qui suit des liens peut être orienté vers l'endpoint d'un attaquant. Limitez les hôtes auxquels
paidFetchaccepte de payer. - Mettez agressivement en cache. Le paiement le moins cher est celui que vous ne faites pas. Un cache de 60 secondes sur un endpoint météo élimine l'essentiel de la dépense dans une boucle d'agent bavarde.
Étape 8 : exposer l'API payante à Claude via MCP
Tout ceci vise les agents : faisons-en donc utiliser un. Un serveur MCP peut envelopper l'endpoint payant et le présenter à Claude comme un outil ordinaire — le modèle ne voit jamais le paiement.
pnpm add @modelcontextprotocol/sdk @x402/axios @x402/evm axios viem dotenv// mcp/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { x402Client, wrapAxiosWithPayment } from "@x402/axios";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import axios from "axios";
import { config } from "dotenv";
config();
const baseURL = process.env.RESOURCE_SERVER_URL ?? "http://localhost:3000";
const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const client = new x402Client();
client.register("eip155:*", new ExactEvmScheme(signer));
const api = wrapAxiosWithPayment(axios.create({ baseURL, timeout: 15_000 }), client);
const server = new McpServer({ name: "paid-weather", version: "1.0.0" });
server.tool(
"get_weather",
"Get the current weather for a city. Each call costs $0.001 in USDC.",
{ city: { type: "string", description: "City name, e.g. Tunis" } },
async ({ city }) => {
try {
const res = await api.get("/api/weather", { params: { city } });
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
} catch (error) {
const message = axios.isAxiosError(error)
? `Weather lookup failed (${error.response?.status ?? "network"}): ${error.message}`
: String(error);
return { content: [{ type: "text", text: message }], isError: true };
}
},
);
await server.connect(new StdioServerTransport());Déclarez-le dans Claude Desktop :
{
"mcpServers": {
"paid-weather": {
"command": "pnpm",
"args": ["--silent", "-C", "/absolute/path/to/mcp", "dev"],
"env": {
"EVM_PRIVATE_KEY": "0xyour_testnet_key",
"RESOURCE_SERVER_URL": "http://localhost:3000"
}
}
}
}Redémarrez Claude Desktop, demandez-lui la météo à Tunis, et il appellera l'outil. Derrière cet unique appel d'outil : un 402, une signature EIP-3009, un transfert USDC on-chain et un reçu réglé — rien de tout cela n'ayant occupé le raisonnement du modèle.
Mentionner le coût dans la description de l'outil est délibéré. Cela donne au modèle l'information nécessaire pour éviter les appels répétés gratuits, et ne vous coûte rien.
Étape 9 : passer en mainnet
Le passage de Base Sepolia au mainnet Base représente un petit diff et un grand changement de conséquences. La checklist :
Changez l'identifiant réseau partout où il apparaît — eip155:84532 devient eip155:8453. Comme il s'agit de données signées, un identifiant testnet oublié produit des paiements rejetés plutôt qu'une erreur claire.
Pointez vers un facilitateur mainnet. L'endpoint https://x402.org/facilitator est réservé au testnet. Coinbase Developer Platform exploite un facilitateur de production sur https://api.cdp.coinbase.com/platform/v2/x402, avec règlement sans frais sur Base et Solana ; PayAI en propose une alternative couvrant Base, Solana et Polygon. Le facilitateur est une dépendance interchangeable — il ne détient jamais les fonds — donc en changer plus tard est peu coûteux.
Sortez l'adresse de réception d'une clé chaude. L'adresse du vendeur ne fait que recevoir : elle peut être un portefeuille matériel, un multisig ou une adresse de dépôt sur une plateforme. Rien ne justifie qu'elle soit une clé posée dans .env.local.
Approvisionnez l'acheteur en USDC mainnet et un peu d'ETH. Les USDC de testnet ne valent rien en mainnet, et les acheteurs ont besoin d'un petit solde d'ETH pour les cas limites où ils soumettent eux-mêmes leurs transactions.
Vérifiez que votre tarification résiste au réel. Sur testnet, 0,05 $ la requête est un nombre dans un fichier de configuration. En mainnet, c'est de l'argent qui part sans retour, au volume que décide un agent. Modélisez le cas d'un appelant unique envoyant dix mille requêtes en une heure : est-ce un revenu qui vous réjouit, ou une infrastructure que vous ne pouvez pas vous permettre de servir ?
Une configuration pilotée par l'environnement garde cela maîtrisable :
// x402.ts
const IS_PRODUCTION = process.env.NODE_ENV === "production";
export const NETWORK = IS_PRODUCTION ? "eip155:8453" : "eip155:84532";
const facilitatorClient = new HTTPFacilitatorClient({
url: IS_PRODUCTION
? "https://api.cdp.coinbase.com/platform/v2/x402"
: "https://x402.org/facilitator",
});Tester votre implémentation
Vérifiez d'abord la forme du 402. Avant d'impliquer le moindre portefeuille, confirmez que le serveur parle bien le protocole :
curl -i http://localhost:3000/api/weather?city=TunisVous attendez HTTP/1.1 402 Payment Required et un en-tête PAYMENT-REQUIRED. Décodez-le pour voir les exigences sur lesquelles l'acheteur va agir :
curl -sI http://localhost:3000/api/weather | grep -i payment-required | cut -d' ' -f2 | base64 -d | jqVérifiez que network, payTo et price correspondent à votre intention. Un payTo à undefined signifie que votre variable d'environnement n'a pas été chargée.
Exécutez ensuite l'acheteur de bout en bout et contrôlez la transaction sur BaseScan pour Sepolia. Cherchez l'adresse du payeur ; le transfert USDC doit apparaître en quelques secondes. C'est la seule preuve que le règlement a réellement eu lieu, et pas seulement qu'il a été annoncé.
Testez le chemin d'échec. Faites lever une exception à votre handler et confirmez que l'acheteur n'est pas débité avec withX402. C'est la garantie pour laquelle vous avez choisi withX402 : vérifiez-la plutôt que de la supposer.
Testez le garde-fou budgétaire. Fixez SESSION_BUDGET_USD=0.002 et bouclez ; le troisième appel doit lever BudgetExceededError au lieu de dépenser.
Dépannage
Toujours un 402 après avoir attaché PAYMENT-SIGNATURE. Presque toujours l'une de ces trois causes : l'identifiant de chaîne de la signature ne correspond pas à celui des exigences ; le montant signé est inférieur au montant requis ; ou le portefeuille payeur manque d'USDC. Le corps JSON du serveur porte un champ error qui nomme laquelle — lisez-le avant de deviner.
« Ça marche sur Sepolia, ça échoue en mainnet ». Vous avez changé l'URL du facilitateur mais pas l'identifiant réseau, ou l'inverse. Les deux doivent bouger ensemble. Vérifiez également que le portefeuille détient des USDC mainnet — le solde testnet ne se reporte pas.
Erreurs de runtime Edge dans le middleware. La vérification des paiements a besoin du crypto de Node. Ajoutez runtime: "nodejs" à l'export config de votre middleware.
L'acheteur signe mais rien ne se règle. Vérifiez que l'URL du facilitateur est joignable depuis votre serveur, pas seulement depuis votre poste. Dans un déploiement conteneurisé, la sortie réseau vers le facilitateur est une dépendance à autoriser explicitement.
Prix rejetés comme invalides. Le préfixe $ est obligatoire. "0.001" est une erreur de validation, pas un synonyme de "$0.001".
Double facturation. Une route couverte à la fois par le middleware paymentProxy et par withX402 exigera deux paiements. Choisissez-en un par route.
Pour aller plus loin
- Ajoutez des analytics d'usage indexées sur l'adresse du payeur — en l'absence de comptes, l'adresse du portefeuille est votre seule dimension d'appelant, et c'en est une bonne.
- Explorez le schéma
upto, en cours de développement, qui règle le montant final d'après un usage mesuré (tokens générés, mégaoctets transférés) plutôt qu'un prix fixe convenu d'avance. - Combinez x402 avec Web Bot Auth et RFC 9421 pour à la fois identifier et facturer le trafic des agents.
- Lisez le tutoriel sur les serveurs MCP si l'étape 8 est allée plus vite que vous ne l'auriez voulu.
- Relisez les garde-fous pour agents IA — un agent capable de dépenser transforme l'injection de prompt en problème financier, et plus seulement informationnel.
Conclusion
x402 retire le formulaire d'inscription du milieu du commerce d'API. Un vendeur déclare un prix dans la configuration d'une route ; un acheteur signe et rejoue ; le règlement arrive en une seconde environ avec des frais quasi nuls. Aucun des deux n'entretient de compte pour l'autre.
Pour quiconque expose des API sur un internet peuplé d'agents, cela change l'économie de la longue traîne. Des endpoints qui n'auraient jamais justifié un forfait à 29 $/mois — une consultation unique, une conversion de document, les données d'une seule région — deviennent viables à un dixième de centime l'appel, parce que le coût de transacter est enfin passé sous la valeur d'une requête isolée.
L'ingénierie est réellement modeste : une configuration serveur partagée, un wrapper par route, un fetch enveloppé côté acheteur. C'est la discipline qui demande du travail. N'approvisionnez les portefeuilles d'agents qu'à hauteur de ce que vous acceptez de perdre, sondez les prix avant de les autoriser, journalisez chaque règlement, et rappelez-vous que les paiements on-chain ne reviennent pas. Réussissez cela et vous pourrez confier un portefeuille à un agent sans en perdre le sommeil.