O que o Clerk fornece
Um valor:userId, o id user_… que os próprios webhooks do Clerk citam. É esse
o external_id.
lib/clerk.ts
auth() lê a sessão que o middleware do Clerk já verificou. Decodificar o cookie
__session na mão e assinar o sub dele seria assinar um valor que o navegador
escolheu.
auth() exige clerkMiddleware() montado, senão ele lança em vez de
responder userId nulo. Essa é a falha certa: “não consegui saber” nunca pode ser
lido como “está deslogado”.A metade do servidor
app/api/userkit-boot/route.ts
A metade do cliente
getState().verified só é true quando o HMAC conferiu. Logado no Clerk com
verified: false significa que o segredo é do outro ambiente ou que a mensagem
assinada não era exatamente o external_id.
Configuração
A organização do Clerk não é a identidade
orgId é uma segunda dimensão, e assinar userId + orgId numa mensagem só
transformaria uma pessoa em dois contatos. O objeto de time do UserKit é o
customer, resolvido por requisição a partir do
X-Customer-Id — mapeie a organização do Clerk nele, não no external_id.
O que não viaja
Um usuário do Clerk carrega endereços de e-mail verificados. Eles não viram identidade no UserKit: um e-mail enviado pelo/v1/boot é guardado como atributo,
não resolve para um contato existente e não vira aresta de identidade. O HMAC prova
o external_id e só ele.
Para provar um endereço, mande um magic link ou um código por
e-mail — os dois fluxos que chegam nele.
O exemplo completo
Um app Next executável com esses arquivos, um
.env.example e as partes que esta
página deixa de fora.