402402Signal

Developer guides

What are you building?

Start with a free reference check, add a guarded purchase, or inspect your own endpoint. Choose the task first. The guide tells you which interface fits.

You keep the wallet, signing authority and final decision. Payment requirements are not permission to spend.

Test the guard without a funded wallet

Use this when you are building an x402 buyer, debugging an integration, or checking what the verification boundary actually does. Start with a reviewed checkout and Node.js 22 or newer.

git clone https://github.com/402signalhq/402signal.git
cd 402signal
# Inspect and select the reviewed revision before executing its code.
node integration/buyer-checks/run.mjs

The default runner uses a PUBLIC TEST KEY, a synthetic fixture clock and fake authorization callbacks. No networking, signing or payment occurs during that run. Downloading the checkout is a separate network operation.

Expect seven passing cases in separate reports, five buyer-adapter cases and two historical-verifier cases: matching offer, changed price, changed recipient, expired evidence, changed request, original saved record and altered saved policy. A matching offer reaches the fake callback once. Refusals reach it zero times. The runner exits nonzero if a case fails.

Test your callback, not just our library

node integration/buyer-checks/run.mjs \
  --adapter ./integration/buyer-checks/example-adapter.mjs
node integration/buyer-checks/run.mjs --self-test

Copy the example adapter and connect your own trusted verification boundary. Export authorize(options, fakeCallback). Call only the supplied fake callback after verification; preserve the verifier's typed refusal error. A generic initialization error is a failure, not a safe refusal. The self-test catches an unguarded adapter, one that refuses every valid purchase, and generic errors mistaken for expected refusals.

This is not a sandbox. The runner measures its callback, not hidden side effects in arbitrary adapter code. Run customer code in an isolated environment without production credentials or external networking. A passing reference suite does not test your application's signing path.

What this test establishes

The named Base exact-x402 fixture cases behaved as expected. It is not a security audit, full x402/MPP certification, chain preflight, Falcon-anchor test or live merchant qualification. The verifier reads tests/fixtures/route-binding-v1.json. Its synthetic inputs are not production keys or receipts.

Complete runner contract and expected report · Add a real buyer integration

Give this task to your coding agent
Test the supported exact-x402 callback with 402Signal. Read integration/buyer-checks/README.md in the reviewed repository. Run node integration/buyer-checks/run.mjs and --self-test with Node 22 or newer. Do not load a wallet or production key. A customer adapter is explicitly chosen trusted local code, not sandboxed. Report the fixture hash, subject and measured acceptance/refusal outcomes. Guide: https://402signal.com/developers/test-buyer

Copies documentation. It does not install a tool or authorize spending.

Add a check to an existing x402 buyer

For whoever configures the agent payment path: an existing wallet, pay-fetch SDK, MCP payment server, or policy owner. Not a merchant, facilitator, or discovery-index guide. Partners wrap the existing sign so the buyer and the live seller offer agree on exact terms at payment time, then keep that snapshot. This is not a negotiation marketplace. For native MPP, use the charge guide.

The offline guard requires Node.js 22 or newer. The supplied file store requires private POSIX storage. The package is published to the npm registry from this repository's release workflow with a provenance attestation; install it and check the attestation:

npm install @402signal/route-guard@0.7.4
npm audit signatures

Prefer a digest you pin yourself? From a reviewed checkout, one command downloads the published GitHub archive, checks both published digests, and installs it:

node scripts/install_route_guard.mjs

Without a checkout, download the published archive and SHA256SUMS, then verify both pins before install:

curl -fsSL -o 402signal-route-guard-0.7.4.tgz \
  https://github.com/402signalhq/402signal/releases/download/route-guard-v0.7.4/402signal-route-guard-0.7.4.tgz
curl -fsSL -o SHA256SUMS \
  https://github.com/402signalhq/402signal/releases/download/route-guard-v0.7.4/SHA256SUMS
echo '164a1328ddcba856b667b74b43573bfb455016144cef84858ae66054be64b5ff  402signal-route-guard-0.7.4.tgz' | sha256sum --check
echo 'd414db64f83d5f68013451c912c7c6a6c03aa32d95d4007ac5b942b353abeaa9  SHA256SUMS' | sha256sum --check
sha256sum --check SHA256SUMS
npm install --ignore-scripts ./402signal-route-guard-0.7.4.tgz

1. Request bound evidence

Use url for an exact endpoint or need to find candidates. Start with an unpaid request:

curl -sS -D - https://402signal.com/route \
  -H 'Content-Type: application/json' \
  --data '{"need":"web search","networks":["base"],"max_price_usd":0.02,"require_route_binding":true}'

HTTP 402 supplies the checking-fee requirements. It is not a completed check. Your buyer validates them, reserves the budget, authorizes the fee once and submits the identical request. A qualifying observation costs $0.003 USDC; seller payment is separate.

When require_route_binding is on, the hosted check may select the next already-probed selectable candidate that can bind if the ranked winner cannot. HTTP 503 with binding_error: route_binding_unavailable means none remained bindable. Failed binding losers in compared[] use excluded_reason: binding_unavailable. The checking fee is not charged twice.

If the check misses or cannot bind

These are completed answers, not a broken service. Binding or constraint misses mean policy is working. Inspect the typed fields, then call /route again. Do not treat a miss as an outage that stops further checks.

Completed miss, HTTP 200
live:false, a typed miss_reason such as no_candidates or constraints_unmet, and route_outcome.next_action usually change_constraints. No checking fee. Use isUnsettledRouteMiss. Not an outage.
No bindable candidate, HTTP 503
binding_error: route_binding_unavailable and an allowlisted binding_error_reason. route_outcome.code is binding_failed; next_action is fix_request_or_compatibility. Fall-through already tried the next already-probed selectable candidate. The wrap maps this to state=binding_unavailable (preferred over miss markers), still keep_calling_route: true, no seller sign. Do not treat this as a crash or send an unguarded payment.
Local guard refusal
A thrown withVerifiedRoute is a stop. Do not pay the seller. HTTP 200 naming a winner is not enough.

2. Wrap the existing sign, do not rewrite the wallet

After install, the helper writes exact-authorize.mjs next to the package. The same one-liner is the default on this spend and the next, including a hosted session=open with require_route_binding: true. Hops do not use the wrap. Supply your current checking-fee signer and seller signer. The wrap observes /route, binds, locally verifies, and only then calls signSeller. A miss or binding failure returns typed next_action and keep_calling_route: true. HTTP 503 with binding_error: route_binding_unavailable maps to wrap state=binding_unavailable (preferred over miss markers); still keep_calling_route: true, no seller sign. That is policy working, not a crash. Do not stop calling /route. MCP preview/validate cannot complete a paid route.

import { wrapExactAuthorize } from './exact-authorize.mjs';

const result = await wrapExactAuthorize({
  id, requestJson, client, trustedLogVkey,
  signRouting: challenge => existingWallet.signRouting(challenge),
  signSeller: (verified, challenge) => existingWallet.signSeller(verified, challenge)
});
if (result.state !== 'authorized') {
  // policy working; inspect result.next_action and call /route again
}

Obtain the log key from independent trusted configuration, not the response being verified. Keep the exact request and raw response. Parsing and reserializing untrusted JSON can erase duplicate-key evidence. The packaged examples/search.ts is the longer pay-fetch form of the same boundary. This is integration scaffolding, not complete wallet code.

Already on the official x402 client? Register the hook instead

Buyers using @x402/core with @x402/fetch can skip the wrap and add one onBeforePaymentCreation hook. It runs the same hosted check with the same wallet, re-reads the seller's unpaid challenge, verifies the receipt with your pinned log key, and returns an abort when the requirements the client selected differ from the verified offer. 402Signal's own fee challenge passes through, so nothing recurses.

import { signalGuard } from '@402signal/route-guard/x402';

client.onBeforePaymentCreation(signalGuard({
  fetchWithPayment, trustedLogVkey,
  requestFor: ({ paymentRequired }) => ({ url: paymentRequired.resource.url, require_route_binding: true, max_price_usd: 0.02 }),
  onResult: result => retain(result.text)
}));

A completed miss, an unbindable candidate or a failed verification aborts the payment. Set onMiss: 'allow' to treat a miss as advisory. The package README documents every option.

3. Compare immediately before signing

The wrap calls withVerifiedRoute at the payment boundary. Get the seller's current unpaid challenge for the same URL, method and body bytes, with redirects disabled:

import { withVerifiedRoute } from '@402signal/route-guard';

await withVerifiedRoute({
  routeResponseJson, routeRequestJson, trustedLogVkey,
  request: { url, method, body: exactRequestBytes },
  challenge: { status: 402, bodyText, paymentRequired }
}, async verified => {
  // Reserve the buyer's durable payment identity and budget.
  // Validate the transaction against verified.accepted.
  // Then invoke the buyer's existing wallet flow once.
});

This is integration scaffolding, not complete wallet code. The reference Base buyer shows the complete supported composition, including the signer and planning mode. It requires Node 24 and private POSIX storage. Start with planning before loading keys.

4. Treat a refusal as a stop

Changed terms, expired evidence or an invalid proof must not trigger an unguarded fallback. A local guard refusal is a stop even if the hosted response named a winner; do not pay the seller without a matching proof. The default observation window is 60 seconds; inspect the returned expiry. Your wallet still checks transaction effects, reserves budgets and prevents duplicate seller sends. A matching proof does not guarantee delivery or output quality.

The ordinary hosted profile covers GET and a narrowly justified empty-object POST fallback. It is not an arbitrary POST proxy. The separate bounded Parallel profile has its own dated compatibility notes, including recipient changes that led the fixed-offer guard to refuse. Do not replace a recipient pin to force a purchase.

Build the request · Handle billing and recovery · Exact request contract

Give this task to your coding agent
Wrap the existing agent payment sign. Customer is the wallet / pay-fetch / MCP payment / policy owner, not a merchant. Read https://402signal.com/developers/check-offer. Install route-guard 0.7.2 with node scripts/install_route_guard.mjs (writes exact-authorize.mjs). Use wrapExactAuthorize around existing signRouting and signSeller: observe or session-open, bind, local verify, only then wallet. Hops do not wrap. Fail closed; no unguarded fallback. Same wrap on the next spend. HTTP 200 live:false or HTTP 503 binding_error is policy working, not a crash; keep calling /route. Inspect miss_reason / route_outcome.next_action. Exact plus binding plus transparency only. Do not request funds or retry an uncertain payment without existing operator authorization.

Open a hosted session

Use this when you want to reuse one paid observation. A hosted session is a /route product. It is not the merchant Session Client, which funds a seller channel.

Open costs $0.005 USDC. The window is 20 hops or 10 minutes on the bound snapshot from open. Then open again. Hops do not run a new 7-URL probe and do not call facilitator /verify or /settle. Completed misses are free. Seller payment stays with your buyer.

Open

curl -sS -D - https://402signal.com/route \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://seller.example/x402","session":"open","require_route_binding":true}'

HTTP 402 supplies the $0.005 requirements. Authorize that fee once and submit the identical request. require_route_binding on open can emit a v4 receipt. wrapExactAuthorize is the default MIT guard on that open. Hops do not use the wrap.

Hop

Send session=hop and the session_id from open. A raw session id, or any other value, in session is miss_reason=invalid_session_shape: HTTP 200, no probe, no checking fee.

curl -sS -D - https://402signal.com/route \
  -H 'Content-Type: application/json' \
  --data '{"session":"hop","session_id":"0000000000000000000000000000000000000000000000000000000000000000","url":"https://seller.example/x402"}'

The session_id in the example is a schema-valid placeholder. Replace it with the real 64-hex id from open. The example url is optional on hop and pins the bound snapshot when present.

A hop that restores the bound winner reports route_outcome.code=session_hop and next_action=none. Optional scheme, amount_atomic and payTo are checked against the ceiling stored at open; a break misses and does not settle.

The ordinary 60-second observation cache applies to new listed-URL probes, not hops. Add the local guard on open · Hosted session contract.

Give this task to your coding agent
Add a hosted 402Signal session. Read https://402signal.com/developers/hosted-session. Open with session=open at $0.005; hops are session=hop plus session_id. Do not put the session id in session. Hops must not probe or call a facilitator. This is not the merchant Session Client. wrapExactAuthorize covers open with require_route_binding; hops do not wrap. Do not request funds without existing operator authorization.

Select the intended MPP charge

Use this for supported native WWW-Authenticate: Payment offers. Base evm.charge and Algorand charge have separate adapters. x402 through mppx is a different path.

Pin the profile, realm, network, asset, recipient and relevant fee/price limits. Exactly one supported offer must match. Two matches remain ambiguous even at the same price. Response order is not a spending policy.

Preserve the full offer

Retain the original header, body and alternate payment header. The verifier rebuilds selection from that evidence. Only the selected original challenge reaches the SDK. Do not simplify the response before verification or select another offer after signing fails.

Base charge

Use prepareVerifiedNativeBaseMpp from the reviewed MPP client. Supply retained observation evidence, the trusted log key, exact GET request, current challenge and buyer policy. Preparation does not sign. Your application durably claims the authorization identity and reserves its budget before createCredential({authorize}). Submit the resulting credential once to the exact merchant request.

This version covers Base USDC EIP-3009. It does not cover splits, Permit2 or arbitrary tokens. Base setup and complete contract.

Using mppx? Register the guard as its onChallenge hook

For buyers on mppx, @402signal/route-guard/mpp gives the same check as an onChallenge hook plus a challenge.received observer. The observer records which URL produced each challenge; the hook runs the hosted Check group offer observation for Base USDC charges under your cap, verifies the receipt, and throws when the live challenge's economic terms differ from the verified terms, so mppx never creates a credential. Hosted Check group offer must be enabled for the observation to qualify.

import { Mppx } from 'mppx';
import { mppGuard } from '@402signal/route-guard/mpp';

const guard = mppGuard({ fetchWithPayment: (u, i) => mppx.fetch(u, i), trustedLogVkey, maxCallAmountAtomic: '1000' });
const mppx = Mppx.create({ methods, onChallenge: guard.onChallenge });
mppx.onChallengeReceived(guard.onChallengeReceived);

Algorand charge

Use the verified Algorand adapter with its explicit sponsor or buyer-paid fee policy, durable budget, signer and bounded transport. A merchant acknowledgment is not independent chain confirmation. A lost response remains uncertain until the original payment is reconciled.

Algorand setup and reference integration · Full-response selection contract.

Check the matching released artifact and hosted profile before paying for an observation. Source support is not universal merchant compatibility. Local POST paths do not broaden hosted POST support.

Give this task to your coding agent
Inspect a supported native MPP charge. Read https://402signal.com/developers/native-mpp and the exact adapter contract. Distinguish native MPP from x402 through mppx. Preserve the complete original offer and require one explicit match. Start with synthetic tests. Keep authorization and submission separate and durable. Do not infer support for all networks, assets, intents or request bodies.

Check a group offer

Use this when a seller challenge describes a grouped settlement, session, native charge, atomic group or invoice. Send the exact HTTPS GET url, buyer_limits caps and require_route_binding: true. Do not pass merchant_profile. When hosted Check group offer is enabled, the server auto-selects a codec from the live wire. Unknown or ambiguous challenges fail closed.

The HTTP result and compared[] name the short codes job (chk_grp) and codec. label is optional debug. The public receipt leaf stays commitment-only. This is a short-lived observation, not permission to deposit, issue vouchers or sign an arbitrary transaction.

Hosted enablement is operator-controlled. Currently enabled hosted codecs: mpp. An ordinary exact-payment receipt does not authorize this job. After a qualifying observation, use the matching session or manifest guide before funding.

curl -sS -D - https://402signal.com/route \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://merchant.example/batch","buyer_limits":{"network":"eip155:8453","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","recipient":"0x2222222222222222222222222222222222222222","receiver_authorizer":"0x3721824a31197dcDD2984cF43b92B6cc8A87c0Fb","withdraw_delay_seconds":900,"max_call_amount_atomic":"1000","max_cumulative_amount_atomic":"4000","max_capital_atomic":"4000"},"require_route_binding":true}'

Caps select a codec before the probe. The live challenge must speak that codec. Empty or unknown caps are refused. A qualifying observation costs $0.003 USDC; seller charges remain separate.

Check group offer contract · Fund a session or invoice after the observation.

Give this task to your coding agent
Add a Check group offer observation. Read https://402signal.com/developers/check-group-offer and docs/batch-observation-v1.md. POST url, buyer_limits and require_route_binding:true. Do not send merchant_profile. Expect job chk_grp, a detected codec and label Check group offer. Unknown wires must fail closed. Do not treat the observation as signing or funding authority.

Check the commitment, not just the call price

Use this when funding a supported session or accepting an explicit grouped-purchase manifest. An ordinary exact-payment receipt does not authorize a batch or session. For the hosted $0.005 observation window, use Open a hosted session instead. That product does not deposit merchant capital.

Base and Solana sessions

The Session Client requires Node 24 and private POSIX/SQLite storage. Pin one endpoint, channel, parties, asset, call price, cumulative cap, maximum call count and fixed deadline before funding. Deposited capital, native fees and seller charges are different exposures.

The original signed observation stays in the private journal. Later continuation follows separately pinned buyer policy; it does not extend the original receipt or buy another check. No automatic top-up or new channel is authorized. Unknown results stop later calls, including after restart.

The controller supports 1 to 64 sequential calls and a buyer deadline up to 24 hours. Those are versioned bounds, not throughput or merchant-availability promises. Base receiver/token activity is serialized; unrelated receiver activity can leave confirmation unknown. This is not general concurrent-receiver coordination.

The Solana session cap is not a per-call price. The observed per_call_amount_atomic is null; establish and pin the merchant's actual call economics separately.

Complete opening, continuation, recovery and close guide.

Algorand groups and invoices

The multi-item profile supports 2 to 15 job payments plus a sponsor transaction. The aggregate-invoice profile supports 2 to 64 explicit jobs represented by one invoice payment plus sponsorship. The invoice total is not a known per-job price.

The merchant must supply the supported ordered manifest. Verify exact jobs, payment count, recipient, total and fee quote. Do not split an ordinary one-payment offer into invented job payments. On-chain atomicity does not promise atomic HTTP delivery.

Manifest contract · Buyer adapter and examples.

Finish and reconcile

A voucher acknowledgment, a settlement transaction and a delivered resource are separate events. Confirm the original transaction and account effects. Receipt lookup must be genuinely read-only; resending a live voucher with a custom header is not automatically safe recovery. Missing evidence leaves the operation uncertain.

A qualifying hosted observation costs $0.003. Merchant charges, capital deposits, gas and rent are separate. Check profile and release scope before a live run.

Give this task to your coding agent
Integrate only the supported session or manifest profile at https://402signal.com/developers/sessions-and-invoices. Review runtime/storage requirements and hosted availability. Separate call price, cumulative spend, locked capital and native fees. Keep initial evidence historical and continuation policy explicit. Do not auto-top-up, extend expiry or retry an uncertain economic operation. Confirm original settlement and refund effects independently.

See your API through a buyer's eyes

Use discovery to inspect how your service is represented, then check basic readiness of an exact listed endpoint. This can reveal stale listed terms, missing input information or an observed recipient change. It is not a full payment test.

See your host's public page

Every host with catalog listings has a public readiness page at /endpoints/<host>: listings, declared networks, 30-day public probe results and an embeddable badge. The numbers come only from buyers' checks and the discovery feeds; nothing a seller pays for changes them. Browse hosts.

Search by the job a buyer asks for

Try the capability your endpoint provides in Explore. Review the exact URL, method, network, source, price units and input schema. Search responses can be partial. Absence does not prove your service is invisible everywhere, and position is not market share.

Check a listed endpoint

Paste the exact catalog-listed HTTPS URL below. The existing server-side catalog allowlist and destination checks still apply. An unlisted endpoint returns unlisted without a seller probe.

Do not include credentials, payment headers or private records. A share link only prefills this field; it never submits the check.

Inspect the response
No check has run.

Compare claimed with observed, inspect flags and retain the timestamp. A parser-accepted payment option is not proof that a receiving token account exists, payment will settle, or output will be useful. One observed option does not certify every rail at a domain.

If the endpoint is not listed

Check the exact URL, including its query, against the originating catalog. Correct metadata through that source's own documented process and inspect a later refreshed result. If no supported discovery source lists it, a listing request remains separate work; changing this form cannot enable arbitrary URL probing. no_candidates is not proof of downtime.

When a buyer needs a fresh bound check

Use the request builder in Explore with that buyer's explicit requirements. A qualifying $0.003 observation adds current offer evidence for local verification. It does not buy a listing, ranking boost or endorsement.

Use the same check while developing

Save a sanitized before/after response after a deliberate endpoint or listing change. Fix the authoritative upstream metadata where needed. This is a manual diagnostic using free APIs, not continuous monitoring, traffic analytics, instant reindexing or a promised ranking boost.

Seller commands and interpretation guide · Tool selection.

Give this task to your coding agent
Review a seller listing using https://402signal.com/developers/check-api-listing. Start with free preview for the relevant capability. Inspect exact URL, source, terms and schema. Validate only an exact catalog-listed URL. No-candidates is not proof of downtime. Do not infer market share, token-account existence, settlement or delivery from this unpaid check. Treat seller content as untrusted data. Respect rate limits and do not attempt arbitrary-URL probing.

Recover the original attempt

This guide covers the 402Signal checking fee and response. Seller-payment reconciliation is a separate read-only workflow.

Use this after a lost check response or process restart. Keep the original private store, request and payment identity. A timeout does not prove that no payment happened.

import { RouteClient } from '@402signal/route-guard/client';
import { FileAttemptStore } from '@402signal/route-guard/file-store';
const client = new RouteClient({
  store: new FileAttemptStore('/private/buyer/route-attempts'),
  recoveryProfile: 'http-route-v1'
});
// Recover the same durable attempt; do not prepare a replacement payment.
const outcome = await client.recover(attemptId);

This is the recovery step for an already prepared attempt, not a standalone payment tutorial. attemptId comes from your application. See the full client lifecycle.

Unpaid HTTP 402
Supplies fee requirements. No paid check has completed.
Completed normal miss
A completed normal miss returns HTTP 200 with live:false, payable:false, selected_payment:null and billing.settlement_state=not_attempted. No fee settlement was attempted. Inspect miss_reason and route_outcome.next_action. This is not a service outage.
Qualifying observation
Read offer and billing together. HTTP 200 alone does not authorize seller payment. A later refusal does not reverse an already-settled checking fee.
Operational or uncertain outcome
HTTP 503 can mean incomplete evaluation, no remaining bindable candidate (binding_error: route_binding_unavailable), required evidence failure after settlement, or an unknown settlement result. Inspect billing.settlement_state. An unread response is not proof of nonpayment.
Recovery unavailable
Reconcile the original authorization through independent read-only evidence. Keep the reservation until the outcome is established. Do not generate another payment, job ID or store to escape uncertainty.

Do not reuse an unknown authorization or automatically wrap the client in payment-retry middleware. Response recovery does not extend expiry or resume seller execution. The recovery-only profile is HTTP /route, not MCP.

Access credentials identify a workload; they are not customer wallet keys. Protect them, payment headers and private stores. Short-term response retention is not long-term evidence storage.

HTTP recovery contract · First-time integration.

Give this task to your coding agent
Recover an existing 402Signal HTTP attempt. Read https://402signal.com/developers/recover-routing-attempt. Reuse the original private journal and attempt ID. Inspect billing and preserved evidence. Do not sign again, resend a seller payment or create a fresh identity to resolve an unknown result. Keep budget reservations until independent reconciliation establishes the outcome.

Inspect the original seller payment

Use this when the reference Base buyer submitted a seller payment but did not retain the response. For a lost 402Signal checking response, use routing recovery instead.

The reference buyer requires Node 24 and private POSIX/SQLite storage. Preserve the original configuration, journal and job ID. Read-only reconciliation does not load the signing account or submit another payment.

cd integration/reference-buyer
npm ci --ignore-scripts
REFERENCE_BUYER_ACK=base-exact-only-once node operator.mjs confirm-seller /private/config.json ORIGINAL_JOB_ID

The path and job ID refer to your existing campaign. This command is not a new-job tutorial. An optional existing transaction hash can be supplied as the final argument. Do not invent a new ID or replace the journal.

A confirmed payment with no retained response means payment confirmed; response not retained. It does not recover the answer, prove useful output or issue a refund. While the job is unresolved, repeat submission and new jobs in that campaign remain blocked. Read the resulting journal status before any further action; confirmation is not permission to rerun the job.

The current reference seller request times out after 20 seconds. Read-only confirmation makes at most six observations within 15 seconds. HTTP timeout, payment-authorization validity and observation expiry are separate clocks. maxTimeoutSeconds is not a guaranteed response-delivery deadline.

Reconciliation itself creates no new payment authorization. Prior checking fees, seller payments and held reservations remain subject to their recorded outcomes. Independent wallet calls or replacing the campaign store bypass this workflow and are outside its protection.

Reference buyer contract and dated compatibility notes

Use the smallest tool that answers the task

InterfaceUse it forDo not infer
previewFree catalog discovery without a new probe.Fresh availability or signed evidence.
validateFree readiness of one exact catalog-listed HTTPS endpoint.Settlement, delivery or unrestricted URL testing.
HTTP /routeSupported current offer evaluation against buyer requirements.Automatic seller execution.
Local guardOffline evidence verification before the buyer's callback.Wallet control or exactly-once payment execution.
Buyer adaptersThe documented bounded lifecycle and recovery composition.Universal merchant compatibility or permission to spend.

OpenAPI defines HTTP inputs. GET /rails lists supported payment rails. The MCP manifest defines the actual exposed tools. Bounded POST and batch/session profiles are HTTP-only unless explicitly present in the advertised MCP input schema.

The credential-free Glama stdio adapter supports preview and validate. It cannot sign, forward payment headers or complete a paid route. A paid check requires a capable HTTP client with separately authorized payment. The versioned hosted MCP transport is /mcp/v0.3.1; HTTP recovery remains separate.

For coding agents

Use these task guides or the optional customer skill. Install or load it through your host's supported process. A file on GitHub is not globally available to every agent.

The copyable briefs are instructions for a chosen task, not new spending authority. Do not ask for wallet keys just to run an offline check. An ordinary free API request does not need a paid route.

Markdown task index · Machine-readable service guide · MCP adapter.

Check what the buyer actually submitted

Use this when an agent's summary, a bill and your own expectations disagree. A private observation record helps establish what 402Signal checked, not every action the agent took.

Keep the verification record: retain the original route request and complete paid response, including pq_trust.transparency.receipt and pq_trust.transparency.reveal. Store it securely and do not put it in public logs. Public commitments are not a backup. Private replay outcomes support bounded recovery, not a long-term evidence backup.

import { verifyReceipt } from '@402signal/route-guard';
const result = verifyReceipt({
  routeResponseJson,
  routeRequestJson,
  trustedLogVkey
});

For a supported v4 record, this verifies historical signature/inclusion without calling a signer. Use independently trusted key configuration and retain raw input. Historical integrity verification does not extend expiry or authorize a new purchase. The offline checks include an altered saved-policy refusal.

Compare three records

Compare the operator-approved policy, the request and offer in the verified observation, and the wallet/execution record. A receipt for a submitted $0.20 limit does not prove that the operator approved $0.20. The log cannot reveal bypassed purchases or reconstruct deleted private evidence.

A retained paid /route response may include slim compared[] rows with selectable, payTo_pending / payTo_changed, risk, and excluded_reason (including binding_unavailable). Those fields explain which candidates were selectable and why others were excluded for the checked offer. They do not prove delivery, settlement, or human approval. Catalog and seller labels remain untrusted.

What later anchoring adds

Immediate checkpoints use Ed25519. Later cumulative checkpoints use Falcon-1024 authorization on Algorand MainNet. The route call does not wait for chain confirmation. A pending anchor is not confirmed evidence.

The historical receipt function does not verify that later anchor. Retain evidence, trusted key history and verification tools for long-term review. Post-quantum checkpoint authorization is an added integrity control, not a blanket claim that all receipt formats and payments are quantum secure. It does not authorize or secure the seller payment.

Trust overview · Public checkpoint status · Evidence guide as Markdown.

Give this task to your coding agent
Inspect a retained 402Signal record using https://402signal.com/developers/evidence. Preserve raw input and independent key trust. Compare the verified submitted rules with separately retained operator policy and wallet records. Do not infer human approval, delivery, payment settlement or unobserved agent actions. Receipt verification and later Falcon anchor verification are distinct. Do not upload private evidence to public tools.

Keep enforceable policy outside the model

Use this when you own the wallet policy or approve an agent-platform integration. Let the model propose a task. Trusted application code supplies enforced limits, recipient allowlists, key pins and signing authority.

402Signal evaluates submitted requirements. It cannot know that the model changed an organization's approved policy before submitting them. The local guard cannot protect a caller that bypasses it. Save observation evidence, approved policy and execution records under operator-controlled retention.

Request human approval before the final fresh check where needed. Approval does not permit ignoring expiry. A stopped operation must not fall back to unguarded payment.

The service's destination-change handling is not a permanent organization-wide recipient allowlist. Pin approved recipients in the buyer when required. There is no hosted approval queue, delegated signer, escrow, delivery guarantee or dispute-resolution service.

Published client bounds and shared storage are not a throughput certification or a service-level agreement. Confirm workload access and operating limits for your integration.

Data and retention boundaries · Uncertain outcomes · Private security contact.

Match the guide, package and hosted profile

A request shape in source, an installable archive, hosted enablement and a completed merchant campaign are different facts. Review the matching release and dated qualification before a funded run.

MechanismSupported shapeMain limits
Exact x402Base, Solana and Algorand offers.GET and defined POST profiles, not arbitrary forwarding.
Hosted session$0.005 open; hops reuse the bound snapshot.Not the merchant Session Client. Hops do not probe or settle.
Native Base MPP chargeBase USDC EIP-3009; explicit offer match.No Permit2, splits or arbitrary tokens.
Native Algorand MPP chargeOne supported USDC charge and fee policy.Separate from sessions and manifest extensions.
Base x402 batch sessionFixed funded channel and continuation policy.Sequential controller; serialized receiver/token scope.
Solana MPP push sessionFixed opening and continuation policy.No pull delegation, automatic top-up or cross-channel batching.
Algorand multi-item2 to 15 ordered job payments plus sponsor.Explicit merchant extension and fee quote.
Algorand invoiceOne invoice payment for 2 to 64 jobs.Per-job price allocation unknown.

The earlier two-item Algorand profile remains a separate supported contract. Do not interpret newer group counts as permission to alter an older manifest.

This guide covers completed source development. Confirm published archive versions and hosted profile availability from GitHub Releases and the operator. Do not infer deployment from a green fixture suite or use a similarly named unverified package.

Complete documentation index · Dated controlled qualification · Confirm integration availability.

When a recipient changes

Two outcomes a buyer can rely on. First, a candidate is excluded from selection (compared[].excluded_reason payTo_pending, with payTo_changed true) when the live challenge's payTo differs from 402Signal's own previous trusted observation of that URL, even if the catalog listing has since been updated to the new wallet; a catalog claim never clears it. The exclusion lifts when a second independent observation confirms the new recipient, or when the request opts in with accept_payTo_change. Second, the guard's binding check fails closed on a recipient change: decision_binding.quote_sha256 covers the seller's whole raw 402 challenge, so a different payTo (like a different price, network or asset) is a quote_changed refusal and the buyer's callback never runs. Definitions: claimed is the catalog listing at claimed.claimed_at; observed is the live challenge at verified_at; payTo_changed means the observed recipient differs from the catalog claim or from the last trusted observed destination; claimed_payTo_match compares the two sides directly.

Get told when a seller you depend on changes

A subscription names up to 20 seller hosts and one public HTTPS webhook. Every two minutes the service compares each host's latest public observations with your subscription and, when something moved, delivers one signed batch of price_changed, recipient_changed and liveness_changed events. Alerts fire on the same observations the endpoint pages count: a change is reported when a check observed it, never from a catalog feed alone. An admission key (X-402Signal-Key) is required; ask at ross@402signal.com.

Subscribe

curl -sS -X POST https://402signal.com/alerts \
  -H "X-402Signal-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/402signal","hosts":["api.example.com"],"events":["price","recipient","liveness"]}'

HTTP 201 returns the subscription id, hosts_known (whether each host has listings today) and signing_secret, shown once. The URL must be public HTTPS with public DNS: private, loopback and link-local addresses, plain HTTP, credentials in the URL and 402signal.com itself are refused at creation and again on every delivery. Up to 10 subscriptions per key, 20 hosts each; events defaults to all three.

What you receive

One POST per scan when something changed. Headers: X-402Signal-Event (402signal.alerts, or 402signal.ping for a test), X-402Signal-Delivery (the delivery id, also in the body) and X-402Signal-Signature (t=<unix seconds>,v1=<hex HMAC-SHA256> over <t>.<raw body>).

{"type": "402signal.alerts", "delivery_id": "9f1c2d3e4a5b6c7d", "subscription_id": "0123456789abcdef",
 "generated_at": "2026-09-13T18:20:00Z",
 "events": [
  {"event": "price_changed", "host": "api.example.com", "url": "https://api.example.com/v1/quote",
   "amount_atomic": "20000", "changed_at": "2026-09-13T18:19:41Z", "endpoint_page": "https://402signal.com/endpoints/api.example.com"},
  {"event": "recipient_changed", "host": "api.example.com", "url": "https://api.example.com/v1/quote",
   "payTo": "0xabc...", "observed_payTo": "0xdef...", "changed_at": "2026-09-13T18:19:41Z", "endpoint_page": "..."},
  {"event": "liveness_changed", "host": "api.example.com", "url": "https://api.example.com/v1/quote",
   "live": false, "miss_reason": "timeout", "observed_at": "2026-09-13T18:19:52Z", "endpoint_page": "..."}
 ]}

price_changed carries the new observed atomic amount on the same asset. recipient_changed carries the address on record and the newly observed one; it stays pending until a second observation confirms it, with no second alert for the confirmation. liveness_changed reports the latest observation flipping between answering with a valid challenge and not. Delivery is at least once: the same event, url and changed_at seen twice is the same change twice. Answer with any 2xx within five seconds; a 3xx is not followed and counts as a failure.

Verify the signature

import hmac, hashlib, time

def verify(secret: str, header: str, body: bytes, tolerance_s: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    ts = int(parts.get("t", "0"))
    if abs(int(time.time()) - ts) > tolerance_s:
        return False
    expected = hmac.new(secret.encode(), b"%d." % ts + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Failures and management

A delivery that raises, times out or answers outside 2xx counts as a failure. Retries back off from one minute to one hour and the undelivered changes stay owed, so the next successful delivery carries them. After 20 consecutive failures the subscription is disabled and shows active: false with the reason; a successful test ping re-enables it.

CallResult
GET /alertsYour subscriptions, no secrets, and the limits.
GET /alerts/<id>One subscription with its last 20 deliveries: time, kind, HTTP status, event count, error class.
POST /alerts/<id>/testA signed 402signal.ping now; delivered: true on a 2xx.
DELETE /alerts/<id>Removes it, HTTP 204.

Every call answers only for the key that created the subscription. The signing secret is stored on the private writer volume beside the session store; to rotate it, delete and recreate the subscription. Alerts are not uptime monitoring: a host nobody checks stays silent. Check your own key and credits any time with GET /keys/usage.

The Offer Evidence Record, version 1

A versioned, protocol-independent description of the evidence a check produces for one offer: what was requested, what was offered, when, observed by whom, signed how and anchored where. Short name offer_evidence_record_v1. It documents what is issued today (the v4 exact x402 binding and the v5 group-offer observation) and reserves fields for what is not yet issued (MPP bindings, agent mandates). It introduces no new wire format, key, signer or on-chain transaction.

A score is an opinion about an endpoint computed from public inputs. A record is a signed statement, made by a third party at a specific second, that a specific request received a specific offer, bound to the payment the buyer then made. Everything below exists to make the record verifiable by someone who does not trust 402Signal.

Seven parts

PartContentToday's field
RequestThe exact seller request: complete HTTPS URL with its query, method, hash of the body bytes. No redirects, no normalization.decision_binding.request.url, .method, .body_sha256
OfferThe whole challenge the seller returned, hashed: every option in order with price, recipient, asset, network, timeout, facilitator data and supported extensions; plus which option the check selected.decision_binding.quote_sha256, .selected_index; the observed challenge in the reveal
RulesThe buyer's rules as submitted. Evidence of what was asked, not of human approval.request_json in the reveal
DecisionThe winner, the compared candidates with exclusion reasons, the selected payment, the scoring model.routing_evidence_json in the reveal
TimeWhen the challenge was received and when the observation stops being usable for signing. Never extended by retries, replay or approval.decision_binding.observed_at, .expires_at
SignatureA commitment to the private evidence in a public leaf; the leaf in an append-only Merkle log; an Ed25519-signed checkpoint; an inclusion proof from leaf to checkpoint.pq_trust.transparency.receipt
AnchorCumulative checkpoints written to Algorand MainNet in a transaction authorized with Falcon-1024, so a later rewrite of log history becomes detectable.Public trust descriptor and the log viewer

Commitment

leaf_commitment = SHA256("402signal.route_decision.v4" || 0x00 || canonical(evidence) || salt_32_bytes)
evidence = { evidence_version, binding, request_json, routing_evidence_json }

canonical is the RFC 8785 subset the verifiers implement: null, booleans, Unicode strings, arrays, objects and safe integers only; floats, duplicate keys, lone surrogates and unsafe integers are rejected. The two JSON strings keep their exact bytes. The public leaf carries only the leaf type, a minute-rounded timestamp, a nonce and the commitment; the reveal stays with the buyer. Nothing copies seller response bodies, payment headers, wallet keys, authorizations or signatures into the record.

Protocol profiles

ProfileOffer contentStatus
x402-exact-v2An x402 v2 PaymentRequired envelope with exact options on Base, Solana, Algorand or an observed EVM network.Issued today as the v4 binding.
x402-group-offer-v1One exact HTTPS GET API observed under buyer limits with a codec auto-detected from the live challenge.Issued today (v5).
mpp-charge-v1A WWW-Authenticate: Payment challenge classified as a charge: method, intent, amount, recipient, network.Observed and returned as terms; no signed binding yet.
mpp-session-v1, mpp-subscription-v1Session and subscription terms: unit price, suggested deposit, period.Observed as terms, never as a fixed price; no binding.
mandate-ref-v1Reserved: a reference to an agent mandate (AP2 cart or payment mandate, Visa Trusted Agent Protocol assertion, Mastercard verifiable intent) so one record carries both the offer and the authority the agent acted under.Reserved; nothing issued.

Adding a profile changes the offer part only; the commitment scheme and the verifiers do not change.

Verification

  1. Recompute the commitment from the reveal and compare it with the leaf.
  2. Check the inclusion proof from the leaf to the checkpoint and the checkpoint's Ed25519 signature under a key you pinned yourself, never one that arrived in the same response.
  3. Compare decision_binding in the response with the authenticated binding inside the evidence.
  4. Compare request_json with the request the buyer actually made.
  5. Before signing a payment, compare the seller's current challenge with the bound quote hash and refuse when the terms differ or expires_at has passed.

Implementations: verifyReceipt and withVerifiedRoute in @402signal/route-guard, the signal402 Python package, and the browser verifier. All three agree on the conformance fixture in CI.

Versioning and limits

evidence_version and the leaf type version the commitment. Historical leaves keep their original verification semantics; new profiles and reserved fields are additive; a change to the commitment scheme or the signature is a new leaf type and a new version of this document. The record proves that a specific request received a specific offer at a specific time and what the check decided. It does not prove delivery, output quality, seller identity or intent, legality, or that a human approved the submitted rules; it cannot recover a deleted private record or observe purchases that bypassed the check; the Falcon anchor protects the checkpoint history, not the seller payment.

The Trust page · Investigate with a retained record · The proposed x402 extension