2026-04-04  ·  15 viewsx402mpp402hackdiscovery

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 address
  • maxAmountRequired — the price in micro-USDC
  • network — the chain
  • asset — the token contract
  • resource — the canonical URL of this API

All of this is available without sending any payment.


How the hack works

The idea is straightforward:

  1. Collect a list of known x402-enabled service URLs.
  2. Send an unauthenticated GET to each one.
  3. Extract payTo and description from the 402 response.
  4. 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 payTo addresses 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.