Webhooks

Sincronizando Usuários com Webhooks

Quando você exclui ou bane um usuário no Authon, o banco de dados do seu app não sabe disso automaticamente. Os webhooks resolvem isso entregando eventos em tempo real ao seu backend.

O Problema

Sem webhooks, seu app não tem como saber quando o estado do usuário no Authon muda. Isso leva a dados desatualizados — usuários excluídos ainda aparecendo, usuários banidos ainda acessando funcionalidades.

1
Administrador exclui um usuário no dashboard do Authon
2
O registro do usuário é removido do Authon
3
O banco de dados do seu app ainda tem a linha desatualizada do usuário — nenhuma sincronização ocorreu

A Solução

Inscreva-se em eventos user.*. Quando eles forem disparados, atualize seu banco de dados para corresponder ao estado do Authon.

1
Registre um endpoint de webhook no dashboard do Authon
2
Inscreva-se em user.created, user.updated, user.deleted, user.banned, user.unbanned
3
No seu handler, faça upsert ou exclua a linha correspondente no seu banco de dados

Exemplo Express + Prisma

Um handler de webhook completo que mantém uma tabela de usuários gerenciada pelo Prisma sincronizada com o Authon.

routes/webhooks.ts
import express from "express";
import { createHmac, timingSafeEqual } from "crypto";
import { PrismaClient } from "@prisma/client";

const app = express();
const prisma = new PrismaClient();

function verifyWebhook(
  rawBody: Buffer,
  signature: string,
  timestamp: string,
  secret: string,
): boolean {
  const payload = `${timestamp}.${rawBody.toString()}`;
  const expected = createHmac("sha256", secret).update(payload).digest("hex");
  const actual = signature.replace("v1=", "");
  return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(actual, "hex"));
}

app.post(
  "/webhooks/authon",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const sig = req.headers["x-authon-signature"] as string;
    const ts = req.headers["x-authon-timestamp"] as string; // ISO 8601

    if (!verifyWebhook(req.body, sig, ts, process.env.AUTHON_WEBHOOK_SECRET!)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const { event, data } = JSON.parse(req.body.toString());

    switch (event) {
      case "user.created":
      case "user.updated":
        await prisma.user.upsert({
          where: { authonId: data.user.id },
          create: {
            authonId: data.user.id,
            email: data.user.email,
            name: data.user.displayName,
            avatarUrl: data.user.avatarUrl,
          },
          update: {
            email: data.user.email,
            name: data.user.displayName,
            avatarUrl: data.user.avatarUrl,
          },
        });
        break;

      case "user.deleted":
        await prisma.user.update({
          where: { authonId: data.user.id },
          data: { deletedAt: new Date() }, // soft-delete
        }).catch(() => {});
        break;

      case "user.banned":
        await prisma.user.update({
          where: { authonId: data.user.id },
          data: { suspended: true, suspendedAt: new Date() },
        });
        break;

      case "user.unbanned":
        await prisma.user.update({
          where: { authonId: data.user.id },
          data: { suspended: false, suspendedAt: null },
        });
        break;
    }

    res.json({ received: true });
  }
);

Exemplo Next.js App Router

Uma rota de API Next.js que trata eventos de sincronização de usuários usando o App Router.

app/api/webhooks/authon/route.ts
import { createHmac, timingSafeEqual } from "crypto";
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/db";

export async function POST(req: NextRequest) {
  const rawBody = await req.text();
  const signature = req.headers.get("x-authon-signature")!;
  const timestamp = req.headers.get("x-authon-timestamp")!; // ISO 8601

  const payload = `${timestamp}.${rawBody}`;
  const expected = createHmac("sha256", process.env.AUTHON_WEBHOOK_SECRET!)
    .update(payload)
    .digest("hex");
  const actual = signature.replace("v1=", "");

  if (!timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(actual, "hex"))) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  const { event, data } = JSON.parse(rawBody);

  if (event === "user.created" || event === "user.updated") {
    await db.user.upsert({
      where: { authonId: data.user.id },
      create: {
        authonId: data.user.id,
        email: data.user.email,
        name: data.user.displayName,
      },
      update: {
        email: data.user.email,
        name: data.user.displayName,
      },
    });
  } else if (event === "user.deleted") {
    await db.user.update({
      where: { authonId: data.user.id },
      data: { deletedAt: new Date() }, // soft-delete
    }).catch(() => {});
  }

  return NextResponse.json({ received: true });
}

Casos Extremos

E se o webhook falhar?
O Authon tenta novamente até 3 vezes com backoff exponencial (1s, 2s). Se todas as tentativas falharem, o evento é marcado como falho no dashboard, onde você pode reproduzi-lo manualmente.
Ordenação de eventos
Os eventos são entregues em ordem, mas podem chegar fora de ordem durante os retries. Use o campo timestamp para detectar e ignorar atualizações desatualizadas quando a ordenação for importante.
Migração inicial
Os webhooks capturam apenas eventos futuros. Para a sincronização inicial de usuários existentes, use o método users.list() da API do Authon para paginar por todos os usuários e popular seu banco de dados.

Sincronização Reversa (App → Authon)

Os webhooks tratam a direção Authon → App. Para o reverso — criar ou atualizar usuários no Authon a partir do seu app — use a API Backend com sua chave secreta.

lib/authon-sync.ts
import { AuthonBackend } from "@authon/node";

const authon = new AuthonBackend(process.env.AUTHON_SECRET_KEY!);

// Create a user in Authon from your app (e.g. after admin invite)
export async function createAuthonUser(email: string, name: string) {
  return authon.users.create({
    email,
    displayName: name,
    emailVerified: true,
  });
}

// Update user metadata in Authon when your app data changes
export async function syncMetadataToAuthon(
  authonId: string,
  publicMetadata: Record<string, unknown>,
) {
  return authon.users.update(authonId, { publicMetadata });
}

// Bulk import existing users from your app into Authon
export async function bulkImportToAuthon(
  users: Array<{ email: string; name: string }>,
) {
  for (const user of users) {
    await authon.users.create({
      email: user.email,
      displayName: user.name,
      emailVerified: true,
    });
  }
}

Todas as operações de gerenciamento de usuários estão disponíveis via endpoint da REST API POST /v1/backend/users.

Authon — Plataforma universal de autenticação