웹훅

웹훅

프로젝트에서 인증 이벤트가 발생할 때 실시간 HTTP 알림을 수신하세요. 사용자가 가입하거나, 로그인하거나, 차단될 때마다 데이터베이스에 동기화하거나, 환영 이메일을 보내거나, 워크플로를 트리거하세요.

웹훅이란?

웹훅은 실시간 이벤트 알림을 수신하기 위해 서버에 등록하는 HTTP 콜백입니다. Authon에 폴링하여 사용자 삭제나 차단 여부를 확인하는 대신, Authon이 이벤트 발생 즉시 등록된 URL로 POST 요청을 능동적으로 전달합니다.

text
Authon 대시보드에서 사용자 삭제
  → Authon이 POST /webhooks/authon 전송
  → 서버에서 이벤트를 수신하여 DB에서 사용자 제거

웹훅 작동 방식

Authon 프로젝트에서 이벤트가 발생하면, API가 JSON 페이로드와 함께 등록된 엔드포인트로 POST 요청을 보냅니다. 각 요청에는 v1= 접두사가 붙은 X-Authon-Signature 헤더가 포함됩니다 — 웹훅 서명 시크릿으로 timestamp.body를 HMAC-SHA256으로 서명한 값입니다. 이벤트를 처리하기 전에 항상 이 서명을 검증하세요.

1
Authon에서 이벤트 발생 (예: 사용자 회원가입)
2
Authon이 웹훅 시크릿으로 HMAC-SHA256 서명
3
Authon이 엔드포인트 URL로 POST 요청 전송
4
서버에서 서명 검증 후 이벤트 처리
5
서버가 수신 확인을 위해 2xx로 응답

웹훅 설정

  1. 1
    이동: 대시보드 → 웹훅 → 웹훅 추가
  2. 2
    입력: 엔드포인트 URL — HTTPS로 공개 접근 가능해야 함
  3. 3
    선택: 수신할 이벤트
  4. 4
    복사: 서명 시크릿 — 한 번만 표시되므로 안전하게 보관
  5. 5
    설정: 서버 환경에 AUTHON_WEBHOOK_SECRET 추가

이벤트 유형

이벤트트리거
user.created새 사용자가 등록을 완료함
user.updated사용자 프로필이 업데이트됨
user.deleted사용자 계정이 영구 삭제됨
user.signin사용자가 로그인함 (이메일 또는 OAuth)
user.signout사용자가 로그아웃함
user.banned관리자가 사용자를 차단함
user.unbanned차단된 사용자가 복원됨
session.created새 세션이 생성됨 (로그인, 토큰 갱신)
session.revoked세션이 취소됨 (로그아웃, 관리자 강제 취소)

페이로드 형식

모든 웹훅 페이로드는 일관된 최상위 구조를 가진 JSON 객체입니다. data 필드에 이벤트별 세부 정보가 포함됩니다.

json
{
  "event": "user.created",
  "data": {
    "user": {
      "id": "usr_abc123",
      "email": "user@example.com",
      "displayName": "Jane Doe",
      "emailVerified": false,
      "isBanned": false,
      "publicMetadata": null,
      "createdAt": "2026-01-15T10:30:00.000Z",
      "updatedAt": "2026-01-15T10:30:00.000Z"
    }
  },
  "timestamp": "2026-01-15T10:30:00.000Z"
}

session.*이벤트의 경우, data필드에 session객체(id, ipAddress, userAgent)도 포함됩니다.

서명 검증

각 웹훅 요청에는 X-Authon-Signature 형식의 v1=<hex_digest>. 헤더가 포함됩니다. 서명은 timestamp.rawBody 에 대해 웹훅 시크릿으로 HMAC-SHA256을 계산한 값입니다. 타이밍 안전 비교로 검증하세요.

헤더형식설명
X-Authon-Signaturev1={hmac_sha256}요청 출처 검증을 위한 HMAC-SHA256 서명
X-Authon-TimestampISO 8601이벤트 전송 시각 (ISO 8601)
X-Authon-Eventuser.created이벤트 유형 (예: user.created, session.revoked)
항상 원시 본문 버퍼 를 사용하여 검증하세요. Express의 json() 미들웨어는 본문을 재직렬화하여 공백이 변경되고 서명이 깨질 수 있습니다.

Node.js 예제

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

const router = express.Router();

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

  const expectedBuf = Buffer.from(expected, "hex");
  const actualBuf = Buffer.from(actual, "hex");

  return (
    expectedBuf.length === actualBuf.length &&
    timingSafeEqual(expectedBuf, actualBuf)
  );
}

router.post(
  "/webhooks/authon",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-authon-signature"] as string;
    const timestamp = req.headers["x-authon-timestamp"] as string; // ISO 8601
    const eventType = req.headers["x-authon-event"] as string;
    const webhookSecret = process.env.AUTHON_WEBHOOK_SECRET!;

    if (!verifySignature(req.body, signature, webhookSecret, timestamp)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

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

    switch (eventType) {
      case "user.created":
        // Sync new user to your database
        break;
      case "user.deleted":
        // Remove user from your database
        break;
      case "user.updated":
        // Update user data
        break;
      case "user.banned":
        // Revoke app-level access
        break;
    }

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

export default router;

SDK 사용

@authon/node SDK는 타이밍 안전 비교를 처리하는 내장 검증 헬퍼를 제공합니다:

ts
import { AuthonBackend } from "@authon/node";

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

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

    let event: Record<string, unknown>;
    try {
      event = authon.webhooks.verify(
        req.body,
        signature,
        timestamp,
        process.env.AUTHON_WEBHOOK_SECRET!,
      );
    } catch {
      return res.status(401).json({ error: "Invalid signature" });
    }

    console.log("Verified event:", event.event);
    res.json({ received: true });
  }
);

재시도 정책

엔드포인트가 비-2xx 상태 코드를 반환하거나 10초내에 응답하지 않으면, Authon은 지수 백오프로 전송을 재시도합니다. 최대 3회 시도합니다.

시도지연누적 시간
최초0s
1차 재시도1 second~1s
2차 재시도2 seconds~3s

활용 사례

데이터베이스에 사용자 동기화
앱의 사용자 테이블을 Authon과 동기화 상태로 유지하세요. user.created, user.updated, user.deleted를 수신하여 변경사항을 자동으로 반영합니다.
환영 이메일 발송
user.created가 발생하면 환영 이메일이나 온보딩 시퀀스를 트리거하세요. 이벤트 페이로드에서 사용자 이메일과 표시 이름을 활용하세요.
실시간 차단 적용
user.banned가 발생하면 앱 레벨의 액세스 토큰이나 캐시 항목을 즉시 무효화하여 사용자가 서비스를 계속 이용할 수 없도록 합니다.

모범 사례

항상 서명 검증
서명 검증을 건너뛰지 마세요. 누구나 엔드포인트에 POST할 수 있습니다 — 검증은 요청이 Authon에서 왔음을 증명합니다.
원시 본문 파싱 사용
서명 검증에는 요청 본문의 정확한 원시 바이트가 필요합니다. 웹훅 라우트에서 JSON 미들웨어를 피하세요.
빠르게 응답, 비동기 처리
즉시 200을 반환하고 이벤트를 비동기적으로 처리하세요. 엔드포인트가 10초 이상 걸리면 Authon이 재시도합니다.
핸들러 멱등성 유지
재시도로 인해 동일한 이벤트를 여러 번 수신할 수 있습니다. 이벤트 timestamp로 중복 제거하세요 — 처리된 이벤트를 DB에 저장하세요.
주기적 시크릿 교체
90일마다 대시보드에서 새 서명 시크릿을 생성하세요. 다운타임 없이 배포에서 AUTHON_WEBHOOK_SECRET을 업데이트하세요.
Authon — 범용 인증 플랫폼