x402 security: the payment failure modes every implementation should test

x402 turns HTTP 402 into a real payment. When an implementation is wrong, nothing crashes: content unlocks for free, or money goes to the wrong place. Here is what to test, and how.

In short

The eight x402 failure modes to test are: a resource that serves content for an invalid signature, a facilitator /verify that accepts a forged one, weak resource binding, drifting terms, malformed challenges, weak transport, long authorization windows and facilitator coverage gaps.

On this page

x402 is a payment protocol built on HTTP. A client requests a resource and receives 402 Payment Required with a PAYMENT-REQUIRED header that lists the accepted payment options. The client signs one of them and retries with a PAYMENT-SIGNATURE header. The resource server, usually with a facilitator's help, verifies and settles the payment before it serves the content. See what x402 is and the three V2 headers for the full picture.

When an x402 implementation is wrong, nothing crashes. The resource unlocks for a signature nobody checked. A recipient address changes between two requests. A facilitator says yes to a forged authorization. The HTTP traffic looks normal, the logs look normal, and the money goes the wrong way or never arrives.

This article walks through eight failure modes we test for, what each one looks like on the wire, and the Settlens check that catches it. Every transcript below is real output from the Settlens CLI against its bundled demo target, a local server with a deliberately broken route for each failure shown. None of these tests needs a wallet or a private key, and none of them calls a facilitator's /settle endpoint.

Where an x402 V2 exchange can fail. Points 3 to 7 live in the 402 challenge and are visible to a passive scan; points 1 and 2 need the authorized active probe; point 8 compares the challenge with the facilitator's GET /supported.

1. Payment bypass: the resource fails open

The worst failure is also the simplest. The server checks that a PAYMENT-SIGNATURE header is present and serves the content without verifying it. Anyone who sends arbitrary bytes in that header gets the paid resource for free. It is a fail-open bug and a direct revenue leak, and it often survives code review because the happy path works perfectly: real clients send real signatures, so the missing verification never shows up in normal traffic.

The only reliable way to find it is to send an invalid payment and watch what happens. With the endpoint owner's explicit approval, the Settlens active probe builds one intentionally invalid authorization from the first advertised payment option (for an EVM option, an all-zero 65-byte signature from the zero address) and retries the request with it. If the resource answers with any 2xx status, security.invalid_signature_fail_closed fails at critical severity. A separate check, transport.invalid_payment_status, expects the status the HTTP transport defines: 400 for malformed payment data or 402 when verification or settlement fails.

Active probe against a fail-open route (demo target)
settlens scan http://127.0.0.1:4402/bypass \
    --active --authorized --authorization-ref DEMO-APPROVAL-002
Settlens x402 assessment eafd7f6a
Targets 1 · Active 1 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/bypass
13 pass · 1 fail · 1 warn · 0 skip
  FAIL critical Invalid payment authorization is denied
       The endpoint returned HTTP 200 after an invalid payment authorization.
  WARN medium   Invalid payment uses a standard failure status
       Expected HTTP 400 or 402 for invalid payment, received HTTP 200.

The scan exits with code 1, so a CI job stops here. Note the scope line: one active target, zero settlement calls. The probe proves the endpoint refuses a bad payment; it cannot create a good one.

Authorized testing only

The active probe sends traffic the endpoint owner must approve. Settlens refuses to run it without both --active and --authorized plus an --authorization-ref that names the approval, and it records that reference in the report.

2. The facilitator accepts a forged signature

Many resource servers delegate verification to a facilitator: they forward the payment payload to POST /verify and serve the content when the answer says isValid: true. If the facilitator's own verification is broken, every resource server that relies on it inherits a payment bypass, and none of them can see it from their own code.

When you pass --facilitator to an authorized active scan, Settlens sends the same invalid payload to the facilitator's /verify. It expects a conformant VerifyResponse, a JSON object with a boolean isValid (facilitator.verify_response_schema), and it expects that value to be false (security.facilitator_rejects_invalid_signature, critical). The demo target ships a deliberately broken facilitator route that approves everything:

Facilitator that approves an all-zero signature (demo target)
settlens scan http://127.0.0.1:4402/secure \
    --facilitator http://127.0.0.1:4402/facilitator-accept \
    --active --authorized --authorization-ref DEMO-APPROVAL-003
Settlens x402 assessment 9fa4dcf9
Targets 1 · Active 1 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/secure
26 pass · 1 fail · 0 warn · 0 skip
  FAIL critical Facilitator /verify rejects an invalid signature
       The facilitator reported isValid=true for an intentionally invalid, all-zero signature.

Test the facilitator directly, not only through the resource. The two can disagree, and the disagreement is exactly where money goes missing. Our guide to x402 facilitator security covers what /verify, /settle and /supported are each expected to do.

3. Weak payment-to-resource binding

Every x402 V2 challenge carries a resource object whose url says what is being paid for. When that URL does not match the URL the client requested, the client cannot tell what it is paying for. Common causes are mundane: one shared challenge for every route, a stale host name after a proxy or CDN change, or a query parameter dropped when the challenge is built. The effect is not mundane: a payment can be steered to, or reused for, a different resource than the one the agent asked for.

The check binding.resource_url compares resource.url with the requested URL, path and query included (the fragment is ignored), and fails at high severity when they differ. You can run it on a captured header with the free x402 payment request checker: paste the header and the URL you requested. On the server side, the matching control is to verify that the terms in a payment payload are the terms you issued for this resource (amount, recipient, asset and network) rather than accepting any valid signature that pays the right address. A passive scan cannot observe that server-side logic; it can only confirm that the challenge itself is bound correctly.

4. Recipient, asset and price drift

The payTo, asset and amount fields decide who gets paid, in which token and how much. If they change between two identical requests, something is wrong: a dynamic pricing bug, two backends behind a load balancer serving different configurations, a recipient rotation nobody reviewed, or tampering. If they drift away from the values you reviewed, funds can be silently redirected or customers overcharged.

Settlens catches drift in two ways. stability.payment_requirements requests the resource several times (two by default) and compares the resource, accepts and extensions it receives, ignoring known ephemeral fields such as a Solana recentBlockhash. A change raises a warning. Local policy checks then pin the values you approved: policy.recipient, policy.asset, policy.network, policy.scheme and policy.maximum_amount fail at high severity when the advertised terms leave the reviewed set. The demo's drift route alternates its price on every request:

Price that changes between identical requests (demo target)
settlens scan http://127.0.0.1:4402/drift
Settlens x402 assessment 56c93ec9
Targets 1 · Active 0 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/drift
12 pass · 0 fail · 1 warn · 0 skip
  WARN medium   Payment requirements remain stable
       Payment terms changed between identical unauthenticated requests.

A recipient that changes without a deploy is a red flag, not a feature. Our guide to detecting payment drift covers the stability check, policy pins and continuous monitoring in detail.

5. Malformed or non-standard challenges

Protocol drift breaks integrations without an obvious error. A server still emitting x402Version: 1 where clients expect 2, an amount written as "0.01" instead of an integer string of atomic units, a network written as base instead of the CAIP-2 identifier eip155:8453, a payTo that is not a 20-byte address on an EVM chain, or the same payment option listed twice. The official SDK schema catches some of these, such as a network that is not CAIP-2, but accepts others: a well-formed version-1 object, a decimal amount, a malformed payTo or a duplicate option. Hand-written clients may accept any of them and interpret them differently. To your users, every variant looks like a mysterious payment failure.

A passive scan checks that the PAYMENT-REQUIRED header is present and decodes as Base64 JSON, that the pinned official @x402/core 2.28.0 SDK accepts the object, that the V2 core shape is there, and, with its own rules on top of the SDK, that every payment option is well formed and that no option is duplicated. If you are still moving an implementation off version 1, the x402 V1 to V2 migration guide lists what changed on the wire. The demo's broken route answers 402 but forgets the header entirely:

402 without a PAYMENT-REQUIRED header (demo target)
settlens scan http://127.0.0.1:4402/missing-header
Settlens x402 assessment fe4514e2
Targets 1 · Active 0 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/missing-header
4 pass · 1 fail · 0 warn · 1 skip
  FAIL high     PAYMENT-REQUIRED header is present
       The response does not include PAYMENT-REQUIRED.
  SKIP medium   Payment requirements remain stable
       Only one successful sample was available.

The stability check is skipped here because there was no decodable challenge to compare. A skip is not a pass: it means the prerequisite evidence was missing.

6. Transport weaknesses

Payment terms are only as trustworthy as the channel that carries them. Over plaintext HTTP, anyone on the network path can rewrite payTo or amount before the agent signs; transport.https warns about any non-loopback endpoint that is not served over HTTPS. A redirect moves the challenge away from the URL that was assessed, so Settlens never follows one: transport.redirect records the sanitized destination and warns instead. And a 402 response body should be small. Settlens stops reading at 1 MiB (transport.response_body_limit), because a very large challenge body wastes agent bandwidth and can mean the protected content is being sent before payment.

7. Long authorization windows and replay exposure

The maxTimeoutSeconds field is the maximum time the server allows for completing a payment. A long value invites long-lived authorizations, and the longer an authorization stays valid, the longer a leaked or intercepted one can be presented again. Settlens warns when any advertised value exceeds 3600 seconds (freshness.timeout_window, medium severity); keep it as short as settlement allows.

For the EVM exact scheme with its default EIP-3009 transfer method, each authorization is a signed message with a validAfter/validBefore window and a nonce, and the token contract refuses a nonce that was already used when the transfer settles. /verify commits no state, so until settlement nothing on-chain has consumed the nonce: a server that serves content after verification alone must make sure the same authorization is not accepted twice. Be precise about what a test can show here: Settlens checks the window of a captured authorization offline with verify-payment, and it checks that the advertised windows are bounded. It does not check whether a nonce was used on-chain and it does not test replay. Our EIP-3009 explainer covers the authorization format.

8. Facilitator coverage gaps

A resource can advertise a scheme and network that its facilitator does not support. The agent signs a perfectly valid payment that can never be verified or settled, and the failure surfaces as a refused purchase. Settlens reads the facilitator's GET /supported and checks its schema, its x402 V2 capability and its signer metadata, then cross-checks every advertised scheme and network against the supported kinds (facilitator.requirement_coverage) and the published signers (facilitator.signer_coverage). The demo includes a facilitator that only supports Base mainnet while the resource asks for Base Sepolia:

Facilitator that does not cover the advertised network (demo target)
settlens scan http://127.0.0.1:4402/secure \
    --facilitator http://127.0.0.1:4402/facilitator-empty
Settlens x402 assessment b92b9c27
Targets 1 · Active 0 · Settlement calls 0
 
127.0.0.1  GET http://127.0.0.1:4402/secure
22 pass · 1 fail · 0 warn · 0 skip
  FAIL high     Facilitator covers advertised payment requirements
       1 advertised payment choice(s) are absent from facilitator capabilities.

The failure modes at a glance

The table is generated from the Settlens check catalog, the same source the CLI, the reports and the check reference use. Mode tells you what a check needs: offline checks run on the challenge content alone, passive checks need live unauthenticated requests, active checks need the authorized probe and facilitator checks read GET /supported.

Failure modes and the Settlens checks that cover them
Failure modeChecksMode · highest severity
1. Fail-open resourcesecurity.invalid_signature_fail_closed
transport.invalid_payment_status
active · critical
2. Facilitator accepts a forged signaturesecurity.facilitator_rejects_invalid_signature
facilitator.verify_response_schema
active · critical
3. Weak payment-to-resource bindingbinding.resource_urloffline · high
4. Recipient, asset and price driftstability.payment_requirements
policy.recipient
policy.asset
policy.maximum_amount
policy.network
passive, offline · high
5. Malformed challengetransport.payment_required_header
transport.payment_required_encoding
schema.official_x402_sdk
schema.payment_required
schema.payment_requirements
schema.duplicate_requirements
passive, offline · high
6. Transport weaknessestransport.https
transport.redirect
transport.response_body_limit
passive · medium
7. Long authorization windowsfreshness.timeout_windowoffline · medium
8. Facilitator coverage gapsfacilitator.requirement_coverage
facilitator.signer_coverage
facilitator · high

What these tests do not tell you

Each check reports observed behavior during the recorded window. A clean report does not prove that untested application code is free of vulnerabilities, that a real wallet signature will settle, that balances are sufficient or that a nonce is unused on-chain. Settlens does not call /settle, does not broadcast transactions and does not inspect the content you deliver after payment. It is evidence about the payment handshake, not a certification. The assessment methodology spells out what each status and severity means.

Within that boundary, every failure above is deterministic to test and cheap to run on every release. Start with the passive checks, add the facilitator, then run the authorized probe once you have the owner's approval. The practical testing checklist walks through the commands in order, and a pilot runs them against your own endpoints with a report your partners can verify.

Sources

  1. x402 protocol specification, version 2 (§5 types, §7 facilitator interface, §10 security considerations)x402 Foundation · github.com
  2. x402 V2 HTTP transport: headers and error status codesx402 Foundation · github.com
  3. Scheme exact on EVM: EIP-3009 authorization payload and verificationx402 Foundation · github.com
  4. EIP-3009: Transfer With AuthorizationEthereum Improvement Proposals · eips.ethereum.org
  5. CAIP-2: Blockchain ID specificationChain Agnostic Standards Alliance · github.com