koh / sylius-x402
x402 payment middleware for Sylius API routes: HTTP 402 with payment requirements agents can fulfil.
Requires
- php: ^8.5.11
- koh/sylius-agent-contracts: ^1.1
- psr/log: ^2.0 || ^3.0
- sylius/sylius: ^2.2.9 || ^2.3.0
- symfony/config: ^7.4 || ^8.0
- symfony/dependency-injection: ^7.4 || ^8.0
- symfony/event-dispatcher: ^7.4 || ^8.0
- symfony/http-foundation: ^7.4 || ^8.0
- symfony/http-kernel: ^7.4 || ^8.0
- symfony/routing: ^7.4 || ^8.0
Requires (Dev)
- koh/dev-sylius: ^1.0
- phpstan/extension-installer: ^1.4.3
- phpstan/phpstan: ^2.3.1
- phpstan/phpstan-doctrine: ^2.0.28
- phpunit/phpunit: ^11.5.57
Suggests
- sandermuller/php-x402: Real on-chain verification and settlement. Without it the bundle advertises payment requirements but settles nothing.
Provides
None
Conflicts
None
Replaces
None
- v1.1.35
- v1.1.34
- v1.1.33
- v1.1.32
- v1.1.31
- v1.1.30
- v1.1.29
- v1.1.28
- v1.1.27
- v1.1.26
- v1.1.25
- v1.1.24
- v1.1.23
- v1.1.22
- v1.1.21
- v1.1.20
- v1.1.19
- v1.1.18
- v1.1.17
- v1.1.16
- v1.1.15
- v1.1.14
- v1.1.13
- v1.1.12
- v1.1.11
- v1.1.10
- v1.1.9
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- dev-main / 1.0.x-dev
- v1.0.0
This package is auto-updated.
Last update: 2026-10-13 21:21:40 UTC
README
koh/sylius-x402
x402 payment middleware for Sylius: selected API paths
answer 402 Payment Required with a machine-readable price that an agent can
pay and retry.
It is off by default. Installing the bundle must not start charging for anything, and even when switched on it ships no settlement: the bundled verifier advertises prices and refuses every payment until you plug in a real one. That makes a shop protocol-correct and discoverable long before anyone decides to accept stablecoins.
Install
composer require koh/sylius-x402
// config/bundles.php Koh\SyliusX402\KohSyliusX402Bundle::class => ['all' => true],
koh/sylius-agent-contracts comes with it. There is no route to import — the
bundle works as request and response listeners.
Configure
# config/packages/koh_x402.yaml koh_x402: enabled: true network: 'eip155:84532' # CAIP-2 chain id; default is Base Sepolia, a testnet asset: '0x…' # contract address of the settlement asset pay_to: '0x…' # receiving address — required when enabled resources: - pattern: '^/agent/feed' # regex against the path amount: '10000' # smallest unit of the asset, digits only description: 'Product feed' - pattern: '^/_mcp' amount: '1000' methods: ['POST'] # GET stays a free capability probe
enabled: truewithoutpay_tofails the container build. A price with no address to pay would deny access and collect nothing.methodsis empty by default, which gates every method. GatingGETbut leavingHEADfree would reveal that a resource exists, and its size, without payment.- Gate API paths only — never the storefront or the checkout.
Settling payments
Alias PaymentVerifierInterface to your own implementation:
services: Koh\SyliusX402\Payment\PaymentVerifierInterface: '@App\Payment\X402Verifier'
verify() checks the X-PAYMENT header against the requirement and throws
PaymentRequiredException when it is missing or invalid. settle() runs only
after the controller produced a 2xx response — a request that ends in an
error has delivered nothing and is not charged. A settlement failure at that
point is logged and does not change the response; the caller already has what
they paid for. A string returned from settle() is sent as
X-PAYMENT-RESPONSE.
Letting authenticated agents through
Implement PaymentBypassVoterInterface from koh/sylius-agent-contracts to
exempt callers, for example agents holding an MCP access token. The listener
runs at priority 5, after the Symfony firewall (8), so the security token
exists when voters are asked. Base the decision on that token, never on a
request header the caller controls.
Output
A gated request without a valid payment:
HTTP/1.1 402 Payment Required Content-Type: application/json Cache-Control: no-store Access-Control-Allow-Origin: * { "x402Version": 1, "error": "This resource advertises payment requirements but settlement is not configured.", "accepts": [{ "scheme": "exact", "network": "eip155:84532", "maxAmountRequired": "10000", "resource": "https://shop.example.com/agent/feed", "description": "Product feed", "mimeType": "application/json", "payTo": "0x…", "maxTimeoutSeconds": 60, "asset": "0x…" }] }
A 402 is never cached: the requirement is per request, and a cached challenge would keep answering after payment.
The same price table feeds PayableOperationProviderInterface, so
koh/sylius-mpp declares it as x-payment-info in /openapi.json. A route
cannot advertise a price it does not charge, because declaration and
enforcement read the same configuration.
Decommission
Set enabled: false first if agents may be mid-payment, and settle anything
outstanding in your verifier. Then remove the composer require, the
config/bundles.php entry, config/packages/koh_x402.yaml and any verifier
alias. The bundle owns no database table and no cache pool.
Security
Report vulnerabilities privately — see SECURITY.md. Advisories are published as CSAF 2.0 at https://ole-hartwig.eu/.well-known/csaf/.
The family threat model, covering this package, is at https://github.com/ohartwig/sylius-agent-ready/blob/main/docs/threat-model.md.
Licence
MIT — see LICENSE.