Kanorio

API 레퍼런스

Webhook

Kanorio에서 서명된 이벤트 알림을 수신하고, 검증하며, 각 전달을 한 번만 처리하고 프론트엔드 캐시를 갱신하는 방법을 알아봅니다.

Webhook은 사이트에서 게시물 발행, 폼 제출, 결제 완료 등 특정 이벤트가 발생했을 때 시스템에 알림을 보내는 기능입니다. Kanorio는 이벤트를 구독하는 각 엔드포인트로 JSON 본문을 포함한 HTTPS POST 요청을 보냅니다.

엔드포인트 추가

사이트 설정 › Webhook에서 엔드포인트를 추가하거나, Management API의 POST /sites/{id}/webhooks를 사용하여 추가할 수 있습니다. 각 엔드포인트에 대해 다음을 설정합니다.

  • 이벤트: 이벤트 참조에서 제품별로 그룹화된 모든 이벤트와 필요한 권한을 확인할 수 있습니다.
  • 필터: 컬렉션 이벤트는 특정 컬렉션으로, 폼 이벤트는 특정 폼으로 제한할 수 있습니다.
  • 서명 시크릿: 각 엔드포인트는 서명 검증을 위한 고유한 whsec_ 시크릿을 가집니다.

사이트당 엔드포인트 개수는 플랜에 따라 다릅니다. 요금 제한 및 할당량을 확인하세요.

요청

헤더값
Content-Typeapplication/json
X-Kanorio-Eventcollections.item.published와 같은 이벤트 키
X-Kanorio-Delivery전송 ID. 재시도나 수동 재전송 시에도 동일하게 유지됩니다
X-Kanorio-Signaturet=<unix seconds>,v1=<hex signature>
User-AgentKanorio-Notifications/1.0

모든 이벤트는 동일한 엔벨로프를 사용합니다. 이벤트별 필드는 data 내에 포함됩니다.

{
  "version": "1",
  "id": "3f9c2a8b1e4d7c0f9a2b3c4d5e6f7a8b",
  "event": "collections.item.published",
  "occurredAt": "2026-11-02T08:30:00.000Z",
  "websiteId": "ws_example123",
  "title": "Collection item published: Sourdough Guide",
  "summary": "blog/sourdough-guide (published)",
  "data": {
    "collectionKey": "blog",
    "itemId": "item_01",
    "slug": "sourdough-guide",
    "status": "published",
    "publicVersion": "1729500000000",
    "cacheTags": ["collection:blog", "item:item_01", "seo", "search"]
  },
  "actionUrl": "https://app.kanorio.com/data/blog",
  "operationId": null,
  "principal": { "kind": "USER", "id": "usr_example01" }
}
필드참고
idX-Kanorio-Delivery와 동일한 전송 ID. 중복 제거에 사용합니다
eventX-Kanorio-Event와 동일
data이벤트별 데이터. 콘텐츠 이벤트에는 locale, publicVersion, cacheTags가 포함됩니다
operationId폼 제출, 결제, 환불, 접근 권한 부여 등 추적되는 작업과 관련된 이벤트에 설정됩니다. GET /operations/{id}로 조회할 수 있으며, 해당하지 않으면 null입니다
principal변경을 시작한 주체: USER, SERVICE_ACCOUNT, CUSTOMER, SYSTEM, VISITOR 중 하나. 알 수 없는 경우 null이며, null을 다른 값으로 간주하지 마십시오

서명 검증

엔드포인트의 시크릿을 사용하여 {t}.{rawBody}의 HMAC-SHA256을 계산한 뒤, 각 v1 값과 상수 시간(constant time) 비교를 수행합니다. t가 현재 시간과 5분 이상 차이 나는 요청은 거부하십시오. JSON 파싱 전의 원본 요청 본문을 반드시 사용해야 합니다.

서명 시크릿을 교체하는 동안 Kanorio는 24 hours 동안 이전 시크릿과 새 시크릿 모두로 서명하므로, 헤더에 두 개의 v1 값이 포함됩니다. 둘 중 하나라도 일치하면 요청을 수락하십시오.

SDK를 사용하면 이 과정을 자동으로 처리할 수 있습니다.

import { verifyWebhookSignature } from "@kanorio/sdk";

export async function POST(request: Request) {
  const rawBody = await request.text();
  const valid = await verifyWebhookSignature({
    rawBody,
    signatureHeader: request.headers.get("x-kanorio-signature"),
    signingSecret: process.env.KANORIO_WEBHOOK_SECRET!,
  });
  if (!valid) return new Response("Invalid signature", { status: 401 });

  const event = JSON.parse(rawBody);
  // handle event.event and event.data
  return new Response(null, { status: 200 });
}

SDK를 사용하지 않는 Node.js 환경에서의 구현 예시는 다음과 같습니다.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyKanorioWebhook(rawBody, header, secret) {
  if (!header || !secret) return false;
  const parts = header.split(",").map((part) => part.split("="));
  const t = parts.find(([key]) => key === "t")?.[1];
  const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value);
  if (!t || signatures.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"));
  return signatures.some((v1) => {
    const actual = Buffer.from(v1);
    return actual.length === expected.length && timingSafeEqual(actual, expected);
  });
}

응답 및 재시도

  • 8 seconds 이내에 2xx 응답을 반환하십시오. 처리에 시간이 더 걸린다면 이벤트를 큐에 넣고 먼저 응답을 보내십시오.
  • Kanorio는 리다이렉트를 따르지 않습니다. 3xx는 실패로 간주되므로, 프레임워크가 기대하는 후행 슬래시를 포함하여 최종 URL을 등록하십시오.
  • 타임아웃, 네트워크 오류, 429, 5xx 오류 발생 시 1m, 5m, 30m, 2h, 8h, 1d, 1d 간격으로 7 회 재시도합니다. 각 재시도 시 새로운 타임스탬프로 서명이 다시 생성됩니다.
  • 410 Gone 응답은 엔드포인트를 즉시 비활성화합니다. 3 회 연속으로 전달에 실패하면 엔드포인트가 일시 중지되고 사이트 팀에 알림이 전송됩니다.
  • 전달 페이로드는 7 days 동안 보관되므로, 해당 기간 내에는 관리 화면에서 전달을 다시 보낼 수 있습니다.

각 전달을 한 번만 처리

재시도 및 수동 재전송으로 인해 동일한 이벤트가 여러 번 전달될 수 있습니다. 처리한 X-Kanorio-Delivery ID를 저장해 두고, 동일한 ID가 다시 들어오면 작업을 수행하지 않고 200을 반환하십시오.

프론트엔드 캐시 갱신

모든 Content Delivery API 응답은 X-Kanorio-Cache-Tags에 의존성을 나열하며, 모든 콘텐츠 이벤트는 data.cacheTags에 동일한 태그를 사용하여 변경 사항을 나열합니다. 프레임워크의 캐시에 응답 태그를 지정하고 이벤트 태그를 재검증하면, 변경된 콘텐츠를 사용하는 페이지만 몇 초 내에 새로 고침되고 나머지는 캐시된 상태로 유지됩니다.

태그대상 응답전송하는 이벤트
site:<id>사이트의 모든 응답content.site.published, localization.locale.* (언어 전환 및 hreflang은 모든 페이지에 포함됨)
locale:<code>보조 로케일의 모든 응답localization.job.completed
page:<id>, item:<id>, product:<id>, form:<id>모든 로케일에서 해당 레코드의 응답 및 이를 포함하는 응답 (브레드크럼 상위 항목, 확장된 참조)레코드 자체의 이벤트. 기본 로케일이 변경되면 모든 로케일이 새로고침됩니다 (번역되지 않은 필드는 기본 텍스트로 대체됨)
page:<id>@<code>, item:<id>@<code>, …해당 로케일의 응답만content.translation.published / deleted: 해당 로케일만 새로고침됨
pages, collection:<key>, products (및 @<code>)목록 응답만해당 목록 내 레코드의 이벤트
collection:<key>:items컬렉션의 모든 단일 항목 응답collections.schema.changed 및 항목별로 나열하기 너무 큰 대량 변경
globals, header:<key>, footer헤더 및 푸터 응답content.site.published, 글로벌 섹션 번역
search, seo, redirects사이트 검색, SEO 파일 (/seo/*), 리다이렉트 테이블이를 변경하는 콘텐츠 이벤트; content.page.updated는 페이지 이름이 변경될 때 redirects를 추가함

태그는 로케일 코드를 사용하며 URL 접두사는 사용하지 마십시오(소유자가 접두사를 변경할 수 있음). revalidateTag는 전체 문자열이 일치해야 하므로 이벤트에는 항상 구체적인 태그가 나열됩니다. 단일 레코드 이벤트에는 site:<id>가 포함되지 않습니다. 사이트 태그로만 캐시를 관리하려면 content.site.published를 구독하거나, 더 나은 방법으로 각 페치(fetch)에 응답 자체의 태그를 지정하십시오.

Next.js에서는 createRevalidateHandler를 사용하여 서명 검증과 태그 재검증을 한 줄로 처리할 수 있습니다. 모든 태그 앞에 kanorio: 접두사가 붙으므로 Kanorio 태그와 사용자의 태그가 충돌하지 않습니다. frameworkCacheTags 또는 cacheTag 빌더를 통해 페치에 태그를 지정하여 양쪽이 일치하도록 하십시오(원본 태그를 사용하려면 tagPrefix: ""를 전달하십시오).

// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { createRevalidateHandler } from "@kanorio/sdk";

export const POST = createRevalidateHandler({
  signingSecret: process.env.KANORIO_WEBHOOK_SECRET!,
  revalidateTag: (tag) => revalidateTag(tag),
});

정적 빌드 사이트의 경우, Webhook을 호스트의 배포 훅(deploy hook)으로 지정하고 콘텐츠 이벤트를 구독하십시오.