> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Crea una Piattaforma API AI con Fatturazione Basata su Crediti

> Costruisci un servizio API AI a livelli con crediti token — emetti crediti tramite piani di abbonamento e pacchetti di ricarica una tantum, e detrai automaticamente quando i clienti chiamano la tua API.

<Tip>
  <strong>Lascia che Sentra scriva il tuo codice di integrazione per te.</strong><br />
  Usa il nostro assistente AI in VS Code, Cursor o Windsurf per generare codice SDK/API, gestori di webhook, concessioni di crediti e altro — semplicemente descrivendo ciò che desideri.

  <a href="https://dodopayments.com/sentra" target="_blank" rel="noopener noreferrer">
    Prova Sentra: Integrazione potenziata da AI →
  </a>
</Tip>

In questo tutorial, creerai **NeuralAPI** — una piattaforma AI a livelli in cui ogni piano di abbonamento include un credito mensile di token, i clienti possono acquistare pacchetti di ricarica quando sono a corto di crediti, e il tuo backend detrae automaticamente i crediti mentre le richieste vengono elaborate da OpenAI.

<Note>
  Questo tutorial utilizza Node.js/Express + l'SDK OpenAI. I concetti di Dodo Payments (crediti, misuratori, webhook) si applicano a qualsiasi framework o fornitore AI — adatta liberamente.
</Note>

Alla fine di questo tutorial, saprai come:

* Creare un diritto di credito personalizzato (token) e un misuratore che detrae automaticamente da esso
* Collegare i crediti ai piani di abbonamento (con e senza sforamento) e a un prodotto di ricarica una tantum
* Collegare un endpoint di completamento OpenAI reale che fattura i token tramite Dodo Payments
* Interrogare il saldo del credito live di un cliente tramite l'SDK
* Verificare le firme del webhook e indirizzare gli eventi di credito di Dodo Payments

## Cosa Stiamo Costruendo

Ecco il modello di pricing per NeuralAPI:

| Prodotto                    | Prezzo          | Token                  | Sforamento           |
| --------------------------- | --------------- | ---------------------- | -------------------- |
| Piano Iniziale              | \$29/mese       | 10,000,000 token/ciclo | Bloccato a zero      |
| Piano Pro                   | \$99/mese       | 40,000,000 token/ciclo | \$0.005 per 1K token |
| Pacchetto di Ricarica Token | \$19 una tantum | +5,000,000 token       | —                    |

<Info>
  Prima di iniziare, assicurati di avere:

  * Un account Dodo Payments (va bene la modalità di test)
  * Una chiave API OpenAI
  * Node.js 18+
  * Conoscenza di base di TypeScript/Node.js
</Info>

## Passaggio 1: Crea il Tuo Diritto di Credito Token

Per prima cosa, crea il diritto di credito che condivideranno entrambi i piani di abbonamento e il pacchetto di ricarica. Pensa a questo come alla definizione dell'unità "token" che utilizza la tua piattaforma.

<Frame caption="The Credits tab under Products shows all your credit entitlements.">
  <img src="https://mintcdn.com/dodopayments/eU6ZCQ885P3550bK/images/CBB/Desktop%20-%20Cookbook%20-%20NeuralAPI%20-%20Credit.png?fit=max&auto=format&n=eU6ZCQ885P3550bK&q=85&s=6c34ed3755c78534dcd6012680e98e40" alt="Pagina di elenco dei crediti che mostra i diritti di credito creati" style={{ maxHeight: '500px', width: 'auto' }} width="2931" height="1665" data-path="images/CBB/Desktop - Cookbook - NeuralAPI - Credit.png" />
</Frame>

<Steps>
  <Step title="Navigate to Credits">
    1. Accedi alla tua dashboard di Dodo Payments
    2. Clicca su **Prodotti** nella barra laterale sinistra
    3. Seleziona la scheda **Crediti**
    4. Clicca su **Crea Credito**
  </Step>

  <Step title="Configure the credit unit">
    Compila i dettagli di base per il tuo credito token:

    **Nome Credito**: `API Tokens`

    **Tipo di Credito**: Seleziona **Unità Personalizzata**

    **Nome Unità**: `token`

    **Precisione**: `0` (i token sono sempre numeri interi)

    **Scadenza Credito**: `30 days` (i crediti si azzerano a ogni ciclo di fatturazione)

    <Warning>
      La precisione non può essere modificata dopo la creazione di un credito. Per i conteggi dei token, `0` (numeri interi) è quasi sempre corretto.
    </Warning>
  </Step>

  <Step title="Skip overage at the credit level">
    Lascia **disabilitato** lo sforamento qui — lo configurerai per piano quando aggancerai il credito ai prodotti. Ciò consente al piano Iniziale di bloccare l'uso a zero mentre il piano Pro consente lo sforamento.

    <Tip>
      Le impostazioni di sforamento configurate qui sono *predefinite*. Ogni collegamento del prodotto può sovrascriverle — ed è esattamente ciò che faremo nel Passaggio 3.
    </Tip>
  </Step>

  <Step title="Save and copy the credit ID">
    Clicca su **Crea Credito**. Una volta salvato, apri il credito e copia il suo ID — sembra `cent_xxxxxxxxxxxx`.

    <Check>
      Il tuo diritto di credito `API Tokens` è pronto. Successivamente, crea un misuratore affinché gli eventi di utilizzo possano guidare le detrazioni automaticamente.
    </Check>
  </Step>
</Steps>

## Passaggio 2: Crea un Misuratore per l'Uso dei Token

Un misuratore aggrega gli eventi di utilizzo in arrivo e li converte in detrazioni di credito. Ti serve questo *prima* di creare i prodotti del piano, poiché lo aggancerai durante la creazione del prodotto nel Passaggio 3.

<Steps>
  <Step title="Open the Meters section">
    1. Nella barra laterale della dashboard, vai su **Prodotti → Misuratori**
    2. Clicca su **Crea Misuratore**
  </Step>

  <Step title="Configure the meter">
    Compila:

    **Nome Misuratore**: `Token Usage Meter`

    **Nome Evento**: `api.tokens_used` *(questo deve corrispondere esattamente a ciò che invia la tua app)*

    **Tipo di Aggregazione**: `Sum` — sommiamo il conteggio dei token da ogni evento

    **Sopra la Proprietà**: `tokens` — la chiave dei metadati su ciascun evento il cui valore verrà sommato

    **Unità di Misura**: `tokens`

    <Warning>
      I nomi degli eventi sono case-sensitive. `api.tokens_used` ≠ `Api.Tokens.Used` — scegline uno e attieniti a esso.
    </Warning>

    Salva il misuratore e copia il suo ID — lo citerai quando lo agganci ai prodotti.

    <Check>
      Il misuratore è creato. Ora possiamo collegarlo al credito quando configuriamo i prodotti.
    </Check>
  </Step>
</Steps>

## Passaggio 3: Crea i Prodotti del Piano

Entrambi i piani devono essere prodotti **Usage Based Billing**, non semplici Abbonamenti — i misuratori possono essere agganciati solo ai prodotti UBB, e hai bisogno del misuratore per detrarre automaticamente i crediti quando i clienti chiamano la tua API. I prodotti UBB supportano ancora una tariffa base ricorrente (la `$29` / `$99`); l'uso al di sopra di tale soglia viene fatturato in crediti.

<Frame caption="Usage Based Billing pricing type with meter configuration.">
  <img src="https://mintcdn.com/dodopayments/ibNfoFRyCIGyt3pO/images/CBB/Desktop%20-%20Attach%20Credit%20-%20UBB.jpg?fit=max&auto=format&n=ibNfoFRyCIGyt3pO&q=85&s=41b2862c12d126e7843098307e27e137" alt="Configurazione della tariffazione Basata sull'Uso" style={{ maxHeight: '500px', width: 'auto' }} width="2880" height="1920" data-path="images/CBB/Desktop - Attach Credit - UBB.jpg" />
</Frame>

### Piano Iniziale (\$29/mese — 10M token, nessuno sforamento)

<Steps>
  <Step title="Create the Starter UBB product">
    1. Vai su **Prodotti → Crea Prodotto**
    2. Seleziona **Usage Based Billing** come tipo di tariffazione
    3. Compila:

    **Nome Prodotto**: `NeuralAPI Starter`

    **Descrizione**: `10 million API tokens per month. Perfect for individual developers and small projects.`

    **Prezzo Fisso**: `29.00` (la tariffa base ricorrente — fatturata mensilmente anche prima di qualsiasi utilizzo)

    **Ciclo di Fatturazione**: `Monthly`

    **Valuta**: `USD`
  </Step>

  <Step title="Attach the meter">
    Nella sezione **Seleziona Misuratore**, clicca su **+** e aggiungi `Token Usage Meter`. Poi sul misuratore:

    1. Attiva **Fattura utilizzo in Crediti**
    2. **Diritto di Credito**: seleziona `API Tokens`
    3. **Unità di Misura per Credito**: `1` — ogni token nell'evento mappa a 1 credito detratto
    4. **Soglia Gratuita**: `0` — la stessa allocazione del credito è il "livello gratuito" del cliente; non abbiamo bisogno di una banda gratuita extra

    <Frame caption="Toggle 'Bill usage in Credits' on the meter and pick the credit entitlement.">
      <img src="https://mintcdn.com/dodopayments/ibNfoFRyCIGyt3pO/images/CBB/Desktop%20-%20Attach%20Credit%20-%20UBB-5.jpg?fit=max&auto=format&n=ibNfoFRyCIGyt3pO&q=85&s=b4ef2fe5079cbf3bb39eb3814f101cbd" alt="Misuratore con usare il fatturato nei crediti abilitati e token API selezionati" style={{ maxHeight: '500px', width: 'auto' }} width="2880" height="2282" data-path="images/CBB/Desktop - Attach Credit - UBB-5.jpg" />
    </Frame>

    Questo è il collegamento che fa sì che gli eventi `api.tokens_used` in arrivo detraggano effettivamente dal saldo del cliente.
  </Step>

  <Step title="Configure credit issuance for Starter">
    Ancora sul prodotto, scorri alla sezione di configurazione del credito che appare una volta agganciato un misuratore fatturato in crediti:

    **Crediti emessi per ciclo di fatturazione**: `10000000`

    **Consentire Sforamento**: **Disabilitato** — i clienti Iniziali sono bloccati quando i token si esauriscono

    **Importa Impostazioni di Credito Predefinite**: Abilitato — usa la scadenza di 30 giorni dal diritto di credito

    <Frame caption="Configure credit issuance per cycle on the UBB product.">
      <img src="https://mintcdn.com/dodopayments/ibNfoFRyCIGyt3pO/images/CBB/Desktop%20-%20Attach%20Credit%20-%20UBB-6.jpg?fit=max&auto=format&n=ibNfoFRyCIGyt3pO&q=85&s=22e99c54f11305a24d63c77e09a4650c" alt="Modulo di configurazione dei crediti con importo per ciclo e impostazioni di sforamento" style={{ maxHeight: '500px', width: 'auto' }} width="2880" height="1920" data-path="images/CBB/Desktop - Attach Credit - UBB-6.jpg" />
    </Frame>

    Clicca **Salva** e copia l'ID del prodotto.

    <Check>
      Piano Iniziale: tariffa base di \$29/mese, 10M token/ciclo, bloccato a zero, detrazioni automatiche tramite misuratore.
    </Check>
  </Step>
</Steps>

### Piano Pro (\$99/mese — 40M token, sforamento abilitato)

<Steps>
  <Step title="Create the Pro UBB product">
    Stesso flusso del Piano Iniziale, con numeri maggiori:

    **Nome Prodotto**: `NeuralAPI Pro`

    **Descrizione**: `40 million API tokens per month with overage. Built for production applications.`

    **Prezzo Fisso**: `99.00`

    **Ciclo di Fatturazione**: `Monthly`

    **Valuta**: `USD`
  </Step>

  <Step title="Attach the meter">
    Identico al Piano Iniziale: aggiungi `Token Usage Meter`, attiva **Fattura utilizzo in Crediti**, seleziona `API Tokens`, **Unità di Misura per Credito** `1`, **Soglia Gratuita** `0`.
  </Step>

  <Step title="Configure credit issuance with overage">
    Configura l'emissione del credito, questa volta abilitando lo sforamento:

    **Crediti emessi per ciclo di fatturazione**: `40000000`

    **Importa Impostazioni di Credito Predefinite**: **Disabilitato** — dobbiamo personalizzare le impostazioni di sforamento per prodotto

    **Consenti Sforamento**: **Abilitato**

    **Prezzo per Unità**: `0.000005` USD per token (cioè, $0.005 per 1K token, o $5 per 1M token — sopra la tariffa effettiva per token del piano per scoraggiare gli spurghi)

    **Comportamento dello Sforamento**: `Bill overage at billing` — lo sforamento viene addebitato sulla fattura successiva, quindi il saldo si azzera

    Salva il prodotto e copia l'ID del prodotto.

    <Check>
      Piano Pro: tariffa base di $99/mese, 40M token/ciclo, sforamento a $0.005/1K token, detrazioni automatiche tramite misuratore.
    </Check>
  </Step>
</Steps>

## Passaggio 4: Crea il Pacchetto di Ricarica Token

Il pacchetto di ricarica è un acquisto una tantum che aggiunge 5,000,000 token al saldo di un cliente esistente.

<Frame caption="Single Payment pricing selected for a one-time credit product.">
  <img src="https://mintcdn.com/dodopayments/ibNfoFRyCIGyt3pO/images/CBB/Desktop%20-%20Attach%20Credit%20-%20OTP.jpg?fit=max&auto=format&n=ibNfoFRyCIGyt3pO&q=85&s=1743cb3e515952f9d4b1b2782cebac8b" alt="Sezione dei prezzi del prodotto con Pagamento Singolo selezionata" style={{ maxHeight: '500px', width: 'auto' }} width="2880" height="1920" data-path="images/CBB/Desktop - Attach Credit - OTP.jpg" />
</Frame>

<Steps>
  <Step title="Create a one-time product">
    1. Vai su **Prodotti → Crea Prodotto**
    2. Seleziona **Pagamento Singolo** come tipo di tariffazione
    3. Compila:

    **Nome Prodotto**: `Token Top-Up Pack`

    **Descrizione**: `Instantly add 5 million tokens to your NeuralAPI balance.`

    **Prezzo**: `19.00`

    **Valuta**: `USD`
  </Step>

  <Step title="Attach the token credit">
    1. Nella sezione **Diritti**, clicca su **Collega** accanto a **Crediti**
    2. Seleziona `API Tokens`
    3. Imposta **Crediti emessi**: `5000000`
    4. **Disabilita** *Importa Impostazioni di Credito Predefinite* — vogliamo sovrascrivere la scadenza predefinita di 30 giorni
    5. Imposta **Scadenza Credito**: `365 days`
    6. Salva il prodotto

    Copia l'ID del prodotto.

    <Tip>
      Perché una scadenza più lunga sulle ricariche? I crediti di abbonamento si azzerano ogni 30 giorni perché è il ciclo. Le ricariche sono *acquisti prepagati* — il cliente ha pagato \$19 in anticipo e si aspetta ragionevolmente che quei token durino più di un mese. 365 giorni corrispondono a come funzionano davvero i crediti prepagati su OpenAI, AWS e Anthropic, pur limitando la tua responsabilità in modo che i clienti non possano accumulare indefinitamente.
    </Tip>

    <Check>
      Pacchetto di Ricarica configurato — l'acquisto concede 5,000,000 token che rimangono validi per 365 giorni.
    </Check>
  </Step>
</Steps>

## Passaggio 5: Costruisci il Backend

Ora costruiamo il server Express che gestisce il checkout dell'abbonamento, il checkout della ricarica, i completamenti OpenAI reali con fatturazione dei token, le query del saldo e gli eventi di webhook del credito.

<Steps>
  <Step title="Set up your project">
    ```bash theme={null}
    mkdir neural-api-billing
    cd neural-api-billing
    npm init -y
    npm install dodopayments openai express dotenv
    npm install -D @types/node @types/express typescript tsx
    ```

    Crea un `tsconfig.json`:

    ```json tsconfig.json theme={null}
    {
      "compilerOptions": {
        "target": "ES2022",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true
      }
    }
    ```

    Aggiorna `package.json` scripts:

    ```json package.json theme={null}
    {
      "scripts": {
        "dev": "tsx watch src/server.ts",
        "build": "tsc",
        "start": "node dist/server.js"
      }
    }
    ```
  </Step>

  <Step title="Set up environment variables">
    Crea `.env` con le tue credenziali e gli ID dai passaggi precedenti:

    ```bash .env theme={null}
    DODO_PAYMENTS_API_KEY=your_dodo_api_key_here
    DODO_PAYMENTS_WEBHOOK_KEY=your_webhook_signing_secret_here
    DODO_ENVIRONMENT=test_mode
    OPENAI_API_KEY=sk-...
    CREDIT_ENTITLEMENT_ID=cent_xxxxxxxxxxxx
    STARTER_PLAN_PRODUCT_ID=pdt_xxxxxxxxxxxx
    PRO_PLAN_PRODUCT_ID=pdt_xxxxxxxxxxxx
    TOPUP_PRODUCT_ID=pdt_xxxxxxxxxxxx
    BASE_URL=http://localhost:3000
    ```

    <Warning>
      Non commettere mai `.env` nel controllo della versione. Aggiungilo immediatamente a `.gitignore`.
    </Warning>

    Compilerai `DODO_PAYMENTS_WEBHOOK_KEY` nel Passaggio 7 dopo aver registrato il tuo endpoint di webhook.
  </Step>

  <Step title="Implement the server">
    Crea `src/server.ts`:

    <CodeGroup>
      ```typescript src/server.ts expandable theme={null}
      import 'dotenv/config';
      import DodoPayments from 'dodopayments';
      import OpenAI from 'openai';
      import express, { Request, Response } from 'express';

      const app = express();

      // IMPORTANT: webhook route needs the raw body for signature verification.
      // We register the raw parser ONLY on /webhooks/dodo, then JSON for everything else.
      app.use('/webhooks/dodo', express.raw({ type: 'application/json' }));
      app.use(express.json());
      app.use(express.static('public'));

      const dodo = new DodoPayments({
        bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
        webhookKey: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
        environment: (process.env.DODO_ENVIRONMENT as 'test_mode' | 'live_mode') ?? 'test_mode',
      });

      const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

      const CREDIT_ENTITLEMENT_ID = process.env.CREDIT_ENTITLEMENT_ID!;
      const BASE_URL = process.env.BASE_URL!;
      const PLAN_PRODUCTS: Record<string, string> = {
        starter: process.env.STARTER_PLAN_PRODUCT_ID!,
        pro: process.env.PRO_PLAN_PRODUCT_ID!,
      };

      // ────────────────────────────────────────────────────────────────────────────
      // Subscription checkout
      // Body: { plan: 'starter' | 'pro', email: string, name: string }
      // ────────────────────────────────────────────────────────────────────────────
      app.post('/checkout/subscribe', async (req: Request, res: Response) => {
        const { plan, email, name } = req.body;
        if (!PLAN_PRODUCTS[plan]) {
          return res.status(400).json({ error: `Unknown plan: ${plan}` });
        }
        try {
          const session = await dodo.checkoutSessions.create({
            product_cart: [{ product_id: PLAN_PRODUCTS[plan], quantity: 1 }],
            customer: { email, name },
            return_url: `${BASE_URL}/?subscribed=1`,
          });
          res.json({ checkout_url: session.checkout_url, session_id: session.session_id });
        } catch (err) {
          console.error('Subscription checkout error:', err);
          res.status(500).json({ error: 'Failed to create subscription checkout' });
        }
      });

      // ────────────────────────────────────────────────────────────────────────────
      // Top-up checkout — buyer must already be a customer
      // Body: { customer_id: string }
      // ────────────────────────────────────────────────────────────────────────────
      app.post('/checkout/topup', async (req: Request, res: Response) => {
        const { customer_id } = req.body;
        if (!customer_id) return res.status(400).json({ error: 'customer_id required' });
        try {
          const session = await dodo.checkoutSessions.create({
            product_cart: [{ product_id: process.env.TOPUP_PRODUCT_ID!, quantity: 1 }],
            customer: { customer_id },
            return_url: `${BASE_URL}/?topup=1`,
          });
          res.json({ checkout_url: session.checkout_url });
        } catch (err) {
          console.error('Top-up checkout error:', err);
          res.status(500).json({ error: 'Failed to create top-up checkout' });
        }
      });

      // ────────────────────────────────────────────────────────────────────────────
      // Live token balance for a customer
      // ────────────────────────────────────────────────────────────────────────────
      app.get('/credits/:customerId', async (req: Request, res: Response) => {
        try {
          const result = await dodo.creditEntitlements.balances.retrieve(req.params.customerId, {
            credit_entitlement_id: CREDIT_ENTITLEMENT_ID,
          });
          res.json({
            balance: result.balance,
            overage: result.overage,
            last_transaction_at: result.last_transaction_at,
          });
        } catch (err) {
          console.error('Balance fetch error:', err);
          res.status(500).json({ error: 'Failed to fetch credit balance' });
        }
      });

      // ────────────────────────────────────────────────────────────────────────────
      // AI completion — calls OpenAI, then ingests a usage event with the real
      // token count. The meter aggregates these and deducts credits automatically.
      // Body: { customer_id: string, prompt: string }
      // ────────────────────────────────────────────────────────────────────────────
      app.post('/api/generate', async (req: Request, res: Response) => {
        const { customer_id, prompt } = req.body;
        if (!customer_id || !prompt) {
          return res.status(400).json({ error: 'customer_id and prompt required' });
        }

        // Best-effort balance gate for Starter (no overage). Note: balance updates
        // are eventually consistent (~1 min lag from event ingestion), so a Starter
        // customer can technically squeeze through a few extra requests right after
        // running out. Use a stricter rate-limiter on top if you need hard cutoffs.
        try {
          const balance = await dodo.creditEntitlements.balances.retrieve(customer_id, {
            credit_entitlement_id: CREDIT_ENTITLEMENT_ID,
          });
          if (Number(balance.balance) <= 0 && Number(balance.overage) <= 0) {
            return res.status(402).json({
              error: 'Out of tokens. Top up or upgrade to continue.',
            });
          }
        } catch {
          // Fall through — if the balance lookup fails, don't block; rely on metering.
        }

        let completion;
        try {
          completion = await openai.chat.completions.create({
            model: 'gpt-5-mini',
            messages: [{ role: 'user', content: prompt }],
          });
        } catch (err) {
          console.error('OpenAI error:', err);
          return res.status(502).json({ error: 'Upstream AI provider failed' });
        }

        const tokensUsed = completion.usage?.total_tokens ?? 0;

        // Fire-and-forget — don't block the response on metering.
        ingestTokenUsage(customer_id, tokensUsed, completion.model).catch((err) =>
          console.error('Usage ingest failed:', err),
        );

        res.json({
          text: completion.choices[0]?.message?.content ?? '',
          tokens_used: tokensUsed,
          model: completion.model,
        });
      });

      async function ingestTokenUsage(customerId: string, tokens: number, model: string) {
        await dodo.usageEvents.ingest({
          events: [
            {
              // event_id is the idempotency key. Use a stable, unique value per request.
              event_id: `req_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
              customer_id: customerId,
              event_name: 'api.tokens_used',
              timestamp: new Date().toISOString(),
              metadata: { tokens, model },
            },
          ],
        });
      }

      // ────────────────────────────────────────────────────────────────────────────
      // Webhook handler — verifies signature using the SDK, then routes events.
      // ────────────────────────────────────────────────────────────────────────────
      app.post('/webhooks/dodo', async (req: Request, res: Response) => {
        const rawBody = (req.body as Buffer).toString('utf8');
        const headers = {
          'webhook-id': req.header('webhook-id') ?? '',
          'webhook-signature': req.header('webhook-signature') ?? '',
          'webhook-timestamp': req.header('webhook-timestamp') ?? '',
        };

        let event: { type: string; data: any };
        try {
          event = dodo.webhooks.unwrap(rawBody, { headers }) as any;
        } catch (err) {
          console.error('Webhook verification failed:', err);
          return res.status(401).json({ error: 'Invalid signature' });
        }

        switch (event.type) {
          case 'credit.added':
            console.log(`[credit.added] customer=${event.data.customer_id} amount=${event.data.amount}`);
            break;
          case 'credit.deducted':
            console.log(`[credit.deducted] customer=${event.data.customer_id} amount=${event.data.amount}`);
            break;
          case 'credit.overage_charged':
            console.log(`[credit.overage_charged] customer=${event.data.customer_id}`);
            break;
          default:
            // Ignore other event types
            break;
        }

        res.json({ received: true });
      });

      app.listen(3000, () => {
        console.log('NeuralAPI billing server running on http://localhost:3000');
      });
      ```

      ```json package.json theme={null}
      {
        "name": "neural-api-billing",
        "version": "1.0.0",
        "scripts": {
          "dev": "tsx watch src/server.ts",
          "build": "tsc",
          "start": "node dist/server.js"
        },
        "dependencies": {
          "dodopayments": "latest",
          "openai": "^4.0.0",
          "express": "^4.18.0",
          "dotenv": "^16.0.0"
        },
        "devDependencies": {
          "@types/node": "^20.0.0",
          "@types/express": "^4.17.0",
          "typescript": "^5.0.0",
          "tsx": "^4.0.0"
        }
      }
      ```
    </CodeGroup>

    <Check>
      Backend completato: checkout dell'abbonamento, checkout della ricarica, completamento OpenAI con fatturazione dei token misurati, query del saldo e un gestore del webhook verificato.
    </Check>

    <Tip>
      [`@dodopayments/ingestion-blueprints`](/features/usage-based-billing/ingestion-blueprints) fornisce tracker drop-in che automatizzano la chiamata `usageEvents.ingest` per te — incluso il [LLM Blueprint](/developer-resources/ingestion-blueprints/llm), [API gateway](/developer-resources/ingestion-blueprints/api-gateway), [object storage](/developer-resources/ingestion-blueprints/object-storage), [streams](/developer-resources/ingestion-blueprints/stream), e [time-range](/developer-resources/ingestion-blueprints/time-range) usage.
    </Tip>
  </Step>

  <Step title="A note on how deductions actually happen">
    Potresti aver notato che non c'è alcuna chiamata esplicita "detrarre N crediti". Questo è voluto:

    1. Il tuo gestore chiama OpenAI e riceve `usage.total_tokens` (es. 1532).
    2. Inserisci un singolo evento di utilizzo: `event_name: api.tokens_used`, `metadata: { tokens: 1532 }`.
    3. `Token Usage Meter` aggrega gli eventi per cliente.
    4. Poiché il misuratore è collegato al credito `API Tokens` con **Fattura utilizzo in Crediti**, Dodo Payments detrae 1532 crediti dal grant non scaduto più vecchio del cliente (FIFO).
    5. Se lo sforamento è abilitato e il cliente va sotto zero, il deficit viene tracciato e fatturato sulla fattura successiva.

    Il misuratore gestisce tutto ciò. Il tuo codice inserisce solo eventi.
  </Step>
</Steps>

## Passaggio 6: Aggiungi un Frontend Demo

Crea `public/index.html` per testare tutti i flussi nel tuo browser. Persistiamo l'ID cliente su `localStorage` in modo che abbonamento → generazione → ricarica condividano tutti la stessa identità, imitando un'app con accesso:

<CodeGroup>
  ```html public/index.html expandable theme={null}
  <!DOCTYPE html>
  <html>
  <head>
    <title>NeuralAPI Demo</title>
    <style>
      body { font-family: system-ui, sans-serif; max-width: 760px; margin: 40px auto; padding: 20px; color: #1a1a2e; }
      h1 { font-size: 24px; }
      h2 { margin-top: 36px; border-bottom: 1px solid #eee; padding-bottom: 8px; font-size: 18px; }
      .panel { padding: 16px; background: #fafafe; border: 1px solid #e6e6f0; border-radius: 8px; margin: 12px 0; }
      .form-group { margin: 12px 0; }
      label { display: block; margin-bottom: 4px; font-weight: 600; font-size: 13px; }
      input, select, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; font-family: inherit; font-size: 14px; }
      textarea { min-height: 80px; resize: vertical; }
      button { background: #6366f1; color: white; padding: 10px 18px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600; }
      button:hover { background: #4f46e5; }
      button:disabled { background: #c7c7d4; cursor: not-allowed; }
      .balance { font-size: 32px; font-weight: 700; color: #6366f1; }
      .muted { color: #777; font-size: 13px; margin-top: 4px; }
      .result { margin-top: 12px; padding: 12px; background: #fff; border: 1px solid #e6e6f0; border-radius: 6px; font-size: 14px; white-space: pre-wrap; }
      .row { display: flex; gap: 12px; align-items: center; }
      .row > * { flex: 1; }
    </style>
  </head>
  <body>
    <h1>NeuralAPI Demo</h1>

    <div class="panel">
      <label>Logged-in customer ID (paste once after subscribing)</label>
      <div class="row">
        <input id="customerId" placeholder="cus_xxxxxxxxxxxx" />
        <button onclick="saveCustomerId()" style="flex:0">Save</button>
      </div>
      <div class="muted">After completing checkout, copy the customer ID from your Dodo Payments dashboard (Customers → most recent) and paste here.</div>
    </div>

    <h2>1. Subscribe to a Plan</h2>
    <div class="form-group"><label>Plan</label>
      <select id="plan">
        <option value="starter">Starter — $29/mo, 10M tokens</option>
        <option value="pro">Pro — $99/mo, 40M tokens + overage</option>
      </select>
    </div>
    <div class="form-group"><label>Email</label><input type="email" id="email" placeholder="you@example.com" /></div>
    <div class="form-group"><label>Name</label><input id="name" placeholder="Your name" /></div>
    <button onclick="subscribe(event)">Get Checkout Link</button>
    <div id="subscribeResult" class="result" style="display:none"></div>

    <h2>2. Generate AI Response (deducts tokens)</h2>
    <div class="form-group"><label>Prompt</label><textarea id="prompt" placeholder="Explain quantum computing in one sentence"></textarea></div>
    <button onclick="generate(event)">Generate</button>
    <div id="generateResult" class="result" style="display:none"></div>

    <h2>3. Live Token Balance</h2>
    <button onclick="checkBalance(event)">Refresh Balance</button>
    <div id="balanceResult" class="result" style="display:none"></div>

    <h2>4. Buy a Top-Up Pack</h2>
    <button onclick="topup(event)">Buy 5M Tokens — $19</button>
    <div id="topupResult" class="result" style="display:none"></div>

    <script>
      const $ = (id) => document.getElementById(id);
      document.addEventListener('DOMContentLoaded', () => {
        $('customerId').value = localStorage.getItem('customerId') || '';
      });

      function getCustomerId() {
        const id = $('customerId').value.trim();
        if (!id) { alert('Save a customer ID first'); throw new Error('no customer'); }
        return id;
      }

      function saveCustomerId() {
        localStorage.setItem('customerId', $('customerId').value.trim());
        alert('Saved');
      }

      async function withLoading(btn, loadingLabel, fn) {
        const original = btn.textContent;
        btn.disabled = true;
        btn.textContent = loadingLabel;
        try { await fn(); } finally {
          btn.disabled = false;
          btn.textContent = original;
        }
      }

      async function subscribe(ev) {
        await withLoading(ev.target, 'Loading…', async () => {
          const res = await fetch('/checkout/subscribe', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ plan: $('plan').value, email: $('email').value, name: $('name').value }),
          });
          const data = await res.json();
          const el = $('subscribeResult');
          el.style.display = 'block';
          el.innerHTML = res.ok
            ? `<a href="${data.checkout_url}" target="_blank">Open Checkout →</a>`
            : `Error: ${data.error}`;
        });
      }

      async function generate(ev) {
        const customer_id = getCustomerId();
        await withLoading(ev.target, 'Generating…', async () => {
          const res = await fetch('/api/generate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ customer_id, prompt: $('prompt').value }),
          });
          const data = await res.json();
          const el = $('generateResult');
          el.style.display = 'block';
          el.innerHTML = res.ok
            ? `<strong>Response:</strong>\n${data.text}\n\n<em>Tokens used: ${data.tokens_used} (${data.model})</em>`
            : `Error: ${data.error}`;
          if (res.ok) refreshBalanceSilently();
        });
      }

      async function checkBalance(ev) {
        const customer_id = getCustomerId();
        await withLoading(ev.target, 'Refreshing…', async () => {
          const res = await fetch('/credits/' + customer_id);
          const data = await res.json();
          const el = $('balanceResult');
          el.style.display = 'block';
          el.innerHTML = res.ok
            ? `<div class="balance">${Number(data.balance).toLocaleString()} tokens</div>
               <div class="muted">Overage used: ${Number(data.overage).toLocaleString()} · Last activity: ${data.last_transaction_at ?? 'never'}</div>`
            : `Error: ${data.error}`;
        });
      }

      async function refreshBalanceSilently() {
        const customer_id = $('customerId').value.trim();
        if (!customer_id) return;
        const res = await fetch('/credits/' + customer_id);
        const data = await res.json();
        const el = $('balanceResult');
        el.style.display = 'block';
        el.innerHTML = res.ok
          ? `<div class="balance">${Number(data.balance).toLocaleString()} tokens</div>
             <div class="muted">Overage used: ${Number(data.overage).toLocaleString()} · Last activity: ${data.last_transaction_at ?? 'never'}</div>`
          : `Error: ${data.error}`;
      }

      async function topup(ev) {
        const customer_id = getCustomerId();
        await withLoading(ev.target, 'Loading…', async () => {
          const res = await fetch('/checkout/topup', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ customer_id }),
          });
          const data = await res.json();
          const el = $('topupResult');
          el.style.display = 'block';
          el.innerHTML = res.ok
            ? `<a href="${data.checkout_url}" target="_blank">Open Top-Up Checkout →</a>`
            : `Error: ${data.error}`;
        });
      }
    </script>
  </body>
  </html>
  ```
</CodeGroup>

## Passaggio 7: Collegare il Webhook

I webhook consentono al tuo server di reagire ai cambiamenti di saldo — li utilizzerai per inviare email di "saldo in esaurimento" prima che i clienti raggiungano lo zero.

<Steps>
  <Step title="Expose your local server">
    I webhook necessitano di un URL pubblico. Per lo sviluppo locale, usa [ngrok](https://ngrok.com) o qualsiasi tunnel:

    ```bash theme={null}
    ngrok http 3000
    ```

    Copia l'URL `https://...ngrok-free.app`.
  </Step>

  <Step title="Register the webhook in Dodo Payments">
    1. Nella dashboard, vai su **Sviluppatori → Webhook → Aggiungi Endpoint**
    2. **URL**: `https://your-tunnel.ngrok-free.app/webhooks/dodo`
    3. Iscriviti a (al minimo):
       * `credit.added`
       * `credit.deducted`
       * `credit.overage_charged`
    4. Salva e copia il **Segreto della Firma**
    5. Incollalo in `.env` come `DODO_PAYMENTS_WEBHOOK_KEY`, quindi riavvia `npm run dev`

    <Tip>
      L'SDK `dodo.webhooks.unwrap()` valida i valori `webhook-id`, `webhook-timestamp` e `webhook-signature` usando il tuo segreto per la firma. Non devi realizzare un HMAC a mano — e non dovresti, perché Dodo Payments utilizza [Standard Webhooks](https://www.standardwebhooks.com/), che firma `id.timestamp.body` invece del solo body.
    </Tip>
  </Step>
</Steps>

## Passaggio 8: Testa il Flusso Completo

<Steps>
  <Step title="Subscribe a test customer">
    1. Esegui `npm run dev`
    2. Apri `http://localhost:3000`
    3. Scegli **Piano Pro**, inserisci un'email e un nome di test, clicca **Ottieni Link per il Checkout**, completa il checkout con [dettagli della carta di test](/miscellaneous/testing-process)
    4. Nella dashboard, vai su **Clienti → il più recente** e copia l'ID `cus_...`
    5. Incollalo nel campo "ID cliente loggato" nel demo e clicca **Salva**

    <Check>
      Il cliente dovrebbe avere 40,000,000 token. Clicca **Aggiorna Saldo** per confermare.
    </Check>
  </Step>

  <Step title="Generate a real AI response">
    Digita un prompt e clicca **Genera**. Il server chiama OpenAI, ottiene indietro il valore effettivo `total_tokens`, inserisce un evento di utilizzo e restituisce la risposta.

    <Info>
      Gli eventi di utilizzo vengono elaborati da un lavoratore in background ogni \~minuto. Il saldo non calerà istantaneamente — attendi 30–90 secondi e clicca di nuovo su **Aggiorna Saldo**. Non concludere che sia rotto se il primo aggiornamento non mostra movimento.
    </Info>
  </Step>

  <Step title="Test the top-up flow">
    Clicca **Acquista 5M Token — \$19** e completa il checkout. Dopo che il pagamento è avvenuto, aggiorna il saldo — dovrebbe aumentare di 5,000,000 token. Il registro del server dovrebbe mostrare un evento `credit.added`.
  </Step>
</Steps>

## Risoluzione dei Problemi

<AccordionGroup>
  <Accordion title="Credits not deducting after usage events">
    **Cause Possibili:**

    * Il nome dell'evento del misuratore non corrisponde a `event_name` che stai inviando (`api.tokens_used` è case-sensitive)
    * Il misuratore non è collegato al credito `API Tokens` sul prodotto — vai alla configurazione del misuratore del prodotto e conferma che **Fattura utilizzo in Crediti** è attivato
    * La chiave `metadata.tokens` non corrisponde al campo "Sopra la Proprietà" del misuratore
    * Il grant del cliente è scaduto (controlla la cronologia dei crediti del cliente)

    **Cosa Controllare:**

    1. **Prodotti → Misuratori**: apri il misuratore e conferma che mostra il nome del credito collegato sul collegamento del prodotto
    2. La scheda **Eventi** sul misuratore — gli eventi inseriti dovrebbero apparire lì anche prima della detrazione
    3. **Clienti → \[Cliente] → Crediti**: le voci del registro dovrebbero apparire entro un minuto o due
  </Accordion>

  <Accordion title="Balance always shows 0 or 'customer not found'">
    **Cause Possibili:**

    * Il cliente non ha ancora completato il checkout — i crediti vengono emessi solo dopo un pagamento riuscito
    * Stai interrogando con il `customer_id` sbagliato (usa l'ID `cus_...` dalla dashboard, non il tuo ID DB)
    * Il `CREDIT_ENTITLEMENT_ID` in `.env` non corrisponde al credito attaccato al prodotto

    **Cosa Controllare:**
    Apri **Clienti → \[Cliente] → Crediti**. Se non appaiono crediti lì, il diritto del prodotto non è stato attaccato o il pagamento non è stato completato.
  </Accordion>

  <Accordion title="Overage not working for Pro plan customers">
    **Cause Possibili:**

    * Lo sforamento non è stato abilitato sul **collegamento del credito del prodotto Pro** (l'impostazione a livello di credito è solo un predefinito)
    * Il cliente è effettivamente sul Piano Iniziale, non sul Pro
    * Il limite di sforamento è stato impostato a 0

    **Cosa Controllare:**
    Modifica Pro → Diritti → Crediti → conferma che **Consenti Sforamento** sia attivato e che **Prezzo per Unità** sia `0.000005` (= \$5 per milione di token; ricontrolla gli zeri principali — il campo accetta il prezzo per token, non per 1K).
  </Accordion>

  <Accordion title="`Webhook verification failed` in logs">
    **Cause Possibili:**

    * Ordine di parsing del corpo: `express.json()` è stato applicato a `/webhooks/dodo` prima di `express.raw()` — l'SDK ha bisogno dei **byte grezzi** della richiesta, non del JSON parsato
    * Segreto di firma errato in `DODO_PAYMENTS_WEBHOOK_KEY`
    * Il proxy inverso sta riscrivendo le intestazioni

    **Cosa Controllare:**
    Conferma che la linea `app.use('/webhooks/dodo', express.raw(...))` venga *prima* di `app.use(express.json())` in `server.ts`.
  </Accordion>
</AccordionGroup>

## Hai Bisogno di Aiuto?

* [Discord Community](https://discord.gg/bYqAp4ayYh)
* [support@dodopayments.com](mailto:support@dodopayments.com)

## Congratulazioni! Hai Costruito la Fatturazione Basata su Crediti per NeuralAPI

La tua piattaforma ora ha un sistema di fatturazione a crediti completo e pronto per la produzione:

<CardGroup cols={2}>
  <Card title="Token Credit Entitlement" icon="coins">
    Un credito `API Tokens` riutilizzabile con scadenza di 30 giorni, condiviso tra tutti i piani e il pacchetto di ricarica
  </Card>

  <Card title="Tiered Plans, One Credit" icon="layer-group">
    Piano Iniziale (10M, limite rigido) e Pro (40M + sforamento) configurati per prodotto senza duplicazione del credito
  </Card>

  <Card title="One-Time Top-Up Pack" icon="circle-plus">
    I clienti aggiungono 5M token per \$19 senza cambiare il loro abbonamento
  </Card>

  <Card title="Auto-Deduction via Meter" icon="bolt">
    I conteggi reali dei token OpenAI vengono inseriti come eventi; il misuratore detrae i crediti FIFO senza tracciamento manuale
  </Card>

  <Card title="Live Balance API" icon="gauge">
    Saldo in tempo reale tramite l'SDK per regolare l'accesso, visualizzare l'uso o avvisare i clienti nell'app
  </Card>

  <Card title="Verified Webhook Pipeline" icon="bell">
    Eventi del registro crediti (`credit.added`, `credit.deducted`, `credit.overage_charged`) instradati tramite un gestore verificato dal SDK
  </Card>
</CardGroup>

<Info>
  **Vai in produzione?** Stringi queste:

  * **Autenticazione su `/credits/:customerId` e `/api/generate`** — attualmente chiunque può colpire questi con qualsiasi ID cliente. Autentica gli utenti e cerca *il loro* ID cliente lato server.
  * **Stable `event_id`s** — l'esempio utilizza `Date.now() + random`. In produzione, usa il tuo ID richiesta in modo che i tentativi siano idempotenti (Dodo Payments deduplica per `event_id`).
  * **Mantieni la mappatura cliente↔utente** — memorizza `customer_id` nel tuo DB dopo il primo checkout in modo da non avere bisogno di un passaggio di copia manuale.
  * **Decidi cosa succede quando un abbonamento termina.** I crediti del piano rimangono nel registro del cliente fino alla loro scadenza naturale (30 giorni dall'emissione) e i crediti di ricarica restano validi per 365 giorni — ma il cookbook `/api/generate` controlla solo *saldo*, non stato dell'abbonamento. Quindi un cliente annullato può ancora consumare i token rimanenti. Questo è il default amichevole per il consumatore. Se desideri un controllo dell'accesso più rigoroso, o (a) ascolta il webhook `subscription.cancelled` e regola `/api/generate` in base allo stato dell'abbonamento, o (b) chiama l'API del registro di Dodo per addebitare i crediti inutilizzati del piano in caso di cancellazione lasciando intatti i crediti di ricarica.
  * **Monitora il dashboard Usage Billing** per rilevare precocemente anomalie di misurazione.
</Info>

<CardGroup cols={2}>
  <Card title="Credit-Based Billing Reference" icon="book" href="/features/credit-based-billing">
    Documentazione completa CBB: rollover, modalità di sforamento, gestione del registro, tutti gli endpoint API.
  </Card>

  <Card title="Credit Webhook Events" icon="bell" href="/developer-resources/webhooks/intents/credit">
    Schemi dei payload per ogni evento di credito che il tuo server potrebbe ricevere.
  </Card>
</CardGroup>
