Todos os artigos
Eventos do ciclo de vida de usuários do Clerk passando por um túnel de webhook assinado até um endpoint do Next.js App Router e um banco de dados local.
ClerkNext.jswebhooksuser sync

Como testar webhooks do Clerk no localhost com Next.js

Para testar webhooks do Clerk em localhost em Next.js, crie uma rota POST do App Router, exponha a porta 3000 com um túnel HTTPS, adicione a URL pública como um endpoint do webhook do Clerk e verifique cada solicitação com verifyWebhook() antes de sincronizar os dados do usuário. Mantenha a rota pública no middleware Clerk: o segredo de assinatura autentica a solicitação máquina a máquina, não uma sessão do navegador.

Quando os webhooks do Clerk são a ferramenta de sincronização certa

Clerk continua sendo a fonte de identidade da verdade. Um webhook é útil quando seu aplicativo precisa de uma projeção local para junções, pesquisas, relatórios, metadados de autorização ou integrações que não podem consultar o Clerk sob demanda. Eventos típicos incluem user.created, user.updated, e user.deleted.

Um webhook é assíncrono. O usuário pode concluir a inscrição antes que a projeção do seu banco de dados exista, as entregas podem ser repetidas e as atualizações podem chegar próximas umas das outras. Não faça da projeção sua única fonte para verificações de identidade pós-inscrição imediatas. Projete leituras para tolerar um pequeno atraso ou crie a linha do aplicativo explicitamente no fluxo do usuário e deixe os webhooks reconciliá-la.

Crie um endpoint público do Next.js App Router

Adicionar app/api/webhooks/clerk/route.ts. Corrente do escriturário guia de sincronização usa verifyWebhook de @clerk/nextjs/webhooks. O auxiliar consome a solicitação, verifica a assinatura dos Webhooks padrão e retorna dados de eventos digitados.

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

Por padrão, o auxiliar lê CLERK_WEBHOOK_SIGNING_SECRET. Escriturário verificar referência do Webhook também permite uma explícita signingSecret opção, mas a configuração do ambiente evita incorporar o segredo na origem.

Excluir a rota do webhook da proteção da sessão

As solicitações de webhook não carregam a sessão do Clerk do seu usuário. Se o middleware chamar auth.protect() para cada caminho de API, a entrega do Clerk recebe um redirecionamento, 401 ou 404, antes da execução da verificação de assinatura. Defina rotas de aplicativos protegidos explicitamente e deixe /api/webhooks/clerk público.

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

Escriturário guia de depuração do webhook chama especificamente a exclusão de rotas de webhook. Nas versões mais recentes do Next.js, a convenção pode usar proxy.ts; siga o guia da versão Clerk instalado em seu projeto. Público não significa confiável: verifyWebhook() é obrigatório antes de qualquer ação.

Conecte o Clerk ao localhost

  1. Correr npm run dev e verifique se o aplicativo Next.js está escutando na porta 3000.
  2. Correr npx portpreview 3000.
  3. No Painel do Clerk, crie um endpoint de webhook com https://your-subdomain.portpreview.dev/api/webhooks/clerk.
  4. Selecione apenas os eventos de usuário, sessão, organização ou email necessários.
  5. Copie o segredo de assinatura do endpoint para CLERK_WEBHOOK_SIGNING_SECRET em seu ambiente local e reinicie Next.js.
  6. Abra a guia Teste do endpoint, escolha user.createde selecione Enviar exemplo.
  7. Confirme se a tentativa foi bem-sucedida, sua rota local retornou 200 e a linha esperada do banco de dados foi alterada uma vez.

A URL do túnel deve permanecer ativa para exemplos subsequentes. Se mudar, atualize o endpoint. Não reutilize o segredo de assinatura de um endpoint de produção para testes locais; crie endpoints específicos do ambiente para que o histórico de rotação e auditoria permaneça claro.

Modele a sincronização do usuário com cuidado

Use o ID de usuário do Clerk como chave externa

Loja user_... em um único clerk_user_id coluna. Insira essa chave para tentar novamente user.created converge em vez de falhar. Mantenha sua própria chave primária interna se outras tabelas já fizerem referência a ela.

Escolha o e-mail principal deliberadamente

Os dados do usuário do Clerk contêm registros de endereço de e-mail e um ID de endereço de e-mail principal. Resolva o registro primário por ID em vez de usar o primeiro elemento da matriz. O e-mail pode mudar; não a use como chave estrangeira imutável.

Decida o que significa exclusão

UM user.deleted O evento pode conter menos dados do que criar ou atualizar. Use o ID para anonimizar, excluir de forma reversível ou iniciar um fluxo de trabalho de retenção de acordo com sua política. A exclusão cega em cascata pode destruir registros de faturamento ou auditoria que os regulamentos exigem que você preserve.

Não espelhe tudo

Persista apenas os campos que seu aplicativo precisa. Cada campo de perfil copiado cria uma obrigação de privacidade, retenção e desatualização. O Fetch raramente usava dados do Clerk sob demanda, em vez de duplicar uma carga inteira.

Torne as novas tentativas e pedidos inofensivos

Clerk documenta que uma resposta diferente de 2xx causa uma nova tentativa de evento. Registre o identificador da mensagem do webhook dos metadados ou cabeçalhos do webhook assinado como um recibo exclusivo antes de aplicar os efeitos. Se o seu SDK expõe IDs de Webhooks padrão em cabeçalhos, preserve-os junto com o tipo de evento e o carimbo de data/hora. Devolva 200 para uma duplicata preenchida.

Para atualizações do usuário, compare os carimbos de data/hora dos eventos ou use regras de última gravação que evitam que um evento mais antigo substitua os dados do perfil mais recentes. Para estado de alto valor, recupere o usuário atual do Clerk após a verificação e trate o webhook como um sinal para reconciliação. Mantenha a inserção do recibo, a atualização do usuário e o trabalho da caixa de saída em uma transação de banco de dados.

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

Esse padrão evita emails de boas-vindas duplicados e gravações parciais. Ver novas tentativas de webhook e idempotência para opções de esquema.

Solucionar problemas de entregas de Clerks locais

404, redirecionamento ou HTML em vez do seu manipulador

Verifique o caminho do arquivo, a exportação POST e o URL completo do túnel. Verifique o middleware e as reescritas de localidade. Um webhook não deve passar por um redirecionamento de login ou verificação de formulário CSRF. Teste a URL pública com um POST básico; espere a rejeição de assinatura de sua rota, não de uma estrutura 404.

verifyWebhook() sempre joga

Reinicie Next.js após definir o segredo de assinatura. Confirme se o segredo pertence a este endpoint e ambiente. Não analise, clone incorretamente ou consuma o corpo da solicitação antes de passá-lo para o auxiliar. Certifique-se de que o túnel preserve os cabeçalhos de assinatura dos Webhooks padrão.

O painel mostra novas tentativas

Veja a resposta exata da tentativa. Retorne 2xx somente após aceitação durável, mas mantenha o processamento abaixo do tempo limite do provedor. Migrações de banco de dados, erros de restrição exclusiva e chamada síncrona de um serviço indisponível são causas comuns.

As linhas estão duplicadas ou obsoletas

Adicione restrições exclusivas para o ID do Clerk e o ID da mensagem do webhook. Fazer user.created e user.updated ambos os upserts seguros e, em seguida, proteja-se contra carimbos de data/hora de eventos mais antigos. Repita o exemplo após cada correção para provar o tratamento duplicado.

Lista de verificação de segurança

  • Verifique cada solicitação com o ajudante do Clerk antes de registrar campos de carga útil ou gravar dados.
  • Mantenha a rota não autenticada pelo middleware de sessão do usuário, mas protegida pela assinatura do webhook.
  • Use segredos de endpoint separados para ambientes locais, de visualização, de preparo e de produção.
  • Desduplicar IDs de mensagens assinadas e restringir IDs de usuários de maneira exclusiva.
  • Edite e-mail, telefone, tokens, segredos e cargas completas de logs normais.
  • Opcionalmente, adicione controles de IP documentados pelo Clerk/Svix como defesa profunda, mas nunca substitua a verificação de assinatura pela filtragem de IP.
  • Limite a taxa de tráfego inválido, limite o tamanho do corpo e gire o segredo se ele aparecer nos logs ou no controle de origem.

Escriturário visão geral dos webhooks explica a verificação de assinatura e restrições opcionais de IP Svix. Para os fundamentos do corpo bruto e comportamento da rota do Next.js, continue com o Guia do webhook localhost Next.js.

Perguntas frequentes

Como testar um webhook do Clerk localmente no Next.js?
Crie uma rota POST do App Router, exponha a porta 3000 por um túnel HTTPS, adicione a rota pública completa no Clerk Dashboard, defina CLERK_WEBHOOK_SIGNING_SECRET e envie um exemplo pela aba de testes do endpoint.
A rota de webhook do Clerk deve ser protegida pelo clerkMiddleware?
Ela precisa ser acessível sem uma sessão de usuário, então não execute auth.protect() nela. Autentique a solicitação entre máquinas com verifyWebhook() e o segredo de assinatura do endpoint.
Preciso analisar o raw body antes de Clerk verifyWebhook()?
Não. Passe a Request original diretamente para verifyWebhook(). Não chame request.json() nem request.text() antes, pois o helper precisa do corpo assinado e dos headers.
Como lidar com novas tentativas de Clerk user.created?
Faça upsert pelo ID de usuário exclusivo do Clerk, elimine duplicidades pelo ID da mensagem webhook assinada e retorne 200 para eventos já processados. Coloque efeitos colaterais não essenciais em uma fila para evitar e-mails duplicados.