---
description: Add x402 payment handling to OpenCode and Claude Code.
title: Pay from coding tools
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/agents/llms.txt  
> Use this file to discover all available pages before exploring further.

# Pay from coding tools

Last updated Aug 24, 2026|Copy as Markdown|[View as Markdown](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/agents/tools/payments/x402/pay-with-tool-plugins/index.md)|[Agent setup](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/agent-setup/)

The following examples show how to add x402 payment handling to AI coding tools. When the tool encounters a 402 response, it pays automatically and retries.

Both examples require:

* A wallet private key (set as `X402_PRIVATE_KEY` environment variable)
* The x402 packages: `@x402/fetch`, `@x402/evm`, and `viem`

## OpenCode plugin

OpenCode plugins expose tools to the agent. To create an `x402-fetch` tool that handles 402 responses, create `.opencode/plugins/x402-payment.ts`:

```ts
// Use base-sepolia for testing. Get test USDC from https://faucet.circle.com/
import type { Plugin } from "@opencode-ai/plugin";
import { tool } from "@opencode-ai/plugin";
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

export const X402PaymentPlugin: Plugin = async () => ({
	tool: {
		"x402-fetch": tool({
			description:
				"Fetch a URL with x402 payment. Use when webfetch returns 402.",
			args: {
				url: tool.schema.string().describe("The URL to fetch"),
				timeout: tool.schema.number().optional().describe("Timeout in seconds"),
			},
			async execute(args) {
				const privateKey = process.env.X402_PRIVATE_KEY;
				if (!privateKey) {
					throw new Error("X402_PRIVATE_KEY environment variable is not set.");
				}

				// Your human-in-the-loop confirmation flow...
				// const approved = await confirmPayment(args.url, estimatedCost);
				// if (!approved) throw new Error("Payment declined by user");

				const account = privateKeyToAccount(privateKey as `0x${string}`);
				const client = new x402Client();
				registerExactEvmScheme(client, { signer: account });
				const paidFetch = wrapFetchWithPayment(fetch, client);

				const response = await paidFetch(args.url, {
					method: "GET",
					signal: args.timeout
						? AbortSignal.timeout(args.timeout * 1000)
						: undefined,
				});

				if (!response.ok) {
					throw new Error(`${response.status} ${response.statusText}`);
				}

				return await response.text();
			},
		}),
	},
});
```

When the built-in `webfetch` returns a 402, the agent calls `x402-fetch` to retry with payment.

## Claude Code hook

Claude Code hooks intercept tool results. To handle 402s transparently, create a script at `.claude/scripts/handle-x402.mjs`:

```js
// Use base-sepolia for testing. Get test USDC from https://faucet.circle.com/
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const input = JSON.parse(await readStdin());

const haystack = JSON.stringify(input.tool_response ?? input.error ?? "");
if (!haystack.includes("402")) process.exit(0);

const url = input.tool_input?.url;
if (!url) process.exit(0);

const privateKey = process.env.X402_PRIVATE_KEY;
if (!privateKey) {
	console.error("X402_PRIVATE_KEY not set.");
	process.exit(2);
}

try {
	// Your human-in-the-loop confirmation flow...
	// const approved = await confirmPayment(url);
	// if (!approved) process.exit(0);

	const account = privateKeyToAccount(privateKey);
	const client = new x402Client();
	registerExactEvmScheme(client, { signer: account });
	const paidFetch = wrapFetchWithPayment(fetch, client);

	const res = await paidFetch(url, { method: "GET" });
	const text = await res.text();

	if (!res.ok) {
		console.error(`Paid fetch failed: ${res.status}`);
		process.exit(2);
	}

	console.log(
		JSON.stringify({
			hookSpecificOutput: {
				hookEventName: "PostToolUse",
				additionalContext: `Paid for "${url}" via x402:\n${text}`,
			},
		}),
	);
} catch (err) {
	console.error(`x402 payment failed: ${err.message}`);
	process.exit(2);
}

function readStdin() {
	return new Promise((resolve) => {
		let data = "";
		process.stdin.on("data", (chunk) => (data += chunk));
		process.stdin.on("end", () => resolve(data));
	});
}
```

Register the hook in `.claude/settings.json`:

```json
{
	"hooks": {
		"PostToolUse": [
			{
				"matcher": "WebFetch",
				"hooks": [
					{
						"type": "command",
						"command": "node .claude/scripts/handle-x402.mjs",
						"timeout": 30
					}
				]
			}
		]
	}
}
```

## Related

* [Pay from Agents SDK](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/agents/tools/payments/x402/pay-from-agents-sdk/) — Use the Agents SDK for more control
* [Charge for HTTP content](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/agents/tools/payments/x402/charge-for-http-content/) — Build the server side
* [Human-in-the-loop guide](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/agents/concepts/agentic-patterns/human-in-the-loop/) — Implement approval workflows
* [x402.org ↗](https://x402.org) — Protocol specification

Was this helpful?

YesNo

## On this page

[![](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://docs-durable-objects-instance-replaced-errors.previews.developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/agents/tools/payments/x402/pay-with-tool-plugins/#page","headline":"Pay from coding tools · Cloudflare Agents docs","description":"Add x402 payment handling to OpenCode and Claude Code.","url":"https://developers.cloudflare.com/agents/tools/payments/x402/pay-with-tool-plugins/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-24","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```
