Tous les articles
Événements du cycle de vie des utilisateurs Clerk transmis par un tunnel webhook signé à une route App Router Next.js et à une base de données locale.
ClerkNext.jswebhooksuser sync

Tester les webhooks Clerk en local avec Next.js

Pour tester les webhooks Clerk sur localhost dans Next.js, créez une route POST App Router, exposez le port 3000 avec un tunnel HTTPS, ajoutez l'URL publique en tant que point de terminaison du webhook Clerk et vérifiez chaque demande avec verifyWebhook() avant de synchroniser les données utilisateur. Gardez l'itinéraire public dans le middleware Clerk : le secret de signature authentifie la demande de machine à machine, pas une session de navigateur.

Quand les webhooks Clerk sont le bon outil de synchronisation

Clerk reste la source identitaire de la vérité. Un webhook est utile lorsque votre application a besoin d'une projection locale pour les jointures, la recherche, la création de rapports, les métadonnées d'autorisation ou les intégrations qui ne peuvent pas interroger Clerk à la demande. Les événements typiques incluent user.created, user.updated, et user.deleted.

Un webhook est asynchrone. L'utilisateur peut terminer son inscription avant que votre projection de base de données n'existe, les livraisons peuvent être réessayées et les mises à jour peuvent arriver à proximité. Ne faites pas de la projection votre seule source de contrôle d'identité immédiat après l'inscription. Concevez les lectures pour tolérer un court délai ou créez explicitement la ligne d'application dans le flux utilisateur et laissez les webhooks la réconcilier.

Créer un point de terminaison public Next.js App Router

Ajouter app/api/webhooks/clerk/route.ts. Courant de Clerk guide de synchronisation utilise verifyWebhook depuis @clerk/nextjs/webhooks. L'assistant consomme la requête, vérifie la signature des Webhooks standard et renvoie les données d'événement saisies.

// app/api/webhooks/clerk/route.ts
import { verifyWebhook } from '@clerk/nextjs/webhooks';
import { NextRequest } from 'next/server';

export const runtime = 'nodejs';

export async function POST(request: NextRequest) {
  try {
    const event = await verifyWebhook(request);

    await processOnce(event, async () => {
      switch (event.type) {
        case 'user.created':
        case 'user.updated':
          await upsertClerkUser(event.data);
          break;
        case 'user.deleted':
          if (event.data.id) await archiveClerkUser(event.data.id);
          break;
      }
    });

    return new Response('accepted', { status: 200 });
  } catch (error) {
    console.error('Clerk webhook rejected', safeError(error));
    return new Response('invalid webhook', { status: 400 });
  }
}

Par défaut, l'assistant lit CLERK_WEBHOOK_SIGNING_SECRET. Greffier vérifier la référence du Webhook permet également une explicitation signingSecret option, mais la configuration de l'environnement évite d'intégrer le secret dans la source.

Exclure la route du webhook de la protection de session

Les requêtes Webhook ne transportent pas la session Clerk de votre utilisateur. Si le middleware appelle auth.protect() pour chaque chemin d'API, la livraison de Clerk reçoit une redirection, 401 ou 404 avant l'exécution de la vérification de la signature. Définir explicitement les routes d'application protégées et quitter /api/webhooks/clerk publique.

// middleware.ts for Next.js 15 and earlier
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher([
  '/dashboard(.*)',
  '/api/private(.*)',
]);

export default clerkMiddleware(async (auth, request) => {
  if (isProtectedRoute(request)) await auth.protect();
});

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)',
    '/(api|trpc)(.*)',
  ],
};

Greffier guide de débogage des webhooks appelle spécifiquement à l’exclusion des routes webhook. Dans les versions plus récentes de Next.js, la convention peut utiliser proxy.ts; suivez le guide de la version Clerk installé dans votre projet. Public ne veut pas dire fiable : verifyWebhook() est obligatoire avant toute action.

Connecter le Clerk à localhost

  1. Courir npm run dev et vérifiez que l'application Next.js écoute sur le port 3000.
  2. Courir npx portpreview 3000.
  3. Dans le tableau de bord Clerk, créez un point de terminaison webhook avec https://your-subdomain.portpreview.dev/api/webhooks/clerk.
  4. Sélectionnez uniquement les événements d'utilisateur, de session, d'organisation ou de courrier électronique requis.
  5. Copiez le secret de signature du point de terminaison dans CLERK_WEBHOOK_SIGNING_SECRET dans votre environnement local et redémarrez Next.js.
  6. Ouvrez l'onglet Test du point de terminaison, choisissez user.created, puis sélectionnez Envoyer un exemple.
  7. Confirmez que la tentative indique Réussi, que votre route locale a renvoyé 200 et que la ligne de base de données attendue a été modifiée une fois.

L'URL du tunnel doit rester active pour les exemples suivants. Si cela change, mettez à jour le point de terminaison. Ne réutilisez pas le secret de signature d'un point de terminaison de production pour des tests locaux ; créez des points de terminaison spécifiques à l’environnement afin que l’historique de rotation et d’audit reste clair.

Modélisez soigneusement la synchronisation des utilisateurs

Utiliser l'ID utilisateur de Clerk comme clé externe

Magasin user_... dans un cadre unique clerk_user_id colonne. Upsert sur cette clé donc réessayé user.created converge au lieu d’échouer. Conservez votre propre clé primaire interne si d’autres tables y font déjà référence.

Choisissez délibérément l'e-mail principal

Les données utilisateur de Clerk contiennent des enregistrements d'adresse e-mail et un identifiant d'adresse e-mail principal. Résolvez l'enregistrement principal par ID au lieu de prendre le premier élément du tableau. L'e-mail peut changer ; ne l'utilisez pas comme clé étrangère immuable.

Décidez de ce que signifie la suppression

UN user.deleted L'événement peut contenir moins de données que la création ou la mise à jour. Utilisez l’ID pour anonymiser, supprimer de manière réversible ou démarrer un workflow de rétention conformément à votre politique. La suppression aveugle en cascade peut détruire les enregistrements de facturation ou d’audit que les réglementations vous obligent à conserver.

Ne reflète pas tout

Conservez uniquement les champs dont votre application a besoin. Chaque champ de profil copié crée une obligation de confidentialité, de conservation et d'obsolescence. Récupérez rarement les données Clerk utilisées à la demande plutôt que de dupliquer une charge utile entière.

Rendre les tentatives et les commandes inoffensives

L'employé indique qu'une réponse non-2xx provoque une nouvelle tentative d'événement. Enregistrez l'identifiant du message du webhook à partir des métadonnées ou des en-têtes du webhook signés en tant que reçu unique avant d'appliquer les effets. Si votre SDK expose les ID Webhooks standard dans les en-têtes, conservez-les avec le type d'événement et l'horodatage. Renvoyez 200 pour un duplicata complété.

Pour les mises à jour utilisateur, comparez les horodatages des événements ou utilisez des règles de dernière écriture qui empêchent un événement plus ancien d'écraser les données de profil les plus récentes. Pour un état de grande valeur, récupérez l’utilisateur actuel auprès de Clerk après vérification et traitez le webhook comme un signal de réconciliation. Conservez l'insertion de reçu, la mise à jour utilisateur et le travail de boîte d'envoi dans une seule transaction de base de données.

await db.transaction(async (tx) => {
  const inserted = await tx.webhookReceipt.insertIfAbsent(messageId);
  if (!inserted) return;

  await tx.user.upsert({
    clerkUserId: clerk.id,
    primaryEmail: findPrimaryEmail(clerk),
    sourceUpdatedAt: eventTimestamp,
  });
  await tx.outbox.enqueue('profile-synced', { clerkUserId: clerk.id });
});

Ce modèle évite les e-mails de bienvenue en double et les écritures partielles. Voir tentatives de webhook et idempotence pour les options de schéma.

Dépanner les livraisons de Clerk local

404, redirection ou HTML au lieu de votre gestionnaire

Vérifiez le chemin du fichier, l'exportation POST et l'URL complète du tunnel. Vérifiez les réécritures du middleware et des paramètres régionaux. Un webhook ne doit pas passer par une redirection de connexion ou une vérification de formulaire CSRF. Testez l'URL publique avec un POST de base ; attendez-vous à un rejet de signature de votre itinéraire, pas à un framework 404.

verifyWebhook() jette toujours

Redémarrez Next.js après avoir défini le secret de signature. Confirmez que le secret appartient à ce point de terminaison et à cet environnement. N'analysez pas, ne clonez pas de manière incorrecte et ne consommez pas le corps de la requête avant de le transmettre à l'assistant. Assurez-vous que le tunnel préserve les en-têtes de signature Webhooks standard.

Le tableau de bord affiche les tentatives

Regardez la réponse exacte à la tentative. Renvoyez 2xx uniquement après une acceptation durable, mais continuez le traitement en dessous du délai d'expiration du fournisseur. Les migrations de bases de données, les erreurs de contrainte unique et l'appel synchrone d'un service indisponible sont des causes courantes.

Les lignes sont dupliquées ou obsolètes

Ajoutez des contraintes uniques pour l’ID de Clerk et l’ID du message webhook. Faire user.created et user.updated les deux upserts sécurisés, puis protégez-vous contre les horodatages d'événements plus anciens. Rejouez l'exemple après chaque correctif pour prouver la gestion des doublons.

Liste de contrôle de sécurité

  • Vérifiez chaque demande avec l'assistant de Clerk avant de consigner les champs de charge utile ou d'écrire des données.
  • Conservez la route non authentifiée par le middleware de session utilisateur mais protégée par la signature du webhook.
  • Utilisez des secrets de point de terminaison distincts pour les environnements locaux, de prévisualisation, de préparation et de production.
  • Dédupliquez les ID de message signés et limitez les ID utilisateur de manière unique.
  • Supprimez les e-mails, les téléphones, les jetons, les secrets et les charges utiles complètes des journaux normaux.
  • Ajoutez éventuellement des contrôles IP documentés par Clerk/Svix comme défense en profondeur, mais ne remplacez jamais la vérification de signature par un filtrage IP.
  • Limitez le trafic invalide, limitez la taille du corps et faites pivoter le secret s'il apparaît dans les journaux ou dans le contrôle de code source.

Greffier présentation des webhooks explique la vérification de la signature et les restrictions IP facultatives de Svix. Pour connaître les principes fondamentaux du corps brut et le comportement des itinéraires de Next.js, continuez avec la section Guide du webhook localhost Next.js.

Questions fréquentes

Comment tester un webhook Clerk en local avec Next.js ?
Créez une route POST App Router, exposez le port 3000 avec un tunnel HTTPS, ajoutez la route publique complète dans le tableau de bord Clerk, définissez CLERK_WEBHOOK_SIGNING_SECRET, puis envoyez un exemple depuis l’onglet de test de l’endpoint.
Faut-il protéger la route webhook Clerk avec clerkMiddleware ?
Elle doit être accessible sans session utilisateur : n’y exécutez donc pas auth.protect(). Authentifiez plutôt la requête machine avec verifyWebhook() et le secret de signature de l’endpoint.
Dois-je analyser le corps brut avant Clerk verifyWebhook() ?
Non. Transmettez directement la Request d’origine à verifyWebhook(). N’appelez pas request.json() ni request.text() auparavant, car le helper a besoin du corps signé et des en-têtes.
Comment gérer les nouvelles tentatives de user.created de Clerk ?
Effectuez un upsert avec l’ID utilisateur Clerk unique, dédupliquez avec l’ID du message webhook signé et renvoyez 200 si l’événement a déjà été traité. Placez les effets secondaires non essentiels en file d’attente pour éviter les e-mails en double.