Webhooks & Callbacks

How to handle async workflows, retries, and long-running tool chains with Agent402.

Current async patterns

Most tools answer in the HTTP response itself. The slow paid routes (reports, media, plan execution) can also run as a job you collect later. The patterns available today:

Async jobs Available

Send Prefer: respond-async with a paid call to a slow route and get 202 Accepted with a job link at once. Poll GET /api/jobs/{id} (free) for its stage, progress and ETA until it is completed or failed. Payment settles only when the result is ready.

Idempotent retries Available

Add an Idempotency-Key header to a paid or proof-of-work call. Resend the same request with the same key and the same payment header, and the server returns the stored answer without charging again. A retry while the first copy still runs gets 409 with Retry-After.

Sequential chaining Available

Chain tools by calling them in sequence, for example render → extract → memory-write (wallet-only tools, paid per call). Each call is independent and stateless. Use workflow examples for patterns.

Wallet-keyed state Available

Use the memory tools (memory-write, memory-read) to persist intermediate results across tool calls. Your wallet address is your identity - no accounts needed.

Async jobs

A report runs for minutes, longer than many HTTP clients wait. Add Prefer: respond-async to the paid call. The server answers 202 Accepted with a Location header and a statusUrl, runs the call with your payment, and settles only on a successful result. A run that fails is not charged on x402, and the job says so.

# the paid call, with your x402 payment header, asking for a job
POST /v1/research
Prefer: respond-async
PAYMENT-SIGNATURE: <signed payment>

# 202 Accepted, Location: /api/jobs/<id>; poll it (free), waiting Retry-After seconds, until status is completed or failed
curl -s https://agent402.tools/api/jobs/<id>
# {"status":"working","state":"running","stage":"searching","steps":{"done":1,"total":5},"progress":0.2,"etaSeconds":64}

status stays working, then completed or failed. Its state is queued until the paid handler starts, then running with a stage, a progress from 0 to 1 and an ETA, then succeeded or failed. A slow route's 402 carries X-Async-Recommended, and agent402-client and agent402-mcp switch to a job on their own. The job id is the only key to the result: keep it private. On the hosted MCP connector the same long calls run as MCP tasks, with the same progress in tasks/get.

Idempotent retries in practice

Pass an Idempotency-Key header with any unique string. The server stores a successful answer keyed to the route, the key, the credential that paid and the body, so the replay must carry the same payment header (an x402 PAYMENT-SIGNATURE, a proof-of-work solution or a credits key). A fresh payment with the same key is a new call. This example uses a proof-of-work solution from GET /api/pow/challenge?slug=hash:

curl -X POST https://agent402.tools/api/hash \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-unique-key-123" \
  -H "X-Pow-Solution: <token>:<nonce>" \
  -d '{"text":"hello","algo":"sha256"}'

# Retry with the same key, the same credential and the same body:
# the stored answer comes back with X-Idempotent-Replay: true, nothing is spent again
curl -i -X POST https://agent402.tools/api/hash \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-unique-key-123" \
  -H "X-Pow-Solution: <token>:<nonce>" \
  -d '{"text":"hello","algo":"sha256"}'

The cache key is sha256(METHOD /path + key + credential + body hash), so different callers with the same idempotency key don't collide, and the same key with a different body is a different entry. agent402-client resends the same payment for you on a retry.

Chaining with agent402-client

import { Agent402 } from "agent402-client";

// Free tier: each call settles its own proof-of-work, no wallet needed
const a = new Agent402();
const md = await a.call("html-to-markdown", { html: "<h1>Q3 report</h1><p>Revenue rose.</p>" });
const stats = await a.call("text-stats", { text: md.markdown });

// Wallet-only tools chain the same way through a payment-wrapped fetch.
// Memory is keyed to the wallet that signs the x402 payment.
// payFetch: an @x402/fetch-wrapped fetch (see the USDC tab on /quickstart).
const paid = new Agent402({ fetch: payFetch });
const page = await paid.call("extract", { url: "https://example.com" });
await paid.call("memory-write", { key: "example-title", value: page.title });

Planned: callback delivery Planned

Async jobs are polled today. The planned addition is delivery to your own endpoint: name a callback URL on the call, and when the job finishes the server POSTs the result there with a signature you can verify. Polling /api/jobs/{id} stays the fallback.

To hear when callbacks ship, follow @Agent402Tools or watch the GitHub repo.

Workflow examples - see how tools chain together
Quickstart - get your first call working in 60 seconds
Documentation - full API reference