Skip to main content

Liste de contrôle pré-lancement

Parcourez cette liste avant la mise en production. Chaque élément renvoie à la section correspondante ci-dessous.
1

Protection contre la réutilisation configurée pour les instances multiples

L’adaptateur par défaut est uniquement en mémoire. Si vous exécutez plus d’un processus (workers Gunicorn, cluster PM2, Kubernetes), définissez SUPABASE_URL + SUPABASE_ANON_KEY ou utilisez RedisReplayAdapter. Détails →
2

Variables d'environnement dans un gestionnaire de secrets

Ne codez jamais les clés API en dur. Utilisez .env en local, un gestionnaire de secrets en production. Détails →
3

Un endpoint de vérification de santé existe

Les pings de surveillance ne doivent pas déclencher la création de factures. Ajoutez une route /health avant vos routes payantes. Détails →
4

Les réponses d'erreur ne divulguent pas d'informations internes

Interceptez les erreurs du fournisseur et retournez un 503 propre — pas une trace de pile. Détails →
5

Le prix est intentionnel

priceSats doit refléter une valeur réelle. À 1 sat ≈ 0,0006 ,100sats0,06, 100 sats ≈ 0,06 pour un endpoint premium est raisonnable. Ne le mettez pas à 0 par accident.
6

Surveillance de la disponibilité sur l'endpoint de facturation

Surveillez la réponse 402 (c’est une réponse normale, pas une erreur). Des outils comme UptimeRobot prennent en charge les attentes de codes de statut personnalisés.
7

Limitation du débit sur la création de factures

Chaque requête non authentifiée crée une facture Lightning. Sans limitation du débit, un attaquant peut épuiser le quota de factures de votre fournisseur gratuitement. Ajoutez express-rate-limit avant de déployer publiquement. Détails →

Critique : protection contre la réutilisation en production

L’adaptateur de réutilisation par défaut est uniquement en mémoire — il se réinitialise à chaque redémarrage de processus et ne fonctionne pas sur plusieurs instances de serveur. En production avec plus d’un processus (workers Gunicorn, pods Kubernetes, cluster PM2), le même preimage peut être accepté deux fois.Correction : Définissez SUPABASE_URL + SUPABASE_ANON_KEY dans votre environnement. Le middleware utilisera automatiquement Supabase comme magasin de réutilisation, partagé entre toutes les instances.Pour Redis : passez un RedisReplayAdapter explicitement (voir SDK TypeScript ou SDK Python).

Variables d’environnement

Ne codez jamais les clés en dur. Utilisez toujours des variables d’environnement :
# .env (ne jamais commiter ce fichier)
BLINK_API_KEY=blink_xxx
BLINK_WALLET_ID=your-wallet-id
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=sb_publishable_xxx    # sûr à exposer aux clients
SUPABASE_SERVICE_KEY=sb_secret_xxx      # côté serveur uniquement — ne jamais exposer aux clients
import 'dotenv/config';
import { BlinkProvider } from 'l402-kit';

const blink = new BlinkProvider(
  process.env.BLINK_API_KEY!,
  process.env.BLINK_WALLET_ID!,
);

Journalisation des paiements (Supabase)

Enregistrez chaque paiement dans Supabase pour le tableau de bord de l’extension VS Code et les analyses :
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!,
);

app.use(async (req, res, next) => {
  const original = res.json.bind(res);
  res.json = (body) => {
    // Journaliser les paiements L402 réussis (après le passage du middleware)
    if (req.l402Paid && req.l402Preimage) {
      supabase.from('payments').insert({
        endpoint: req.path,
        preimage: req.l402Preimage,
        amount_sats: req.l402AmountSats,
      }).then(() => {});
    }
    return original(body);
  };
  next();
});

Déploiement sur Cloudflare Workers

L’API l402-kit fonctionne sur Cloudflare Workers (isolats V8, démarrage à froid nul). Le magasin de réutilisation en mémoire se réinitialise par isolat — pour les APIs à fort trafic, utilisez Cloudflare KV ou Durable Objects pour la protection contre la réutilisation.
// api/data.ts
import { l402 } from 'l402-kit';
import { BlinkProvider } from 'l402-kit';

const blink = new BlinkProvider(
  process.env.BLINK_API_KEY!,
  process.env.BLINK_WALLET_ID!,
);

export default async function handler(req, res) {
  await new Promise<void>((resolve, reject) => {
    l402({ priceSats: 10, lightning: blink })(req, res, (err) => {
      if (err) reject(err); else resolve();
    });
  });
  res.json({ data: 'premium content' });
}

Docker

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
# docker-compose.yml
services:
  api:
    build: .
    environment:
      BLINK_API_KEY: ${BLINK_API_KEY}
      BLINK_WALLET_ID: ${BLINK_WALLET_ID}
    ports:
      - "3000:3000"

Suppression des données utilisateur

Exposez un endpoint de suppression pour que les utilisateurs puissent supprimer définitivement leurs données. Le backend l402-kit inclut /api/delete-data par défaut :
POST /api/delete-data
Content-Type: application/json

{ "lightningAddress": "user@blink.sv" }
// 200 OK
{ "deleted": { "payments": 42, "proAccess": true } }
L’extension VS Code affiche cela sous la forme d’un panneau Zone de danger en bas du tableau de bord — les utilisateurs doivent saisir leur adresse Lightning pour confirmer, puis tout l’historique des paiements et l’accès Pro sont supprimés. L’opération utilise la clé de service Supabase côté serveur ; la clé anon n’a pas de permission DELETE.

Gestion des erreurs

Interceptez les erreurs du fournisseur avant qu’elles n’apparaissent sous forme de 500 non formatés :
import { l402, L402Error } from 'l402-kit';

app.get('/api/data', l402({ priceSats: 10, lightning }), async (req, res) => {
  try {
    res.json({ data: await fetchData() });
  } catch (err) {
    if (err instanceof L402Error) {
      // Jeton invalide, expiré ou déjà utilisé — laisser le middleware le gérer
      return res.status(err.status).json({ error: err.code });
    }
    console.error('[api/data]', err);
    res.status(503).json({ error: 'Service temporairement indisponible' });
  }
});
Règles générales :
  • Ne retournez jamais de traces de pile aux clients — enregistrez-les côté serveur.
  • Les délais d’attente du fournisseur (503) sont transitoires — il est prudent de réessayer avec un délai exponentiel.
  • Les erreurs de jeton (401) ne sont jamais transitoires — ne réessayez pas automatiquement, exigez une nouvelle facture.
  • Les erreurs de limitation du débit (429) — transmettez le champ retryAfter à l’appelant.
Consultez la Référence des erreurs pour la liste complète des codes d’erreur structurés.

Limitation du débit

Chaque requête non authentifiée déclenche createInvoice() sur votre fournisseur Lightning. Sans limitation du débit, n’importe qui peut saturer votre endpoint et épuiser le quota API de votre fournisseur gratuitement — sans payer un seul sat. Ajoutez express-rate-limit avant vos routes L402 :
npm install express-rate-limit
import rateLimit from "express-rate-limit";
import { l402 } from "l402-kit";

// Limiter la création de factures : 30 requêtes non authentifiées/minute par IP
const invoiceLimit = rateLimit({
  windowMs: 60_000,
  max: 30,
  // Appliquer uniquement aux requêtes qui ne portent pas déjà un en-tête L402 valide
  skip: (req) => req.headers.authorization?.startsWith("L402 ") ?? false,
  message: { error: "Trop de requêtes — ralentissez" },
});

app.get("/api/premium", invoiceLimit, l402({ priceSats: 10, lightning }), handler);
Les clients déjà payants sont ignorés par la fonction skip — la limite s’applique uniquement aux appels non authentifiés qui déclenchent une nouvelle facture. Les payeurs légitimes ne sont jamais limités.
Pour FastAPI :
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.get("/api/premium")
@limiter.limit("30/minute")
@l402_required(price_sats=10, lightning=lightning)
async def premium(request: Request):
    return {"data": "paid content"}

Endpoint de vérification de santé

Ajoutez toujours une vérification de santé gratuite pour que les outils de surveillance ne déclenchent pas la création de factures :
app.get('/health', (req, res) => res.json({ ok: true, ts: Date.now() }));

// Endpoint payant
app.get('/api/data', l402({ priceSats: 10, lightning: blink }), handler);

Surveillance

Métriques clés à suivre :
  • Taux de réponse 402 — la valeur de référence saine est élevée (la plupart des appelants doivent payer)
  • Taux de vérification des paiements — ratio des appels payés par rapport aux non payés
  • Latence du fournisseurblink.createInvoice() devrait être < 500 ms
  • Tentatives de réutilisation — un pic indique des attaques de réutilisation de jeton
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    console.log(JSON.stringify({
      method: req.method,
      path: req.path,
      status: res.statusCode,
      ms: Date.now() - start,
      paid: res.statusCode !== 402,
    }));
  });
  next();
});

Performance

  • La vérification des jetons est O(1) — cryptographie pure, sans base de données, sans réseau
  • La création de factures (chemin 402) appelle l’API de votre fournisseur Lightning — ajoutez un cache si vous prévoyez que le même endpoint sera sollicité à plusieurs reprises avant le paiement
  • Le magasin de réutilisation est un Set en mémoire — pour les déploiements multi-instances, remplacez-le par Redis
// Exemple de magasin de réutilisation Redis
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);

// Passer une fonction de réutilisation personnalisée au middleware
app.get('/api', l402({
  priceSats: 10,
  lightning: blink,
  checkReplay: async (preimage) => {
    const set = await redis.set(`l402:${preimage}`, '1', 'NX', 'EX', 86400);
    return set === 'OK';
  },
}), handler);