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
- Correr
npm run deve verifique se o aplicativo Next.js está escutando na porta 3000. - Correr
npx portpreview 3000. - No Painel do Clerk, crie um endpoint de webhook com
https://your-subdomain.portpreview.dev/api/webhooks/clerk. - Selecione apenas os eventos de usuário, sessão, organização ou email necessários.
- Copie o segredo de assinatura do endpoint para
CLERK_WEBHOOK_SIGNING_SECRETem seu ambiente local e reinicie Next.js. - Abra a guia Teste do endpoint, escolha
user.createde selecione Enviar exemplo. - 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.
