---
title: "Cloudflare, Coinbase e Lightning hanno appena resuscitato HTTP 402 dopo 29 anni di oblio: ecco come far pagare gli agenti AI che scrapano il tuo sito con Laravel, Symfony e PHP vanilla"
description: "HTTP 402 Payment Required in produzione per monetizzare crawler AI: x402, L402, Cloudflare pay-per-crawl, middleware Laravel, Symfony e PHP vanilla."
canonical: "https://www.mauriziofonte.it/blog/post/http-402-payment-required-monetizzare-crawler-ai-x402-l402-laravel-symfony-php.html"
author: "Maurizio Fonte"
date: "2026-05-01T08:45:00+02:00"
modified: "2026-06-19T10:51:42+02:00"
section: "API Design"
tags: ["API Design", "Architettura Software"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[API Design](https://www.mauriziofonte.it/blog/cat/api-design.html) [Architettura Software](https://www.mauriziofonte.it/blog/cat/architettura_software.html)

# [Cloudflare, Coinbase e Lightning hanno appena resuscitato HTTP 402 dopo 29 anni di oblio: ecco come far pagare gli agenti AI che scrapano il tuo sito con Laravel, Symfony e PHP vanilla](https://www.mauriziofonte.it/blog/post/http-402-payment-required-monetizzare-crawler-ai-x402-l402-laravel-symfony-php.html)

Venerdì 1 Mag 2026, alle 08:45 ![Cloudflare, Coinbase e Lightning hanno appena resuscitato HTTP 402 dopo 29 anni di oblio: ecco come far pagare gli agenti AI che scrapano il tuo sito con Laravel, Symfony e PHP vanilla](https://www.mauriziofonte.it/image/004130200-http-402-payment-required-monetizzare-crawler-ai-x402-l402-laravel-symfony-php.jpg)

Il 3 aprile 2026, il giorno successivo al passaggio formale di x402 sotto la Linux Foundation, ho acceso un middleware sperimentale sul mio blog di laboratorio, un Hetzner AX52 (Ryzen 7 7700, 64 GB RAM DDR5, 2x NVMe RAID 1) con Laravel 12, PHP-FPM 8.3, Nginx 1.26, Redis 7.2 e un wallet Coinbase Base testnet funded con 50 USDC. Ho esposto tre URL con risposta `HTTP/1.1 402 Payment Required` e body JSON x402-compliant al costo di 0,003 USDC per richiesta (circa tre millesimi di dollaro). Le prime 72 ore sono state deludenti come copione: zero pagamenti, 127 bot scappati al primo 402. Poi il 6 aprile un Claude Code v2.1.41 con Coinbase wallet pre-autorizzato ha pagato e consumato l'endpoint. Il 7 aprile un OpenCode 1.3 ha fatto la stessa cosa. Nei 14 giorni di esercizio ho misurato 1.842 richieste che hanno ricevuto 402, 423 che hanno completato il pagamento e retry con successo, incasso cumulativo di 1,27 USDC lordi (al netto del gas Base, meno di un centesimo). Numeri piccoli, assolutamente, ma verificabili on-chain e sufficienti a validare il pattern end-to-end.

La narrativa commerciale attorno a HTTP 402 nel 2026 è sopra le righe: McKinsey proietta [tra $3 e $5 trilioni di agentic commerce entro il 2030](https://www.coinbase.com/developer-platform/discover/launches/x402), Cloudflare dichiara che i suoi siti emettono oltre un miliardo di risposte 402 al giorno, il mercato x402 ha una valutazione ecosystem di ~$7 miliardi. La realtà on-chain misurata da CoinDesk e Artemis a marzo 2026 è più ruvida: $28.000 di volume medio giornaliero reale, circa metà delle transazioni classificate come "gamified" (testing, non commercio), daily transaction count sceso del 92% dal picco di dicembre 2025. Questo articolo ti mostra come implementare HTTP 402 davvero, con codice Laravel 12, Symfony 7.2 e PHP vanilla, e ti dice quando ha senso e quando è premature optimization travestita da feature. Prima di farti pagare dagli agenti, devi saper rispondere loro in un formato che capiscono: ne ho parlato nel [pezzo di ieri sulla content negotiation text/markdown on-origin](https://www.mauriziofonte.it/blog/post/content-negotiation-text-markdown-senza-cloudflare-laravel-symfony-php-vanilla.html). Il trasporto viene prima del commercio.

## Perché HTTP 402 è stato fermo dal 1997

Lo status code è documentato in [RFC 9110 §15.5.2](https://www.rfc-editor.org/rfc/rfc9110#section-15.5.2) (giugno 2022, stabilizzazione dell'HTTP Semantics) con una formulazione ancora asciutta: *"The 402 (Payment Required) status code is reserved for future use."* Era già così in RFC 2068 (gennaio 1997) e in RFC 2616 (giugno 1999). Per ventinove anni "reserved for future use" non è diventato nient'altro.

La ragione è doppia. Tecnicamente, mancava l'infrastruttura di micropagamento: le carte di credito hanno un costo fisso di interchange più processing fees che rende economicamente assurdo un pagamento di 0,05 dollari, figurarsi 0,003. I circuiti Visa/Mastercard non scalano sotto 30 centesimi senza bruciare il merchant. ACH e SEPA hanno latenza di ore o giorni, incompatibile con un handshake HTTP che deve concludersi in secondi. Nessuna rete permetteva payment programmatico sub-cent senza un account preesistente.

Economicamente, mancava la domanda. Finché i client di servizi web erano umani, il pattern dominante era subscription (SaaS mensile) o advertising (traffic-in-exchange-for-content). Entrambi assumono un utente persistente con identità. HTTP 402 per pagamenti one-shot anonimi non aveva un caso d'uso di massa.

Entrambi i vincoli si sono rotti nel 2024-2025. Lightning Network ha abilitato Bitcoin sub-cent settlement in millisecondi; USDC su Layer 2 (Base, Arbitrum, Polygon) ha portato stablecoin con finality rapida e fee prossime allo zero. Parallelamente, gli agenti AI hanno creato una nuova classe di client: un software che gira 24/7, decide autonomamente cosa comprare, non ha carta di credito propria ma ha un wallet programmatico, e processa migliaia di richieste giorno per cui ogni micro-pagamento vale l'overhead. Per la prima volta in trent'anni, l'infrastruttura tecnica e la domanda economica si sono incontrate.

Se gestisci pipeline AI di produzione con agenti che consumano dati, o se hai contenuti proprietari che agenti terzi stanno già scansionando gratis, nel [mio hub dedicato all'AI per aziende](https://www.mauriziofonte.it/ai-aziende/ai-aziende-integrazione.html) trovo gli articoli tecnici che uso per orientare i clienti su integrazione agent-to-payment e governance dei costi.

## Cosa è cambiato tra il 2023 e oggi: timeline condensata

Sei eventi hanno costruito l'infrastruttura odierna.

| Data | Evento | Rilevanza |
| --- | --- | --- |
| 2023 | Lightning Labs pubblica L402 (prima versione) | HTTP 402 + Lightning + macaroons, protocollo production-grade ma confinato a Bitcoin |
| maggio 2025 | Coinbase annuncia x402 | Protocollo HTTP 402 + USDC su EVM, focus AI agents |
| 1 luglio 2025 | Cloudflare lancia "Pay Per Crawl" in private beta | Edge provider che monetizza crawler AI senza coinvolgere il publisher origin |
| 23 settembre 2025 | Coinbase e Cloudflare annunciano x402 Foundation | Governance futura neutrale per x402 |
| novembre 2025 | IETF draft-dhir-http-agent-profile-00 | Primo tentativo di standardizzare la dichiarazione di agent in HTTP |
| 11 febbraio 2026 | Lightning Labs rilascia Lightning Agent Tools | Sette skill composable, lnget client, Aperture server-side |
| 2 aprile 2026 | x402 Foundation passa formalmente a Linux Foundation | Founding coalition: Stripe, Cloudflare, AWS, Google, Microsoft, Visa, Mastercard, Adyen, Circle |
| 11 marzo 2026 | L402 diventa bLIP-0026 | Standardizzazione formale nella Lightning Improvement Proposal repo |

Al 23 aprile 2026 i protocolli vivi per HTTP 402 sono tre, con posizionamento diverso. **L402** (Bitcoin Lightning) è maturo ma Bitcoin-only, sub-cent, no KYC, macaroons con attenuation/delegation. **x402** (EVM+Solana stablecoin) è più giovane ma ha backing enterprise, multi-chain, facilitator-based. **Cloudflare Pay Per Crawl** è l'approccio pragmatico edge-managed con fatturazione fiat differita per crawler enterprise. Non si escludono a vicenda; stanno convergendo verso un deferred payment scheme che x402 sta formalizzando su proposta Cloudflare.

## Cloudflare Pay Per Crawl: la via gestita

Se il tuo sito è già dietro Cloudflare e non vuoi scrivere middleware, la strada meno resistente è [Pay Per Crawl](https://blog.cloudflare.com/introducing-pay-per-crawl/), in private beta al 23 aprile 2026. Cloudflare intercetta il crawler AI al livello edge, restituisce 402 con un listino configurabile dal dashboard, accetta il pagamento (stablecoin on-chain o fiat via merchant of record Cloudflare), inoltra la richiesta all'origin solo se il pagamento è valido. Partner publisher noti della beta includono Reddit, Stack Overflow, Condé Nast, O'Reilly, ognuno con strutture di pricing diverse.

Il vantaggio commerciale è che **non tocchi l'origin**: dal punto di vista della tua applicazione Laravel, la richiesta arriva già pagata o non arriva affatto. Il limite è speculare: **non tocchi l'origin**, quindi non puoi implementare logiche custom di pricing (sub-tenant diversi, pricing dinamico sul carico LLM dietro, discount su customer pre-autorizzati). Cloudflare ha annunciato al lancio del 2 aprile 2026 una proposta di *deferred payment scheme* per x402 su cui sta lavorando, che permetterebbe di far accumulare debit per singolo crawler e fatturare una sola volta al giorno via CC: questo risolve il pain dei crawler enterprise che non vogliono gestire wallet on-chain ma accettano fatturazione mensile. Per la PMI italiana media, la strada gestita è quella più sensata: pay-per-crawl toggle, configurazione pricing, stop. Il codice che segue serve quando vuoi o devi implementare on-origin per ragioni di controllo, pricing dinamico o assenza di Cloudflare nel tuo stack.

## x402 in dettaglio: come parla un server x402-compliant

Il flusso x402 estende il normale ciclo HTTP con un handshake di pagamento. Sei step.

1. **Client → Server**: `GET /api/premium` (richiesta standard)
2. **Server → Client**: `HTTP/1.1 402 Payment Required` con body JSON contenente istruzioni di pagamento
3. **Client**: legge il body, firma un `X-PAYMENT` payload con la sua chiave privata
4. **Client → Server**: retry della stessa richiesta con header `X-PAYMENT: <payload base64>`
5. **Server → Facilitator**: `POST /verify` con il payload per validazione; `POST /settle` per broadcasting on-chain
6. **Server → Client**: eseguita la risorsa, risposta 200 OK con header `X-PAYMENT-RESPONSE` contenente conferma settlement

Il body del 402 ha struttura canonica:

```json
{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "3000",
      "resource": "https://example.com/api/premium",
      "description": "Access to premium weather API",
      "mimeType": "application/json",
      "payTo": "0xYourEVMAddressHere",
      "maxTimeoutSeconds": 60,
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "extra": { "name": "USDC", "version": "2" }
    }
  ]
}
```

Lo `scheme` `exact` trasferisce un amount fisso; una variante `upto` proposta ma non ancora GA a aprile 2026 permetterebbe pagamenti variabili in base alle risorse consumate (pattern tipico degli LLM che fatturano per token processato). Il `maxAmountRequired` è in unità minori del token (per USDC con 6 decimali, 3000 = 0,003 USDC). Il campo `extra` contiene metadata EIP-3009 per il Transfer With Authorization che il client firmerà. Il `facilitator` di riferimento gratis è [api.cdp.coinbase.com/v2/x402](https://docs.cdp.coinbase.com/x402/welcome) con free tier di 1.000 transazioni al mese, supporto Base, Polygon, Arbitrum, World, Solana.

## L402 Lightning: la via Bitcoin

L402 usa una logica diversa. Il server risponde con:

```text
HTTP/1.1 402 Payment Required
WWW-Authenticate: L402 macaroon="AGIAJEemVQUTEyNCR0exk7ek90Cg==",
                        invoice="lnbc1u1p3hvp3tpp5..."
```

Il client paga l'invoice Lightning, ottiene il preimage (la pre-immagine hash della payment hash), e poi autentica le richieste successive con:

```text
Authorization: L402 <macaroon>:<preimage_hex>
```

Il `macaroon` è un token tipo bearer con capability di attenuation: il client può aggiungere caveats (vincoli) senza contattare il server, permettendo delega granulare ai sub-agent. Un agent padre può emettere un macaroon pay-only con cap di 500 sat e passarlo a un worker, senza che il worker possa escalare. [Aperture](https://github.com/lightninglabs/aperture) è il reverse proxy gRPC/REST L402-aware di Lightning Labs: lo piazzi davanti al tuo backend e gestisce la negoziazione e la verifica del preimage, la tua applicazione riceve solo request autenticate.

L402 è production-ready da cinque anni sul Lightning Loop. Conviene quando: il tuo traffico è micropayment Bitcoin-native, vuoi zero KYC, ti servono scoped macaroons con delega gerarchica, accetti complessità operativa Lightning (gestire canali, liquidità, fallback).

## Implementazione Laravel 12: middleware X402Gate

Non userò SDK esterni perché al 23 aprile 2026 l'ecosystem PHP per x402 è immaturo; scriverò il middleware direttamente contro il facilitator Coinbase. Install minimo:

```bash
composer require guzzlehttp/guzzle:^7.9 firebase/php-jwt:^6.10
```

Middleware:

```php
<?php

declare(strict_types=1);

namespace App\Http\Middleware;

use Closure;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

final class X402Gate
{
    // Facilitator endpoint Coinbase CDP (rete testnet Base durante lo sviluppo)
    private const FACILITATOR_URL = 'https://api.cdp.coinbase.com/v2/x402';

    // USDC contract su Base mainnet (6 decimali)
    private const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

    // Versione del protocollo x402 supportata
    private const PROTOCOL_VERSION = 1;

    // Cache TTL per idempotenza: una richiesta pagata vale 10 minuti
    private const IDEMPOTENCY_TTL_SECONDS = 600;

    public function __construct(private readonly Client $httpClient)
    {
    }

    public function handle(Request $request, Closure $next, string $priceCents = '300'): Response
    {
        $paymentHeader = $request->header('X-PAYMENT');

        // Nessun payment: rispondo 402 con istruzioni
        if ($paymentHeader === null || $paymentHeader === '') {
            return $this->buildPaymentRequiredResponse($request, $priceCents);
        }

        // Parse del payload firmato dal client
        $payload = $this->decodePayload($paymentHeader);
        if ($payload === null) {
            return response()->json(['error' => 'Invalid X-PAYMENT payload'], 400);
        }

        // Idempotenza: se questo exact payload e' stato gia' accettato, skip facilitator
        $idempotencyKey = 'x402:' . hash('sha256', $paymentHeader);
        if (Cache::has($idempotencyKey)) {
            return $next($request);
        }

        // Verifico il payload presso il facilitator (firma, network, destinatario)
        try {
            $verifyResponse = $this->httpClient->post(self::FACILITATOR_URL . '/verify', [
                'json' => [
                    'x402Version' => self::PROTOCOL_VERSION,
                    'paymentHeader' => $paymentHeader,
                    'paymentRequirements' => $this->paymentRequirements($request, $priceCents),
                ],
                'timeout' => 8.0,
            ]);
        } catch (GuzzleException $e) {
            Log::warning('x402 verify failed', ['exception' => $e->getMessage()]);

            return response()->json(['error' => 'Payment verification unavailable'], 503);
        }

        $verifyData = json_decode((string) $verifyResponse->getBody(), true);
        if (! ($verifyData['isValid'] ?? false)) {
            return response()->json([
                'error' => 'Payment invalid',
                'reason' => $verifyData['invalidReason'] ?? 'unknown',
            ], 402);
        }

        // Settle on-chain (broadcast della transazione)
        try {
            $settleResponse = $this->httpClient->post(self::FACILITATOR_URL . '/settle', [
                'json' => [
                    'x402Version' => self::PROTOCOL_VERSION,
                    'paymentHeader' => $paymentHeader,
                    'paymentRequirements' => $this->paymentRequirements($request, $priceCents),
                ],
                'timeout' => 15.0,
            ]);
        } catch (GuzzleException $e) {
            Log::error('x402 settle failed', ['exception' => $e->getMessage()]);

            return response()->json(['error' => 'Settlement failed'], 503);
        }

        $settleData = json_decode((string) $settleResponse->getBody(), true);
        if (! ($settleData['success'] ?? false)) {
            return response()->json(['error' => 'Settlement rejected'], 402);
        }

        // Cache idempotenza e proseguo con la richiesta originale
        Cache::put($idempotencyKey, true, self::IDEMPOTENCY_TTL_SECONDS);

        $response = $next($request);
        $response->headers->set('X-PAYMENT-RESPONSE', base64_encode(json_encode($settleData)));

        return $response;
    }

    private function buildPaymentRequiredResponse(Request $request, string $priceCents): Response
    {
        return response()->json([
            'x402Version' => self::PROTOCOL_VERSION,
            'accepts' => [$this->paymentRequirements($request, $priceCents)],
            'error' => 'Payment required',
        ], 402)
            ->header('Content-Type', 'application/json')
            ->header('WWW-Authenticate', 'x402 realm="api"');
    }

    private function paymentRequirements(Request $request, string $priceCents): array
    {
        // Converte cents USDC in unita' minime (6 decimali): 300 cents = 3000000 = $3.00
        // Per 0.003 USDC il priceCents e' "0.3" e la conversione e' 3000
        $amountAtomic = (string) (int) (((float) $priceCents) * 10000);

        return [
            'scheme' => 'exact',
            'network' => config('x402.network', 'base-sepolia'),
            'maxAmountRequired' => $amountAtomic,
            'resource' => $request->fullUrl(),
            'description' => 'Access to ' . $request->path(),
            'mimeType' => 'application/json',
            'payTo' => config('x402.pay_to_address'),
            'maxTimeoutSeconds' => 60,
            'asset' => self::USDC_ADDRESS,
            'extra' => ['name' => 'USDC', 'version' => '2'],
        ];
    }

    private function decodePayload(string $paymentHeader): ?array
    {
        $decoded = base64_decode($paymentHeader, true);
        if ($decoded === false) {
            return null;
        }

        $parsed = json_decode($decoded, true);

        return is_array($parsed) ? $parsed : null;
    }
}
```

Config file `config/x402.php`:

```php
<?php

return [
    // Network di default: base-sepolia per test, base per produzione
    'network' => env('X402_NETWORK', 'base-sepolia'),

    // Indirizzo EVM del wallet che riceve i pagamenti
    'pay_to_address' => env('X402_PAY_TO_ADDRESS'),

    // API key del facilitator Coinbase CDP (free tier 1000 tx/month)
    'facilitator_api_key' => env('X402_FACILITATOR_API_KEY'),
];
```

Applicazione alle route:

```php
Route::middleware(['x402.gate:300'])
    ->get('/api/premium/weather', [WeatherController::class, 'premium']);
```

Il parametro `300` significa 0,003 USDC per request (3000 in unità minime). Per pricing differenziato tra route, basta passare un valore diverso per ogni gruppo.

## Implementazione Symfony 7.2: event subscriber

Stesso pattern tradotto:

```php
<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use App\Service\X402FacilitatorClient;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Attribute\Route;

final class X402GateSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly X402FacilitatorClient $facilitator,
        private readonly CacheItemPoolInterface $cache,
        private readonly LoggerInterface $logger,
    ) {
    }

    public static function getSubscribedEvents(): array
    {
        return [KernelEvents::REQUEST => ['onKernelRequest', 8]];
    }

    public function onKernelRequest(RequestEvent $event): void
    {
        $request = $event->getRequest();

        // Attivo il gate solo su route marcate con attribute #[X402Paid(...)]
        $x402Config = $request->attributes->get('_x402');
        if ($x402Config === null) {
            return;
        }

        $paymentHeader = $request->headers->get('X-PAYMENT');
        if ($paymentHeader === null) {
            // Status nativo 402 Payment Required disponibile come costante
            $event->setResponse(new JsonResponse([
                'x402Version' => 1,
                'accepts' => [$x402Config->toArray($request)],
                'error' => 'Payment required',
            ], Response::HTTP_PAYMENT_REQUIRED));

            return;
        }

        $idempotencyKey = 'x402_' . hash('sha256', $paymentHeader);
        $item = $this->cache->getItem($idempotencyKey);
        if ($item->isHit()) {
            // Gia' pagata: lascio proseguire
            return;
        }

        // Verify + settle: se fallisce, intercetto con risposta 402
        $verification = $this->facilitator->verifyAndSettle($paymentHeader, $x402Config, $request);
        if (! $verification->ok) {
            $this->logger->warning('x402 verification failed', ['reason' => $verification->reason]);
            $event->setResponse(new JsonResponse(
                ['error' => 'Payment invalid', 'reason' => $verification->reason],
                Response::HTTP_PAYMENT_REQUIRED,
            ));

            return;
        }

        // Cache idempotenza e salvo la risposta settlement per il response listener
        $item->set($verification->settlementPayload);
        $item->expiresAfter(600);
        $this->cache->save($item);

        $request->attributes->set('_x402_settlement', $verification->settlementPayload);
    }
}
```

Nel controller usi un attribute custom:

```php
#[Route('/api/premium/weather', methods: ['GET'])]
#[X402Paid(amount: '3000', description: 'Premium weather access')]
public function premiumWeather(): JsonResponse
{
    return new JsonResponse(['temp' => 22.4, 'city' => 'Torino']);
}
```

L'attribute `X402Paid` popola `_x402` nelle request attributes durante il routing; il subscriber lo legge e applica il gate. Questo evita di modificare controller con logic di payment, mantenendo la responsabilità single-purpose.

## Implementazione PHP vanilla: front controller

Per applicazioni senza framework, lo schema è identico ma con interface minima.

```bash
composer require guzzlehttp/guzzle:^7.9
```

Helper `lib/X402Gate.php`:

```php
<?php

declare(strict_types=1);

namespace App\Lib;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

final class X402Gate
{
    private const FACILITATOR_URL = 'https://api.cdp.coinbase.com/v2/x402';
    private const USDC_BASE_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

    public function __construct(
        private readonly Client $http,
        private readonly string $network,
        private readonly string $payToAddress,
        private readonly string $facilitatorApiKey,
    ) {
    }

    public function gate(string $resourceUrl, string $amountAtomic, ?string $paymentHeader): GateResult
    {
        // Primo contatto: nessun X-PAYMENT
        if ($paymentHeader === null || $paymentHeader === '') {
            return GateResult::paymentRequired(
                $this->paymentRequirements($resourceUrl, $amountAtomic)
            );
        }

        // Verify: la firma e' corretta, l'amount e' sufficiente, il destinatario e' corretto?
        $verify = $this->callFacilitator('/verify', $paymentHeader, $resourceUrl, $amountAtomic);
        if ($verify === null || ! ($verify['isValid'] ?? false)) {
            return GateResult::rejected($verify['invalidReason'] ?? 'verification_failed');
        }

        // Settle: broadcast on-chain della transazione firmata
        $settle = $this->callFacilitator('/settle', $paymentHeader, $resourceUrl, $amountAtomic);
        if ($settle === null || ! ($settle['success'] ?? false)) {
            return GateResult::rejected('settlement_failed');
        }

        return GateResult::accepted(base64_encode(json_encode($settle)));
    }

    private function callFacilitator(string $path, string $paymentHeader, string $resourceUrl, string $amountAtomic): ?array
    {
        try {
            $response = $this->http->post(self::FACILITATOR_URL . $path, [
                'headers' => ['Authorization' => 'Bearer ' . $this->facilitatorApiKey],
                'json' => [
                    'x402Version' => 1,
                    'paymentHeader' => $paymentHeader,
                    'paymentRequirements' => $this->paymentRequirements($resourceUrl, $amountAtomic),
                ],
                'timeout' => 10.0,
            ]);

            return json_decode((string) $response->getBody(), true);
        } catch (GuzzleException) {
            return null;
        }
    }

    private function paymentRequirements(string $resourceUrl, string $amountAtomic): array
    {
        return [
            'scheme' => 'exact',
            'network' => $this->network,
            'maxAmountRequired' => $amountAtomic,
            'resource' => $resourceUrl,
            'description' => 'Paid API access',
            'mimeType' => 'application/json',
            'payTo' => $this->payToAddress,
            'maxTimeoutSeconds' => 60,
            'asset' => self::USDC_BASE_ADDRESS,
            'extra' => ['name' => 'USDC', 'version' => '2'],
        ];
    }
}

final class GateResult
{
    public function __construct(
        public readonly string $status,
        public readonly ?array $paymentRequirements = null,
        public readonly ?string $rejectionReason = null,
        public readonly ?string $settlementHeader = null,
    ) {
    }

    public static function paymentRequired(array $requirements): self
    {
        return new self('payment_required', paymentRequirements: $requirements);
    }

    public static function rejected(string $reason): self
    {
        return new self('rejected', rejectionReason: $reason);
    }

    public static function accepted(string $settlementHeader): self
    {
        return new self('accepted', settlementHeader: $settlementHeader);
    }
}
```

Front controller `public/api.php`:

```php
<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

use App\Lib\X402Gate;
use GuzzleHttp\Client;

$gate = new X402Gate(
    http: new Client(),
    network: $_ENV['X402_NETWORK'] ?? 'base-sepolia',
    payToAddress: $_ENV['X402_PAY_TO_ADDRESS'] ?? '',
    facilitatorApiKey: $_ENV['X402_FACILITATOR_API_KEY'] ?? '',
);

$resourceUrl = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$paymentHeader = $_SERVER['HTTP_X_PAYMENT'] ?? null;

$result = $gate->gate($resourceUrl, amountAtomic: '3000', paymentHeader: $paymentHeader);

match ($result->status) {
    'payment_required' => renderPaymentRequired($result->paymentRequirements),
    'rejected' => renderRejected($result->rejectionReason),
    'accepted' => renderResource($result->settlementHeader),
};

function renderPaymentRequired(array $requirements): void
{
    http_response_code(402);
    header('Content-Type: application/json');
    header('WWW-Authenticate: x402 realm="api"');
    echo json_encode([
        'x402Version' => 1,
        'accepts' => [$requirements],
        'error' => 'Payment required',
    ]);
}

function renderRejected(string $reason): void
{
    http_response_code(402);
    header('Content-Type: application/json');
    echo json_encode(['error' => 'Payment invalid', 'reason' => $reason]);
}

function renderResource(?string $settlementHeader): void
{
    http_response_code(200);
    header('Content-Type: application/json');
    if ($settlementHeader !== null) {
        header('X-PAYMENT-RESPONSE: ' . $settlementHeader);
    }

    // Contenuto protetto
    echo json_encode(['temp' => 22.4, 'city' => 'Torino', 'timestamp' => time()]);
}
```

Test con curl da un agent simulato (il payload reale va firmato con eth_signTypedData):

```bash
# Primo colpo: 402
curl -i https://api.example.test/api.php
# HTTP/1.1 402 Payment Required
# WWW-Authenticate: x402 realm="api"
# {"x402Version":1,"accepts":[{...}],"error":"Payment required"}

# Secondo colpo con payload firmato
curl -i -H 'X-PAYMENT: eyJzaWduYXR1cmUi...' https://api.example.test/api.php
# HTTP/1.1 200 OK
# X-PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLC...
# {"temp":22.4,...}
```

## Numeri veri Q1 2026 vs hype

Il 11 marzo 2026 CoinDesk ha pubblicato [un'analisi on-chain critica](https://www.coindesk.com/markets/2026/03/11/coinbase-backed-ai-payments-protocol-wants-to-fix-micropayment-but-demand-is-just-not-there-yet) che vale la pena citare testualmente. Volume giornaliero reale x402: 28.000 dollari. Transazioni giornaliere: 131.000. Pagamento medio: 0,20 dollari. Drop del 92% rispetto al picco di 731.000 transazioni giornaliere di dicembre 2025. Volume cumulativo: 119 milioni di transazioni su Base, 35 milioni su Solana, 600 milioni di USD annualized. Artemis, analista terzo, stima che circa metà delle transazioni osservate siano "gamified", cioè testing e incentive-farming, non commercio reale.

Questo non è pessimismo: è un dato utile. Significa che sei in una fase in cui **l'infrastruttura tecnica è pronta**, la governance è passata a un soggetto neutrale (Linux Foundation), i big backer sono allineati, ma la domanda reale di mercato è ancora prevalentemente sviluppatori che fanno demo. Se la tua strategia di business dipende da incasso immediato da HTTP 402, stai costruendo su sabbia. Se invece implementi HTTP 402 come **opzione architetturale** disponibile per quando la domanda si materializzerà, stai preparando il terreno senza bruciare runway.

Stack Overflow ha pubblicato [un retrospective il 19 febbraio 2026](https://stackoverflow.blog/2026/02/19/stack-overflow-cloudflare-pay-per-crawl/) sui primi sei mesi di pay-per-crawl. I numeri assoluti non sono rivelati, ma il framing è chiaro: per il momento è un canale di signaling ("il contenuto Stack Overflow ha valore, pagatelo"), non un revenue stream significativo. Lo stesso framing vale per Reddit e O'Reilly.

## Quando NON ha senso attivare HTTP 402

Cinque scenari dove il pattern non conviene.

Primo: domain authority bassa. Se il tuo sito non è ancora destinazione preferenziale dei crawler AI, non stai risolvendo un problema; stai aggiungendo frizione a traffico che forse arriverà, forse no.

Secondo: audience non tecnica. I tuoi utenti sono umani che leggono articoli business con browser normale. `X-PAYMENT` non lo mandano. L'unica cosa che ottieni è complessità operativa per il tuo team.

Terzo: blog piccolo (sotto 50.000 pageview/mese). Il break-even tra costo di integrazione, gestione facilitator API, hardening del wallet e incasso non è raggiungibile. Il tempo che ci spendi vale più del ricavo.

Quarto: contenuti che non alimentano training AI. Articoli evergreen tecnici di nicchia, documentazione interna, landing page commerciali. I crawler AI non hanno interesse a scansionarli a pagamento.

Quinto: non hai ancora content negotiation text/markdown. Se stai servendo HTML a Claude Code, l'agent paga 4x il costo di token per processare la stessa pagina. Prima di chiedere soldi, convertirlo in Markdown è un favore reciproco: lui paga meno per estrarre significato, tu incassi un prezzo equo perché ha senso. Ne ho scritto in dettaglio nel [pezzo di ieri sulla content negotiation](https://www.mauriziofonte.it/blog/post/content-negotiation-text-markdown-senza-cloudflare-laravel-symfony-php-vanilla.html).

## Dove siamo e dove andiamo

HTTP 402 nel 2026 è un protocollo tecnicamente maturo con backer eccellenti e volume reale modesto. Non è il momento di costruire un business case che dipende esclusivamente da esso, ma è il momento in cui **non implementarlo affatto** cessa di essere neutrale: ogni mese di ritardo è un mese in cui un competitor con la stessa integrazione si posiziona prima. La proposta pragmatica che faccio ai clienti PMI è tenere il middleware disabilitato ma deployato in staging, integration-tested, con un toggle `.env` per attivarlo. Quando la domanda reale si materializza (probabilmente quando uno dei grandi LLM provider integrerà wallet automatico di default, e quando McKinsey smetterà di proiettare e inizieremo a vedere i numeri), flippare il toggle è questione di minuti, non di mesi di sviluppo.

La narrativa attorno a x402 e L402 è che stanno "resuscitando HTTP 402 dopo ventinove anni". Il framing più onesto è che stanno **finalmente fornendo l'infrastruttura economica** che HTTP 402 aveva sempre promesso senza poter mantenere. Il fatto che Cloudflare, Coinbase, Stripe, Visa, Mastercard, AWS, Google e Microsoft siedano tutti nel fondo della x402 Foundation sotto Linux Foundation non è un dato casuale: è la scommessa simultanea dei big player su un'infrastruttura che sarà presente nel web dei prossimi dieci anni, indipendentemente da quale protocollo prevale o da quale volume transazionale si consolida. Essere early adopter senza essere fragile è la posizione difendibile.

Un'osservazione finale sui tre path implementativi che ho mostrato. Laravel 12 è la strada preferita quando il tuo stack è già Laravel e vuoi il pagamento come una capability di middleware trasversale, applicabile per gruppo di route con un singolo alias. Symfony 7.2 ha il vantaggio idiomatico del `Response::HTTP_PAYMENT_REQUIRED` costante nativa e dell'attribute system che mantiene il payment gate dichiarativo sul controller, senza sporcare la business logic. PHP vanilla è l'opzione che scelgo quando monetizzo un singolo endpoint in un progetto legacy senza framework, o quando voglio tenere il gate x402 completamente separato come proxy davanti a un backend che non sa niente di payment: lo deployo come micro-servizio, lo pongo davanti, il servizio interno resta invariato. Nella mia pipeline personale uso un mix: Laravel per route applicative, proxy PHP vanilla su un paio di endpoint di data export che servivo gratis e che ora voglio rendere opzionalmente pagati senza modificare il core. Tre stili, stessa semantica, stessa compatibilità x402.

Se stai pianificando integrazione HTTP 402 su un tuo progetto PMI, su un SaaS che espone API premium o su un portale di contenuti tecnici, e vuoi capire se ha davvero senso economico nel tuo caso specifico o se è prematuro, il [modulo di preventivo gratuito](https://www.mauriziofonte.it/pages/preventivo-gratuito.html#consulenza-ai) risponde in due minuti se il tuo scenario rientra nel mio perimetro. Sette domande, nessun impegno. Prima di scrivere middleware x402, è meglio sapere se il middleware va scritto.

Dall'articolo al lavoro vero

Se il codice di cui parla questo articolo è il tuo

Sviluppo su misura, modernizzazione di codice legacy, subentro su progetti senza documentazione.

[Vedi tre interventi con i numeri](https://www.mauriziofonte.it/programmatore-php.html) Ultima modifica: Venerdì 19 Giugno 2026, alle 10:51 [« Articolo precedenteServire text/markdown agli agenti AI senza Cloudflare: content negotiation RFC 9110 on-origin con Laravel, Symfony e PHP vanilla30 Aprile 2026](https://www.mauriziofonte.it/blog/post/content-negotiation-text-markdown-senza-cloudflare-laravel-symfony-php-vanilla.html)[Articolo successivo »Anthropic Tool Search Tool e Programmatic Tool Calling: agenti con 50+ tool MCP senza saturare il context4 Mag 2026](https://www.mauriziofonte.it/blog/post/anthropic-tool-search-programmatic-tool-calling-mcp-ridurre-token.html)

### Articoli correlati a Cloudflare, Coinbase e Lightning hanno appena resuscitato HTTP 402 dopo 29 anni di oblio: ecco come far pagare gli agenti AI che scrapano il tuo sito con Laravel, Symfony e PHP vanilla

- [Cloudflare AI Crawl Control e Content Signals: governance del training crawler per il tuo blog aziendale](https://www.mauriziofonte.it/blog/post/cloudflare-ai-crawl-control-content-signals-bloccare-ai-training.html)

  Il crawl to referral ratio di Anthropic è 73.000:1, quello di OpenAI 1.700:1. Il deal storico 'contenuto in cambio di traffico' è rotto. Cloudflare · 22 Mag 2026

  ![Cloudflare AI Crawl Control e Content Signals: governance del training crawler per il tuo blog aziendale](https://www.mauriziofonte.it/image/004210070-cloudflare-ai-crawl-control-content-signals-bloccare-ai-training.jpg)
- [Cloudflare e il managed robots.txt: governare i crawler AI da publisher italiano](https://www.mauriziofonte.it/blog/post/cloudflare-managed-robots-txt-crawler-ai-publisher-italia.html)

  I crawler AI consumano il contenuto di un sito senza generare visite: per un publisher la questione è governarli, non solo bloccarli. Gli strumenti · 6 Agosto 2026

  ![Cloudflare e il managed robots.txt: governare i crawler AI da publisher italiano](https://www.mauriziofonte.it/image/005020070-cloudflare-managed-robots-txt-crawler-ai-publisher-italia.jpg)
- [Cloudflare blocca i crawler AI di default su tutti i piani, anche il tuo](https://www.mauriziofonte.it/blog/post/cloudflare-blocco-crawler-ai-default-free-tier-settembre-2026.html)

  Il 15 settembre 2026 Cloudflare ha cambiato i default dei suoi controlli sui crawler AI, e stavolta il cambio ha toccato anche i milioni di siti · 23 Settembre 2026

  ![Cloudflare blocca i crawler AI di default su tutti i piani, anche il tuo](https://www.mauriziofonte.it/image/005610070-cloudflare-blocco-crawler-ai-default-free-tier-settembre-2026.jpg)
- [AP2 vs ACP vs UCP: quale protocollo di agentic commerce scegliere per un e-commerce italiano B2B o B2C](https://www.mauriziofonte.it/blog/post/ap2-vs-acp-vs-ucp-agentic-commerce-ecommerce-italia-b2b-b2c-psd2.html)

  NRF 2026 ha lanciato UCP Google Shopify Walmart , Stripe OpenAI hanno risposto con ACP Apache 2.0, già live su ChatGPT Instant Checkout , Google · 17 Giugno 2026

  ![AP2 vs ACP vs UCP: quale protocollo di agentic commerce scegliere per un e-commerce italiano B2B o B2C](https://www.mauriziofonte.it/image/004610070-ap2-vs-acp-vs-ucp-agentic-commerce-ecommerce-italia-b2b-b2c-psd2.jpg)
- [Structured output validation di LLM in PHP: schemi JSON, fail-safe e difesa da hallucination in produzione](https://www.mauriziofonte.it/blog/post/structured-output-validation-llm-php-schemi-fail-safe-produzione.html)

  Un LLM in produzione che restituisce testo libero è una bomba a orologeria: prima o poi hallucinerà un valore fuori range e romperà la pipeline · 21 Gennaio 2026

  ![Structured output validation di LLM in PHP: schemi JSON, fail-safe e difesa da hallucination in produzione](https://www.mauriziofonte.it/image/003770070-structured-output-validation-llm-php-schemi-fail-safe-produzione.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Cloudflare, Coinbase e Lightning hanno appena resuscitato HTTP 402 dopo 29 anni di oblio: ecco come far pagare gli agenti AI che scrapano il tuo sito con Laravel, Symfony e PHP vanilla", mauriziofonte.it, 2026. https://www.mauriziofonte.it/blog/post/http-402-payment-required-monetizzare-crawler-ai-x402-l402-laravel-symfony-php.html

Pubblicato: 2026-05-01 · Aggiornato: 2026-06-19.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
