すべての記事
Clerkのユーザーライフサイクルイベントが、署名付きWebhookトンネルを通ってNext.js App Routerエンドポイントとローカルデータベースへ届く様子。
ClerkNext.jswebhooksuser sync

Next.jsでClerk Webhookをローカルテストする

Next.js の localhost で Clerk Webhook をテストするには、App Router の POST ルートを作成し、HTTPS トンネルでポート 3000 を公開して、公開 URL を Clerk Webhook エンドポイントとして追加します。ユーザーデータを同期する前に、各リクエストを verifyWebhook() で検証してください。 Clerk ミドルウェアではこのルートを公開したままにします。署名シークレットが認証するのはブラウザーセッションではなく、マシン間リクエストです。

Clerk Webhook が適切な同期ツールである場合

Clerkは依然としてアイデンティティの真実の情報源です。 Webhook は、アプリケーションが結合、検索、レポート、認可メタデータ、または Clerk をオンデマンドでクエリできない統合のためにローカル プロジェクションを必要とする場合に役立ちます。代表的なイベントとしては、 user.createduser.updated、 そして user.deleted

Webhook は非同期です。データベース プロジェクションが存在する前にユーザーがサインアップを完了し、配信が再試行され、更新がほぼ同時に到着する可能性があります。サインアップ直後の本人確認の唯一の情報源として投影を使用しないでください。短い遅延を許容するように読み取りを設計するか、ユーザー フロー内で明示的にアプリケーション行を作成し、Webhook でそれを調整できるようにします。

Next.js App Router のパブリック エンドポイントを作成する

追加 app/api/webhooks/clerk/route.ts。Clerkの現在 同期ガイド 用途 verifyWebhook から @clerk/nextjs/webhooks。ヘルパーはリクエストを消費し、標準 Webhook 署名をチェックして、型指定されたイベント データを返します。

// 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 });
  }
}

デフォルトでは、ヘルパーは次のように読み取ります。 CLERK_WEBHOOK_SIGNING_SECRET。事務員さん verifyWebhook リファレンス 明示的なも許可します signingSecret オプションですが、環境設定によりソースにシークレットを埋め込むことが回避されます。

Webhook ルートをセッション保護から除外する

Webhook リクエストには、ユーザーの Clerk セッションは含まれません。ミドルウェアが呼び出す場合 auth.protect() すべての API パスについて、Clerk の配信は署名検証が実行される前にリダイレクト、401、または 404 を受け取ります。保護されたアプリケーションルートを明示的に定義して終了する /api/webhooks/clerk 公共。

// 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)(.*)',
  ],
};

事務員さん Webhook デバッグ ガイド 特に Webhook ルートを除外して呼び出します。新しい Next.js バージョンでは、規約で使用される可能性があります proxy.ts;プロジェクトにインストールされている Clerk バージョン ガイドに従ってください。公開されているということは、信頼できるという意味ではありません。 verifyWebhook() アクションの前に必須です。

Clerk をローカルホストに接続する

  1. 走る npm run dev Next.js アプリがポート 3000 でリッスンしていることを確認します。
  2. 走る npx portpreview 3000
  3. Clerk ダッシュボードで、Webhook エンドポイントを作成します。 https://your-subdomain.portpreview.dev/api/webhooks/clerk
  4. 必要なユーザー、セッション、組織、または電子メール イベントのみを選択します。
  5. エンドポイントの署名シークレットをコピーします CLERK_WEBHOOK_SIGNING_SECRET ローカル環境で Next.js を再起動します。
  6. エンドポイントの「テスト」タブを開き、選択します user.createdをクリックし、「例を送信」を選択します。
  7. 試行が成功し、ローカル ルートが 200 を返し、予期されたデータベース行が 1 回変更されたことを確認します。

トンネル URL は、後続の例でもアクティブなままにしておく必要があります。変更された場合は、エンドポイントを更新します。運用エンドポイントの署名シークレットをローカル テストに再利用しないでください。環境固有のエンドポイントを作成して、ローテーションと監査の履歴を明確に保ちます。

ユーザー同期を慎重にモデル化する

Clerk ユーザー ID を外部キーとして使用する

user_... ユニークな clerk_user_id カラム。そのキーに対して更新/挿入を行うため、再試行されます user.created 失敗するのではなく収束します。他のテーブルが既に内部主キーを参照している場合は、独自の内部主キーを保持します。

メインのメールアドレスを慎重に選択する

事務員ユーザー データには、電子メール アドレス レコードとプライマリ電子メール アドレス ID が含まれています。最初の配列要素を取得する代わりに、ID によってプライマリ レコードを解決します。電子メールは変更される可能性があります。これを不変の外部キーとして使用しないでください。

削除の意味を決定する

user.deleted イベントには、作成または更新よりも少ないデータが含まれる可能性があります。 ID を使用して、ポリシーに従って匿名化、論理削除、または保持ワークフローを開始します。ブラインド カスケード削除を行うと、規制で保存が義務付けられている請求記録や監査記録が破壊される可能性があります。

すべてをミラーリングしないでください

アプリケーションに必要なフィールドのみを永続化します。コピーされたプロファイル フィールドごとに、プライバシー、保持、および最新性に関する義務が生じます。ペイロード全体を複製するのではなく、オンデマンドで Clerk データをフェッチすることはほとんどありません。

再試行と注文を無害化する

Clerk は、2xx 以外の応答によってイベントの再試行が発生することを文書化しました。エフェクトを適用する前に、署名された Webhook メタデータまたはヘッダーからの Webhook メッセージ ID を一意のレシートとして記録します。 SDK がヘッダーで標準 Webhook ID を公開する場合は、それらをイベント タイプとタイムスタンプと一緒に保存します。完成した複製の場合は 200 を返します。

ユーザー更新の場合は、イベントのタイムスタンプを比較するか、古いイベントが新しいプロファイル データを上書きしないようにする最終書き込みルールを使用します。高価値状態の場合は、検証後に Clerk から現在のユーザーを取得し、Webhook を調整する信号として扱います。レシートの挿入、ユーザー更新、および送信トレイのジョブを 1 つのデータベース トランザクションにまとめます。

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 });
});

このパターンにより、ウェルカム メールの重複や部分的な書き込みが防止されます。見る Webhook の再試行と冪等性 スキーマオプションについては。

地元のClerkによる配達のトラブルシューティング

404、リダイレクト、またはハンドラーの代わりに HTML

ファイル パス、POST エクスポート、および完全なトンネル URL を確認します。ミドルウェアとロケールの書き換えを確認します。 Webhook は、ログイン リダイレクトまたは CSRF フォーム チェックを通過しないでください。基本的な POST を使用してパブリック URL をテストします。フレームワーク 404 ではなく、ルートからの署名拒否を期待してください。

verifyWebhook() いつも投げる

署名シークレットを設定した後、Next.js を再起動します。シークレットがこのエンドポイントと環境に属していることを確認します。ヘルパーに渡す前に、リクエスト本文を解析したり、間違って複製したり、消費したりしないでください。トンネルが標準 Webhook 署名ヘッダーを保持していることを確認します。

ダッシュボードに再試行が表示される

正確な試行応答を見てください。永続的な受け入れ後にのみ 2xx を返しますが、プロバイダーのタイムアウト未満で処理を続けます。データベースの移行、一意制約の間違い、利用できないサービスの同期呼び出しなどが一般的な原因です。

行が重複しているか古い

Clerk ID と Webhook メッセージ ID の一意の制約を追加します。作る user.created そして user.updated 両方とも安全な更新/挿入を実行し、古いイベント タイムスタンプから保護します。各修正後に例を再生して、重複の処理を確認します。

セキュリティチェックリスト

  • ペイロード フィールドを記録したりデータを書き込んだりする前に、Clerk のヘルパーを使用してすべてのリクエストを検証してください。
  • ルートはユーザーセッションミドルウェアによって認証されないままにしますが、Webhook 署名によって保護されます。
  • ローカル、プレビュー、ステージング、実稼働環境に個別のエンドポイント シークレットを使用します。
  • 署名付きメッセージ ID の重複を排除し、ユーザー ID を一意に制限します。
  • 通常のログから電子メール、電話、トークン、シークレット、および完全なペイロードを編集します。
  • 必要に応じて、多層防御として Clerk/Svix 文書化された IP 制御を追加しますが、署名検証を IP フィルタリングに置き換えないでください。
  • 無効なトラフィックをレート制限し、本体サイズを制限し、ログまたはソース管理に表示される場合はシークレットをローテーションします。

事務員さん Webhook の概要 署名の検証とオプションの Svix IP 制限について説明します。 Next.js の raw-body の基礎とルートの動作については、「 Next.js localhost Webhook ガイド

よくある質問

Next.jsでClerk Webhookをローカルテストするには?
App RouterのPOSTルートを作成し、HTTPSトンネルでポート3000を公開します。Clerk Dashboardに完全な公開ルートを追加し、CLERK_WEBHOOK_SIGNING_SECRETを設定して、エンドポイントのテストタブからサンプルを送信します。
Clerk WebhookルートをclerkMiddlewareで保護する必要はありますか?
ユーザーセッションなしで到達できる必要があるため、auth.protect()を適用しません。代わりにverifyWebhook()とエンドポイントの署名シークレットでマシン間リクエストを認証します。
Clerk verifyWebhook()の前にraw bodyを解析する必要はありますか?
いいえ。元のRequestをそのままverifyWebhook()へ渡してください。署名対象の本文とヘッダーが必要なので、先にrequest.json()やrequest.text()を呼び出してはいけません。
Clerkのuser.created再送はどう処理しますか?
一意なClerkユーザーIDでupsertし、署名付きWebhookメッセージIDで重複排除します。処理済みイベントには200を返し、重複メールを防ぐため重要でない副作用はキューへ送ります。