The 402 Hack Explained
How paylog uses HTTP 402 error responses as a data source to map on-chain payment addresses to service names — without ever making a real payment.
What is HTTP 402?
HTTP 402 Payment Required is one of the oldest status codes in the spec — it has been in HTTP/1.0 since 1991. The original description said it was "reserved for future use" with digital payment systems, then it sat unused for over thirty years.
Around 2024–2025, protocols like MPP and x402 gave it a real job, and that dormant status code suddenly became interesting.
What a 402 response contains
Hit an x402-protected API endpoint without a payment header and you get back something like this:
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402Version": 1,
"error": "X-PAYMENT header is required",
"accepts": [{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "10000",
"resource": "https://api.example.com/v1/data",
"description": "AI search API",
"mimeType": "application/json",
"payTo": "0x29322ea7ecb34aa6164cb2ddeb9ce650902e4f60",
"maxTimeoutSeconds": 300,
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"extra": {}
}]
}
That response includes:
payTo— the recipient addressmaxAmountRequired— the price in micro-USDCnetwork— the chainasset— the token contractresource— the canonical URL of this API
All of this is available without sending any payment.
How the hack works
The idea is straightforward:
- Collect a list of known x402-enabled service URLs.
- Send an unauthenticated GET to each one.
- Extract
payToanddescriptionfrom the 402 response. - Build a map:
{ payTo address → service name }.
async function probe402(url: string): Promise<ServiceInfo | null> {
const res = await fetch(url)
if (res.status !== 402) return null
const body = await res.json()
const accept = body.accepts?.[0]
if (!accept?.payTo) return null
return {
payTo: accept.payTo.toLowerCase(),
name: accept.description ?? new URL(url).hostname,
url,
}
}
Why this matters
On-chain payments are public data, but addresses are opaque. When someone sends $0.001 USDC to 0x29322ea7ecb34aa6164cb2ddeb9ce650902e4f60, nothing on-chain tells you what service that is.
With the address map from the 402 hack:
Transfer to 0x29322ea7... → "Hugen" (hugen.ai)
Transfer to 0x3a7d5b... → "Exa" (exa.ai)
paylog uses this mapping to turn raw wallet history into a human-readable breakdown of spend by service.
Limitations
- The map captures a point-in-time snapshot and needs periodic refresh.
- Services that rotate
payToaddresses dynamically cannot be tracked this way. - If official directories like the Bazaar / CDP discovery API become comprehensive enough, this workaround becomes unnecessary.
For now, paylog runs a weekly update script (scripts/update-services-x402.ts) to keep the service list current.
The same trick works for MPP
MPP (Tempo chain) uses the same pattern. An MPP 402 response looks like:
{
"payment_address": "0x...",
"amount": "1000",
"currency": "USDC",
"description": "Service Name"
}
paylog probes both protocols and supports both chains. Use chain=all to get a combined report across Tempo MPP and Base x402 in a single call.