atokim4/stripe-webhook-security

Vérifie et corrige un endpoint webhook Stripe. Analyse les gestionnaires qui traitent des événements Stripe sans vérification de signature `constructEvent` et ceux où la signature est vérifiée sur un corps parsé, puis passe en revue le corps brut, l'idempotence, la rejeu et la gestion du secret. À utiliser lorsque l'utilisateur écrit ou révise un webhook Stripe, demande « mon webhook Stripe est-il sécurisé ? », rencontre l'erreur « No signatures found matching the expected signature », ou avant de lancer une application qui gère des paiements.

Qu'est-ce que stripe-webhook-security ?

Cette compétence automatise la vérification de sécurité d'un endpoint webhook Stripe dans les projets Next.js, Express, Hono et Supabase Edge Functions. Elle exécute un scanner local qui détecte deux catégories de problèmes : les gestionnaires qui traitent des événements Stripe sans jamais appeler `constructEvent`, ce qui les rend vulnérables à toute requête POST frauduleuse, et ceux qui vérifient la signature sur un corps déjà parsé (via `req.json()`, `express.json()`, `bodyParser` par défaut de Next.js pages/api), ce qui provoque des échecs de signature pour les événements légitimes. Le scanner propose des correctifs concrets pour chaque plateforme : `request.text()` pour les route handlers fetch-style, `express.raw()` avant `express.json()` pour Express, et `bodyParser: false` pour pages/api. Au-delà du scan automatisé, la compétence guide une revue manuelle complète couvrant le secret webhook (`whsec_...`), la protection anti-rejeu (tolérance par défaut de 300 secondes), l'idempotence (upsert ou table dédiée), l'ordre des événements, la confiance dans les données vérifiées, la réponse rapide au webhook, et la liste d'événements abonnés. Elle s'intègre à Claude Code via `CLAUDE_SKILL_DIR`, et inclut un rapport structuré avec possibilité d'ignorer les faux positifs par commentaire.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/atokim4/vibe-preflight/tree/HEAD/skills/stripe-webhook-security

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Sécurité des webhooks Stripe

Cette compétence exécute la partie Stripe du scanner vibe-preflight (scripts/preflight.mjs à côté de ce fichier, Node 20+, aucune dépendance, aucun réseau). Elle trouve les gestionnaires de webhook dans les route handlers Next.js, pages/api, Express et Supabase Edge Functions et vérifie deux points dans le code. Le reste de cette page est une revue que vous effectuez en lisant le gestionnaire, car une analyse de motifs ne peut pas tout prouver.

1. Lancer le scan

Depuis la racine du projet :

node ${CLAUDE_SKILL_DIR}/scripts/preflight.mjs . --only stripe --fail-on warning --format json

Claude Code remplit ${CLAUDE_SKILL_DIR}. Autres agents : utilisez le dossier de cette compétence, par exemple .agents/skills/stripe-webhook-security ou .claude/skills/stripe-webhook-security.

RègleNiveauSignification
stripe-webhook-unverifiederreurLe gestionnaire agit sur des événements Stripe sans jamais appeler constructEvent / constructEventAsync. Quiconque trouve l'URL peut envoyer un faux checkout.session.completed.
stripe-webhook-parsed-bodyavertissementLa signature est vérifiée, mais sur un corps déjà parsé. Stripe signe les octets exacts qu'il a envoyés, donc les vrais événements échouent avec 400 et quelqu'un finit par supprimer la vérification.

--only stripe trouve un gestionnaire par les noms d'événements Stripe, une lecture de l'en-tête stripe-signature, ou un chemin de webhook dans un fichier qui mentionne Stripe. Si le scan indique 0 erreurs mais que l'utilisateur sait qu'il a un webhook, trouvez le gestionnaire vous-même et effectuez quand même la revue ci-dessous.

2. Corriger la vérification

Route handler (Next.js App Router, Remix, Hono et autres gestionnaires de style fetch) :

export async function POST(request: Request) {
  const body = await request.text(); // corps brut, jamais request.json()
  const signature = request.headers.get("stripe-signature");
  if (!signature) return new Response("Signature manquante", { status: 400 });
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response("Signature invalide", { status: 400 });
  }
  // traiter l'événement
  return Response.json({ received: true });
}

Express : enregistrez la route du webhook avec express.raw({ type: "application/json" }) avant app.use(express.json()), et passez req.body (un Buffer alors) à constructEvent.

Next.js pages/api : export const config = { api: { bodyParser: false } }; et lisez la requête brute dans un Buffer avant de vérifier.

Supabase Edge Functions et autres runtimes Deno / edge : await stripe.webhooks.constructEventAsync(body, signature, secret, undefined, Stripe.createSubtleCryptoProvider()).

3. Examiner le reste en lisant le gestionnaire

Le scanner ne vérifie pas ces points. Regardez le code et dites à l'utilisateur ce que vous avez trouvé pour chacun.

  • Secret. STRIPE_WEBHOOK_SECRET (whsec_...) provient des variables d'environnement serveur, jamais d'une variable NEXT_PUBLIC_ / VITE_ ni du code source. Les modes test et production ont des secrets différents, de même que chaque endpoint et la CLI stripe listen.
  • Rejeu. constructEvent rejette une signature plus ancienne que sa tolérance (300 secondes par défaut). Laissez l'argument de tolérance de côté, ou gardez-le petit. N'attrapez jamais l'erreur pour continuer.
  • Idempotence. Stripe peut délivrer le même événement plusieurs fois. Rendez l'écriture naturellement répétable (un upsert avec l'ID Stripe comme clé, définir un statut plutôt que d'ajouter des crédits) ou stockez event.id dans une table avec une contrainte d'unicité et ignorez les ID déjà vus. Examinez attentivement tout ce qui ajoute : crédits, solde, stock.
  • Ordre. Les événements peuvent arriver dans le désordre. Pour les abonnements, récupérez l'objet actuel depuis l'API (stripe.subscriptions.retrieve) ou comparez les horodatages au lieu de faire confiance à l'ordre des événements.
  • Confiance. Prenez l'utilisateur et le plan de l'événement vérifié (client_reference_id, metadata, l'ID client stocké lors du paiement), jamais d'une chaîne de requête ou d'une seconde requête non signée. La redirection vers la page de succès ne prouve rien : accordez l'accès depuis le webhook.
  • Réponse. Retournez un code 2xx rapidement une fois l'événement stocké ou traité. Le travail lent va dans une file d'attente. Retournez 400 uniquement pour une signature invalide, afin que les tentatives de Stripe restent significatives.
  • Liste d'événements. Abonnez l'endpoint uniquement aux événements que le code gère, et ignorez les autres types sans erreur.

4. Rapport

Listez les résultats du scanner, puis une ligne par élément de la revue : OK, corrigé, ou une question pour l'utilisateur (par exemple « des crédits sont ajoutés à chaque livraison : un événement répété devrait-il en ajouter à nouveau ? »). Relancez le scan après la correction et montrez sa dernière ligne. Pour tester en conditions réelles, suggérez stripe listen --forward-to localhost:3000/api/webhooks et stripe trigger checkout.session.completed avec la CLI Stripe.

Un faux positif confirmé peut être ignoré avec // vibe-preflight-ignore <rule-id> sur la ligne ou la ligne au-dessus, avec la raison dans le commentaire.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills associés