Download the PHP package sandermuller/laravel-x402 without Composer

On this page you can find all versions of the php package sandermuller/laravel-x402. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-x402

laravel-x402

HTTP 402 stablecoin payments for Laravel routes.

Latest Version on Packagist Tests Total Downloads

Implements the x402 payment protocol on top of sandermuller/php-x402, the framework-agnostic core. You can charge per HTTP request in USDC, vary the price per resource, gate AI agents while leaving content free for humans, or pay upstream APIs automatically via Http::withX402().

[!NOTE] Status: alpha. Public API may shift before v1.0.

Requirements

Install

x402:install publishes the config and prompts for the recipient address plus (optionally) a buyer wallet private key, appending them to .env if not already set. The service provider is auto-discovered.

To run the steps manually:

Recipes

Gate a route (flat price)

::using() returns a chainable spec (Stringable). The string form ->middleware('x402:0.01,USDC,base') works too — the named middleware alias is registered for both x402 and x402.bots.

Per-route overrides (fluent builder)

Available overrides:

Method Effect
->payTo($address) Override x402.recipient for this route.
->onNetwork($slug) base, base-sepolia, ethereum, polygon, arbitrum, or raw CAIP-2.
->asAsset($symbol) Display symbol in the challenge.
->describing($text) Custom challenge description (otherwise auto-generated).
->skipWhen($predicate) Per-route skip. Returning true bypasses enforcement for this route only.

[!NOTE] The spec is immutable; fluent setters return a new instance. Chain the calls or assign the result. Mutating an aliased reference does nothing.

Specs survive route:cache since 0.8.0 — closures inside ->skipWhen() ride along via laravel/serializable-closure, so cached routes reconstruct the spec from the middleware string alone.

Variable price per resource

Implement Priceable on the bound model. The middleware reads the price from the first Priceable route parameter; the static ::using() amount becomes the base price when no parameter is priceable.

[!IMPORTANT] Priceable resolution needs Laravel's SubstituteBindings middleware in the route's chain. Without it, route parameters stay as raw scalars and the price quietly falls back to the base amount. The web group includes it by default; on API-only routes add it explicitly. Laravel's middleware- priority list orders SubstituteBindings ahead of named middleware automatically, so declaration order doesn't matter as long as it's there.

When a route binds multiple Priceable parameters (/articles/{article}/extras/{extra}), the first one in iteration order wins. Override by reordering the route signature so the priceable to charge is bound first, or by implementing Priceable only on the model that should drive the price.

[!NOTE] Priceable lookup keys off the URL path only — query strings are ignored. /articles/42?utm=x and /articles/42 resolve to the same challenge. If you need pricing to vary by query parameter, compute it inside the bound model's x402Price() (it has access to the active Request) instead of relying on the resource resolver.

Charge AI crawlers, free for humans

User-Agent matched against a curated list (see X402\Server\BotDetector from sandermuller/php-x402). Override or extend in config/x402.php:

Composes with Priceable (bots pay the model's price) and the builder (->skipWhen(), ->payTo(), etc. apply once the bot check passes).

Skip enforcement globally (grace cache, IP allowlist, plan tier)

Returning false short-circuits the entire pipeline — no challenge, no nonce claim, no facilitator round-trip.

[!WARNING] Call enforceWhen once, from a service provider's boot(). The predicate is stored on a process-global singleton, so calling it from a controller, job, or middleware mutates enforcement for all subsequent requests under long-lived workers (Octane, RoadRunner). Put per-request logic inside the closure, which receives the current Request. The package restores the boot-time predicate on every Octane RequestReceived event when Octane is installed.

For per-route skipping, prefer ->skipWhen() on the builder. It's scoped, doesn't touch global state, and survives Octane.

Read the settle receipt in your controller

After a successful settlement the SettleResult is exposed on the request:

Returns null when enforcement was skipped (e.g. enforceWhen returned false, or the route is wrapped in RequirePaymentFromBots and the caller was a human).

Throttle unpaid 402 floods

The package registers a throttle:x402 named rate limiter at 60 requests per IP per minute by default. Apply it before the payment middleware so unsigned requests don't tie up facilitator capacity:

Tune the cap via X402_RATE_LIMIT_PER_MINUTE. Set to 0 to skip the package's registration entirely and define your own limiter under the same key:

Pay outbound API calls

The wallet key comes from config/x402.php (X402_PRIVATE_KEY). On any upstream 402 the macro signs a fresh authorization, retries with X-PAYMENT, and returns the second response.

For per-tenant or per-request wallet selection (HD wallet derivation, KMS-backed signers, multi-tenant SaaS), bind a custom resolver and optionally pass a context to the macro:

The $context is forwarded onto every OutboundPaymentSent event so listeners can attribute spend back to a tenant, job, or correlation id without parsing the URL.

Wallet drivers

The buyer wallet used by Http::withX402() is selected by x402.wallet.driver. Two drivers ship in 0.8.0:

Driver Setup When to use
private_key X402_PRIVATE_KEY=0x… (default — no driver env needed) Local dev, single-key servers, low-stakes production
kms X402_WALLET_DRIVER=kms, X402_WALLET_KMS_PROVIDER=aws, plus AWS config SOC2 / FIPS environments where the key never touches the app

The kms driver requires an extra Composer package — the package's composer.json only suggests the SDK so adopters who do not need it do not pull a 200-package dependency tree:

The KMS key MUST be ECC_SECG_P256K1 with usage SIGN_VERIFY. Anything else fails the first time address() runs.

x402:verify-config reports the active driver and the resolved address so misconfiguration surfaces at boot, not at first paid call.

KMS adds 50–200ms per outbound payment. The Laravel HTTP client's pool() parallelises across requests; per-request the latency is unavoidable.

Per-tenant KMS

Tenants that each sign with a distinct KMS key bind TenantKmsWalletResolver (a reference implementation; copy and adapt when your tenant lookup differs):

The default tenant extraction reads Request::user()->tenant_id. Pass a tenantIdResolver: closure to the constructor when your dispatch key lives elsewhere (job context, header, subdomain).

Multi-tenant facilitator

Multi-tenant SaaS apps that ship a different facilitator per tenant — typically per-tenant CDP credentials, occasionally per-tenant self-hosted facilitator URLs — bind a custom FacilitatorResolver. The default resolver returns the env-configured Coinbase facilitator on every resolve; backward compatible with single-tenant setups.

Register in a service provider:

The resolver receives the current Request as $context so it can dispatch on tenant id, headers, route, model — whatever the host threads through.

Caveats.

Payment history

Opt-in (since 0.5.0). An Eloquent listener records every settled and rejected payment to a publishable x402_payments table. Off by default; enable in two steps:

Columns: id (ULID), status, resource, payer, pay_to, amount (atomic units, big-int string), asset, network, transaction, nonce, reason, extensions (json), meta (json), settled_at, plus the standard timestamps. Writes are idempotent on transaction and nonce: a retry through the x402.cache middleware updates the existing row instead of inserting a duplicate.

Attach per-request context (tenant_id, user_id, request_id)

Register a closure once in a service provider's boot(). The closure runs while the live Request is still in scope; the returned array lands in the meta column on every history row, and rides queue serialisation if you switch the listener to async.

Rewrite the resource for high-cardinality routes

Storing the full URL on /articles/{id} blows up cardinality fast. Register a formatter to stash a route name (or any stable identifier) instead:

The formatter receives a URL string, not a live Request, so it survives queue serialisation and the async-webhook dispatch path. The try/catch is mandatory: Route::getRoutes()->match() throws when no route matches (which happens on URLs that came in via the async-webhook path), and an unhandled throw inside the formatter breaks event emission.

[!NOTE] Hosts adding belongsTo relations (User, Tenant) extend the X402\Laravel\Models\Payment model in their own namespace. The shipped model is final for the read path; for relation work, compose via Eloquent global scopes or query the table directly.

Prune the rejected flood

Failed verifies on a public endpoint accumulate fast. The shipped pruner is bounded — it deletes rows older than a window:

Schedule it from app/Console/Kernel.php:

Idempotent paid responses (replay-on-retry)

If a client's connection drops between facilitator-settle and the response landing, the same signed authorization can be retried — but the nonce store will reject the duplicate and 402 a user who already paid. Stack x402.cache before the payment middleware to short- circuit the retry with the cached 2xx body:

Cache key: (network, from, nonce, signature bytes, method, resource). A forged signature with the same nonce will not replay, and a retry against a different route (or method) cannot reuse another route's cached response. TTL defaults to 1 hour; it has to exceed the replay window (X402_RESPONSE_CACHE_TTL).

[!NOTE] Cache scope is route-name-aware since 0.5.0. The service-provider binding passes a resourceResolver: closure that prefers Route::current()?->getName() over the raw URI path, so two retries against the same named route share cached responses across different query strings (which is normally what you want). Pricing-equivalent named routes share the same scope by design. If articles.show charges differently per query parameter, either rebind PaymentResponseCache without the resolver in your service provider, or split the routes so each pricing surface has its own name.

The cache stores the response status, body, and a safe-by-default header allow-list (Content-Type, Content-Length, Cache-Control, ETag, Last-Modified, Location, the PAYMENT-RESPONSE receipt, plus a few others). Set-Cookie, Authorization, Proxy-Authorization, Www-Authenticate, and Cookie are always dropped: anyone replaying a stolen X-PAYMENT header would otherwise inherit the original buyer's session. Hosts that need extra headers in the cached snapshot can extend the allow-list:

The hard-block list applies regardless of what you add.

What gets skipped: range, partial, and negotiated responses bypass the cache and run fresh on each retry. Any of Vary, Content-Range, Content-Encoding, Accept-Ranges, or a 206 Partial Content status triggers the skip. Don't stack x402.cache behind streaming responses (StreamedResponse / BinaryFileResponse); the cache only helps when the controller returns a fully-buffered body.

Events

The middleware and outbound macro dispatch Laravel events so you can record receipts, alert on failures, or hand work off to a queue.

Event Fires when Payload
X402\Laravel\Events\PaymentSettled Facilitator settles an inbound payment SettleResult $result, string $resource, ?PaymentRequired $challenge, ?PaymentSignature $signature, array $context
X402\Laravel\Events\PaymentRejected Facilitator rejects verify, or settle fails string $reason, string $resource, ?PaymentRequired $challenge, ?PaymentSignature $signature, array $context
X402\Laravel\Events\OutboundPaymentSent Http::withX402() countersigns a 402 challenge and retries string $url, string $amount, string $asset, string $network, string $payTo, mixed $context

Testing

The package ships a FakeFacilitator so consumers can test paid routes without a network round-trip:

X402::fake() swaps the bound FacilitatorClient for a recording fake that still dispatches the same Laravel events — Event::fake() works alongside. It also rebinds FacilitatorResolver to the fake, so a tenant-aware resolver is bypassed for the duration of the test; to exercise tenant routing, bind your own resolver explicitly with one FakeFacilitator per tenant rather than calling X402::fake().

Console commands

Command Purpose
php artisan x402:install Publish config and append X402_RECIPIENT / X402_PRIVATE_KEY to .env. Idempotent — existing values are never overwritten.
php artisan x402:verify-config Validate config, resolve the wallet, report missing values. Pass --ping to additionally probe the configured facilitator URL with the configured auth headers.
php artisan x402:list-routes Tabulate every route guarded by RequirePayment / RequirePaymentFromBots with its amount, network, asset, and per-route overrides.
php artisan x402:test-payment {url} Send a test request through Http::withX402() and report the settlement. Flags: --simulate-bot=GPTBot/1.0 to test the bots middleware, --ping for an unsigned request that just reports the 402 challenge, --json for machine-readable output.
php artisan x402:prune Delete x402_payments rows older than the window. Flags: --before=30days (relative) or --before=2026-01-01 (absolute), --status=settled\|rejected, --dry-run to preview.

Configuration

The most-set keys (see config/x402.php for the full reference):

Key Env Default
recipient X402_RECIPIENT required
network X402_NETWORK eip155:8453 (Base) — default for x402:0.01,USDC route-string macro when network is omitted
networks (array) Slug → CAIP-2 map (base, polygon, …). Add custom chains here.
asset.address X402_ASSET_ADDRESS USDC on Base
assets (array) Symbol → {address, decimals, eip712} map. Resolved when RequirePayment::using('0.01', 'PYUSD') picks a non-default asset.
facilitator.url X402_FACILITATOR_URL https://x402.org/facilitator
wallet.driver X402_WALLET_DRIVER private_key (also kms; see Wallet drivers recipe)
wallet.private_key X402_PRIVATE_KEY — (required when driver=private_key)
wallet.kms.provider X402_WALLET_KMS_PROVIDER — (aws; required when driver=kms)
wallet.kms.aws.region X402_WALLET_AWS_REGION
wallet.kms.aws.key_id X402_WALLET_AWS_KEY_ID
replay.cache_store X402_REPLAY_CACHE default cache store
response_cache.cache_store X402_RESPONSE_CACHE_STORE default cache store (used by the x402.cache middleware)
response_cache.ttl X402_RESPONSE_CACHE_TTL 3600 (seconds)
response_cache.prefix (string) x402:idem:v2: (bumped from v1 in 0.5.0 — adopters with custom prefixes should bump too; see UPGRADING.md)
bots.patterns (array | null) null (use built-in list)
bots.extra_patterns (array) []
history.enabled X402_HISTORY false (opt-in payment-history persistence)
history.queue X402_HISTORY_QUEUE null (sync; set a queue name to defer writes)
history.connection X402_HISTORY_CONNECTION null (default DB connection)
history.table X402_HISTORY_TABLE x402_payments

Custom networks and assets are picked up at request time — no rebuild required. Supply your own:

MCP support

For laravel/mcp integration, install sandermuller/laravel-x402-mcp.

Changelog

See GitHub Releases for the version history, or CHANGELOG.md.

Upgrading

Breaking changes between minor versions live in UPGRADING.md. 0.5.0 ships two source-level breaks (MiddlewareSpec immutability and the PaymentSettled / PaymentRejected constructor expansion) plus the php-x402 ^0.4 cache-prefix migration. Check the doc before bumping.

Security

If you discover a security issue, follow the disclosure process in SECURITY.md.

Credits

License

MIT. See LICENSE.


All versions of laravel-x402 with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
guzzlehttp/guzzle Version ^7.9
illuminate/console Version ^11.0|^12.0
illuminate/contracts Version ^11.0|^12.0
illuminate/database Version ^11.0|^12.0
illuminate/http Version ^11.0|^12.0
illuminate/support Version ^11.0|^12.0
nyholm/psr7 Version ^1.8
sandermuller/php-x402 Version ^0.7.0
symfony/psr-http-message-bridge Version ^7.0|^8.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package sandermuller/laravel-x402 contains the following files

Loading the files please wait ...