PHPackages                             sandermuller/php-x402 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. sandermuller/php-x402

ActiveLibrary[HTTP &amp; Networking](/categories/http)

sandermuller/php-x402
=====================

Framework-agnostic PHP implementation of the x402 payment protocol (HTTP 402 stablecoin settlement).

0.8.0(1mo ago)1498[1 PRs](https://github.com/SanderMuller/php-x402/pulls)1MITPHPPHP ^8.2CI passing

Since May 8Pushed 6d agoCompare

[ Source](https://github.com/SanderMuller/php-x402)[ Packagist](https://packagist.org/packages/sandermuller/php-x402)[ Docs](https://github.com/sandermuller/php-x402)[ RSS](/packages/sandermuller-php-x402/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (33)Versions (13)Used By (1)

php-x402
========

[](#php-x402)

[![Latest Version on Packagist](https://camo.githubusercontent.com/2359ff243fc1d6c159fe1dca67212ed783fdcd69131587be4d04606ce4a2644b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73616e6465726d756c6c65722f7068702d783430322e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/sandermuller/php-x402)[![GitHub Tests Action Status](https://camo.githubusercontent.com/c39f5f3d045947a7cdfa3e2b4ae57180fe35d80e2568ffaf43edd170a93647ba/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f73616e6465726d756c6c65722f7068702d783430322f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/sandermuller/php-x402/actions/workflows/run-tests.yml)[![Total Downloads](https://camo.githubusercontent.com/0970af6c298340168077faf73ba279e0b686ba0ed05350592fcb21f07bfd5d9d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f73616e6465726d756c6c65722f7068702d783430322e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/sandermuller/php-x402)[![License](https://camo.githubusercontent.com/21447bd009ec23ffc9597975f2a0d5a389377c0fecc616b2a80961124e9a46c9/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f73616e6465726d756c6c65722f7068702d783430322e7376673f7374796c653d666c61742d737175617265)](LICENSE)

Framework-agnostic PHP implementation of the [x402 payment protocol](https://www.x402.org/).

HTTP 402 stablecoin settlement. Pay-per-request APIs without subscriptions, API keys, or fiat rails. EIP-3009 `transferWithAuthorization` on EVM chains via the Coinbase facilitator (or any compatible facilitator).

Note

Pre-1.0 (`0.x`). Public surface is feature-complete for v1 of the spec: HTTP / MCP / A2A transports, `exact` + `upto` schemes on EVM, ERC-7710 shape, SVM pass-through, replay protection, response-cache idempotency, and Bazaar discovery. See [`ROADMAP.md`](https://github.com/SanderMuller/php-x402/blob/main/ROADMAP.md) for what's shipped vs. deferred.

What it does
------------

[](#what-it-does)

The server middleware drops a 402 challenge on protected resources, verifies and settles signed payments via a facilitator, then hands the request to the inner handler. The client decorator auto-pays 402 responses by signing an EIP-3009 authorization with your operator wallet and retrying. Both pieces are pure PSR-7/15/17/18 + PSR-16/3, so they wire into Slim, Mezzio, raw Symfony, or the Laravel adapter.

Install
-------

[](#install)

```
composer require sandermuller/php-x402
```

Requires PHP `^8.2`. Pulls in PSR-7/15/17/18, PSR-16 cache, PSR-3 logger.

Optional extensions for performance:

- `ext-secp256k1`: ~50× faster signature verification.
- `ext-gmp`: required for BigInteger math in signature recovery.

Quick start
-----------

[](#quick-start)

### Server: gate a resource behind 402

[](#server-gate-a-resource-behind-402)

```
use X402\Facilitator\CoinbaseFacilitator;
use X402\Protocol\PaymentRequired;
use X402\Schemes\Evm\ExactScheme;
use X402\Server\PaymentEnforcer;
use X402\Server\StaticPriceTable;

$priceTable = new StaticPriceTable();
$priceTable->set('/premium', new PaymentRequired(
    scheme: 'exact',
    network: 'eip155:8453',                  // Base mainnet
    amount: '10000',                          // 0.01 USDC (6 decimals)
    asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    payTo: '0xYourReceivingAddress',
));

$middleware = new PaymentEnforcer(
    priceTable:      $priceTable,
    facilitator:     CoinbaseFacilitator::default($psr18Client, $psr17),
    nonceStore:      $atomicNonceStore,                  // see Replay protection
    schemes:         ['exact' => new ExactScheme()],
    responseFactory: $psr17,
    streamFactory:   $psr17,
);
```

Pipe `$middleware` through any PSR-15 dispatcher.

### Client: pay automatically on 402

[](#client-pay-automatically-on-402)

```
use X402\Client\PayingClient;
use X402\Client\PrivateKeyWallet;

$client = new PayingClient(
    inner:  $psr18Client,                       // Guzzle, Symfony HttpClient, etc.
    wallet: new PrivateKeyWallet($operatorPk),  // or an AwsKmsWallet — see Wallets row in Surface
);

$response = $client->sendRequest($request);     // 402 → sign → retry → 200
```

`PrivateKeyWallet` is fine for tests and CLI tools. **For production, use `X402\Client\AwsKmsWallet`** (or subclass `X402\Client\KmsWallet` for GCP / Azure / Vault / HSM) so private keys never sit in process memory. The abstract owns DER decoding, EIP-2 low-s normalisation, on-curve validation, and recovery-id derivation; subclasses provide two thin methods. See [`docs/kms.md`](https://github.com/SanderMuller/php-x402/blob/main/docs/kms.md) for wiring + the GCP / Vault sketches.

Surface
-------

[](#surface)

LayerClassServer middleware`X402\Server\PaymentEnforcer` (PSR-15)Response cache`X402\Server\PaymentResponseCache` (PSR-15)Price table`X402\Server\StaticPriceTable`, `RegexPriceTable`Bot detection`X402\Server\BotDetector`Idempotency key`X402\Server\IdempotencyKeyBuilder` (transport-agnostic)Client decorator`X402\Client\PayingClient` (PSR-18)Facilitator`X402\Facilitator\CoinbaseFacilitator`Event hooks`X402\Facilitator\DispatchingFacilitator` (wraps any FacilitatorClient, fires closures on every outcome)Outcome DTO`X402\Facilitator\PaymentOutcome`, `PaymentOutcomeKind` (enum)Payment history`X402\PaymentHistory\PaymentRowBuilder` (flat-row helper)Webhook primitives`X402\Webhook\SignatureVerifier`, `WebhookEvent`, `WebhookDedupStore`Schemes (opt-in)`X402\Schemes\ReplayKeyExtractor` (per-scheme replay extraction)Signing`X402\Schemes\Evm\AuthorizationSigner`, `Eip712Hasher`Wallets`X402\Client\PrivateKeyWallet`, `HdWallet` (BIP-32), `KmsWallet` (abstract) + `AwsKmsWallet`Verification`X402\Schemes\Evm\SignatureVerifier`Replay store`X402\Replay\NonceStoreContract`, `CallbackNonceStore`Decimal helper`X402\Support\PriceParser`Testing helpers`X402\Testing\PaymentRequiredBuilder`, `FakeFacilitator`CLI`bin/x402 decode `Composing policy
----------------

[](#composing-policy)

`PaymentEnforcer` accepts an optional `shouldEnforce` predicate that gates the entire pipeline per request. Useful for bot-only payment, IP allowlists, geo policy, or plan-tier exemption:

```
use Psr\Http\Message\ServerRequestInterface;
use X402\Server\PaymentEnforcer;

$middleware = new PaymentEnforcer(
    // ... priceTable, facilitator, nonceStore, schemes, factories ...
    shouldEnforce: fn (ServerRequestInterface $request): bool
        => str_starts_with($request->getUri()->getPath(), '/api/paid/'),
);
```

Predicate returns `false` → inner handler runs, no challenge / no nonce claim / no facilitator hit. Default (`null`) = always enforce. Compose multiple policies downstream: `fn ($r) => $bot($r) && $geo($r) && $plan($r)`.

`X402\Server\BotDetector` ships a curated list of AI agents / assistants / scrapers / search crawlers (~70 patterns from ) for the bot-only-payment shape:

```
use X402\Server\BotDetector;

$detector = new BotDetector(extra: ['MyCustomCrawler']);

$middleware = new PaymentEnforcer(
    // ...
    shouldEnforce: static fn (ServerRequestInterface $request): bool
        => $detector->isBot($request->getHeaderLine('User-Agent')),
);
```

Override the default list with `patterns:`, extend it with `extra:`, or pass `patterns: []` to disable detection. Match is case-insensitive substring on the User-Agent.

Response-cache idempotency
--------------------------

[](#response-cache-idempotency)

`PaymentResponseCache` is a separate PSR-15 middleware that sits **before** `PaymentEnforcer` in the chain. It caches paid 2xx responses keyed by `(network, from, nonce, signature bytes)` and replays the cached body on duplicates, so a client whose connection drops between facilitator settle and response delivery sees the paid response on retry instead of a 402.

```
use X402\Server\PaymentResponseCache;
use X402\Server\PaymentResponseCacheOptions;

// Pipeline: PaymentResponseCache → PaymentEnforcer → handler
$pipeline = [
    new PaymentResponseCache(
        $psr16Cache, $psr17, $psr17,
        schemes: ['exact' => new ExactScheme()],
        options: new PaymentResponseCacheOptions(ttl: 3600),
    ),
    $enforcer,
];
```

Same Redis-backed PSR-16 store as the nonce store. TTL should comfortably exceed the nonce TTL so retries past nonce expiry still hit the cache.

Replay protection
-----------------

[](#replay-protection)

Important

Replay protection requires an **atomic** "set-if-absent with TTL" store: Redis `SET key value NX EX ttl` or equivalent. Two concurrent requests carrying the same `(network, from, nonce)` MUST resolve to a single winner; anything else lets a settled signature replay.

- `InMemoryNonceStore`: in-process only, single worker.
- `Psr16NonceStore`: `has() + set()` on PSR-16. **NOT atomic** (PSR-16 has no add-if-absent primitive). Acceptable for tests and single-worker dev only. A small race window allows two workers to both claim the same nonce and both settle.
- Production: use `LaravelNonceStore` (in [`sandermuller/laravel-x402`](https://github.com/sandermuller/laravel-x402), backed by `Cache::add()`), or implement `NonceStoreContract` against Redis `SETNX EX` directly. Anything else breaks the security contract.

Event hooks and payment history
-------------------------------

[](#event-hooks-and-payment-history)

`DispatchingFacilitator` wraps any `FacilitatorClient` and fires a closure with a `PaymentOutcome` on every verify / settle outcome — `VerifyRejected`, `VerifyError`, `SettleSucceeded`, `SettlePending`, `SettleFailed`, `SettleError`. Adopters wire host-specific event dispatch (Symfony EventDispatcher, log channels, metrics) inside the closure:

```
use X402\Facilitator\DispatchingFacilitator;
use X402\Facilitator\PaymentOutcome;
use X402\Facilitator\PaymentOutcomeKind;
use X402\PaymentHistory\PaymentRowBuilder;

$facilitator = new DispatchingFacilitator(
    inner: CoinbaseFacilitator::default($psr18Client, $psr17),
    onOutcome: function (PaymentOutcome $outcome, array $context) use ($db): void {
        $db->insert('x402_payments', PaymentRowBuilder::fromOutcome($outcome, $context));
    },
    captureContext: fn (): array => ['user_id' => $request->getAttribute('user')?->id],
);
```

`PaymentRowBuilder::fromOutcome()` returns a flat array matching the laravel-x402 `x402_payments` migration shape — adopters using Eloquent feed it directly into `Payment::query()->updateOrCreate(...)`. Listener and context-capture exceptions on `*-error` paths are silently swallowed so the original facilitator throwable always propagates to the caller.

Framework adapters
------------------

[](#framework-adapters)

- Laravel: [`sandermuller/laravel-x402`](https://github.com/sandermuller/laravel-x402)
- Laravel MCP: [`sandermuller/laravel-x402-mcp`](https://github.com/sandermuller/laravel-x402-mcp)

Testing
-------

[](#testing)

```
composer test          # vendor/bin/pest
composer qa            # rector + pint + phpstan (auto-fix variants)
composer ci            # all gates in --dry-run mode (suitable for CI / pre-push)
```

For adopter integration tests, the `X402\Testing` namespace ships:

- `PaymentRequiredBuilder`: fluent USDC-on-Base / Base-Sepolia helpers, atomic-unit conversion via `X402\Support\PriceParser` (since 0.4.1).
- `FakeFacilitator`: canonical test double. Settles locally, configures outcomes via `rejectVerify('reason')` / `failSettle('reason')` mid-test, records every call (signature + challenge + returned `verifyResults()` / `settleResults()`), and ships PHPUnit assertion helpers (`assertVerified`, `assertSettled`, `assertNothingSettled`).

Conformance vectors in `tests/Fixtures/eip712-vectors.json` mirror the upstream Coinbase Go test suite. A hash deviation here is a deviation from the spec.

Roadmap
-------

[](#roadmap)

See [`ROADMAP.md`](https://github.com/SanderMuller/php-x402/blob/main/ROADMAP.md) for shipped scope and explicit non-goals (Solana client-side signing, Stellar, ERC-7710 redelegation chain hashing, RFC 9421, SIWX).

Changelog
---------

[](#changelog)

See [`CHANGELOG.md`](https://github.com/SanderMuller/php-x402/blob/main/CHANGELOG.md). Updated automatically from GitHub release notes.

Upgrading
---------

[](#upgrading)

See [`UPGRADING.md`](https://github.com/SanderMuller/php-x402/blob/main/UPGRADING.md) for migration notes between minor / major bumps (`0.2.x` → `0.3.0`, `0.3.x` → `0.4.0`, `0.6.x` → `0.7.0`, `0.7.x` → `0.8.0`).

Contributing
------------

[](#contributing)

See [`CONTRIBUTING.md`](https://github.com/SanderMuller/php-x402/blob/main/CONTRIBUTING.md) for boundary rules, the QA bar, the crypto-stub gotcha, and spec-drift policy.

Security
--------

[](#security)

See [`SECURITY.md`](https://github.com/SanderMuller/php-x402/blob/main/SECURITY.md) for vulnerability reporting.

License
-------

[](#license)

MIT.

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance96

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

11

Last Release

30d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/abeb7bd51fd77656b4133bdcdf4eca99cc8b495b83ca72f63674ff15f2b72a39?d=identicon)[SanderMuller](/maintainers/SanderMuller)

---

Top Contributors

[![SanderMuller](https://avatars.githubusercontent.com/u/9074391?v=4)](https://github.com/SanderMuller "SanderMuller (65 commits)")

---

Tags

psr-18psr-15paymentsstablecoinUSDCx402http-402eip-3009eip-712

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sandermuller-php-x402/health.svg)

```
[![Health](https://phpackages.com/badges/sandermuller-php-x402/health.svg)](https://phpackages.com/packages/sandermuller-php-x402)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.8k19.1M1.7k](/packages/cakephp-cakephp)[tempest/framework

The PHP framework that gets out of your way.

2.2k31.1k11](/packages/tempest-framework)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

84735.1k](/packages/flow-php-flow)[windwalker/framework

The next generation PHP framework.

25640.0k1](/packages/windwalker-framework)[typo3/cms-core

TYPO3 CMS Core

3312.9M4.7k](/packages/typo3-cms-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
