Tous les articles
Next.jsApp Routerwebhook debugginglocal testing

Webhooks Next.js sur localhost, sans détour

La principale cause de webhooks cassés dans Next.js App Router : le corps brut. L'App Router fonctionne. Les route handlers fonctionnent. Mais les conventions pour récupérer le corps non parsé ont changé entre Pages Router et App Router, et la plupart des intégrations Stripe / GitHub / Shopify copient des snippets de l'ancien monde.

La règle en deux lignes

Dans un route handler App Router, récupérez le corps brut avec await request.text(). N'appelez pas request.json() avant la vérification de signature. Ne tentez pas bodyParser: false — ce flag n'existe plus.

// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const body = await request.text();
  const signature = (await headers()).get('stripe-signature')!;

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch (err) {
    return NextResponse.json(
      { error: 'invalid signature' },
      { status: 400 },
    );
  }

  return NextResponse.json({ received: true });
}

request.text() donne la chaîne brute signée par Stripe. headers() est async dans App Router. La vérification doit précéder tout équivalent JSON.parse.

L'habitude Pages Router à oublier

// pages/api/webhooks/stripe.ts (ANCIEN)
export const config = { api: { bodyParser: false } };

Cette config n'existe plus en App Router. Le corps est lu à la demande. La deuxième lecture renvoie rien — request.text() après request.json() renvoie vide.

Setup tunnel pour Next.js

  1. Lancez next dev (port 3000 généralement).
  2. Dans un autre terminal, npx portpreview 3000.
  3. Collez l'URL HTTPS dans la config webhook du provider.
  4. Déclenchez un événement. La requête arrive avec en-têtes intacts.

Compatible avec next dev --turbo.

Edge runtime : à éviter

Si vous avez export const runtime = 'edge' sur une route webhook, certains SDKs perdent accès à des APIs crypto Node. Le SDK Stripe fonctionne en edge en versions récentes mais la vérification de signature avec des providers arbitraires est aléatoire.

export const runtime = 'nodejs';

Pièges App Router récurrents

Mauvais type de retour

Si vous retournez new Response(JSON.stringify(...)) au lieu de NextResponse.json(...), le content-type peut manquer. Certains providers s'en plaignent.

Streaming et WebSockets

Les handlers webhook sont des requêtes HTTP courtes. Si vous tunnelisez aussi des routes WebSocket, vérifiez que le tunnel préserve l'upgrade.

Middleware qui lit le corps

Un middleware.ts qui appelle request.text() pour logger ou auth-ifier vide le corps avant la route webhook. Excluez le chemin webhook du matcher.

Vercel ne vous sauvera pas

Les bugs de corps brut se comportent à l'identique sur Vercel et localhost. Si le test local en tunnel passe, le déploiement aussi — sauf limites mémoire/timeout serverless (symptômes 5xx, pas 401).

Voir test webhook Stripe en local et guide vérification de signature. Rejoignez la waitlist PortPreview.

Questions fréquentes

Comment récupérer le corps brut dans un webhook Next.js App Router ?
Appelez await request.text() dans le handler POST. N'appelez pas request.json() avant — une fois le corps lu, le second appel renvoie vide.
Faut-il bodyParser: false dans Next.js App Router ?
Non. Cette config était un pattern Pages Router sans équivalent en App Router. Le corps est lu à la demande, en texte brut si vous devez vérifier une signature.
Peut-on lancer une route webhook sur le edge runtime Next.js ?
Techniquement oui, mais certains SDKs de vérification dépendent d'APIs crypto Node indisponibles en edge. Restez sur le runtime Node par défaut pour les webhooks.