이 가이드는 Webhook 수신기를 구현하는 엔지니어를 위한 것입니다. X-Kanorio-Signature 서명을 검증하고, 멱등성을 처리하며, cacheTags를 사용하여 외부 프론트엔드 캐시 업데이트를 트리거하는 방법에 중점을 둡니다.
collectionKeys 또는 formIds별 필터링 지원), 또는 전달 로그 확인에 대해서는 Webhook 설정 및 관리를 참조하십시오.data 스키마(SSOT): 구독 가능한 모든 이벤트 코드, 필수 권한, data JSON 예시는 Webhook 참조 페이지의 시스템 이벤트 레지스트리에서 자동으로 생성됩니다.모든 Webhook 알림은 HTTPS POST를 통해 전송되며, Content-Type: application/json 및 다음 헤더가 포함됩니다.
| 헤더 | 설명 |
|---|---|
X-Kanorio-Event | 이벤트 코드(예: collections.item.published, ec.order.paid). JSON 최상위의 event 필드와 동일합니다 |
X-Kanorio-Delivery | 고유 알림 ID. JSON 최상위의 id 필드와 동일합니다. 자동 재시도나 대시보드에서 수동으로 "재전송"을 수행해도 변경되지 않습니다 |
X-Kanorio-Signature | HMAC-SHA256 서명. t=timestamp,v1=signature 형식입니다. 서명 키의 24시간 교체 기간 동안에는 두 개의 v1= 값이 포함됩니다 |
User-Agent | Kanorio-Notifications/1.0 |
2xx 반환:
모든 2xx 응답(예: 200 OK, 202 Accepted)은 성공적인 전달로 간주됩니다. 처리 과정(외부 데이터베이스 동기화 또는 장시간 실행되는 작업 트리거 등)이 느린 경우, 서명 검증 및 큐잉 직후에 200을 반환하고 백그라운드에서 비동기적으로 처리하십시오.3xx 리다이렉트 금지:
Kanorio는 Webhook 전송 시 어떠한 3xx 리다이렉트도 따르지 않습니다(301, 302, 307, 308 수신 시 전달 실패로 처리). 대시보드에 구성된 URL이 최종 엔드포인트인지 확인하십시오(프레임워크 수준에서 리다이렉트를 유발할 수 있는 후행 슬래시 /에 주의하십시오).429 또는 5xx 응답은 자동 지수 백오프 재시도를 트리거합니다. 410 Gone은 즉시 엔드포인트를 비활성화합니다. 실패가 제한을 초과하면 시스템이 자동으로 엔드포인트를 중단하고 팀에 알립니다.X-Kanorio-Delivery를 통한 멱등성:
네트워크 재시도나 수동 재전송으로 인해 동일한 알림이 두 번 이상 도착할 수 있습니다. X-Kanorio-Delivery(또는 최상위 JSON id)를 멱등성을 위한 고유 키로 사용하십시오. 이미 처리된 경우 즉시 200 OK를 반환하십시오.X-Kanorio-Signature)수신기를 구축할 때는 항상 엔드포인트의 "서명 시크릿"(whsec_...)을 사용하여 X-Kanorio-Signature를 검증하고, 요청이 Kanorio에서 시작되었으며 변조되지 않았음을 확인하십시오.
X-Kanorio-Signature를 쉼표,로 분할합니다. t= 뒤의 Unix 타임스탬프와 v1= 뒤의 모든 16진수 서명 문자열을 추출합니다(24시간 키 교체 중첩 기간 동안 두 개의 v1 값이 존재합니다).t와 서버의 현재 시간을 비교합니다. 5분(300초) 이상 차이가 나는 요청은 거부하십시오(재시도할 때마다 현재 시간을 사용하여 t와 서명이 다시 계산됩니다).t, 마침표 ., 그리고 파싱되지 않은 원본 요청 본문을 연결하여 {t}.{rawBody}를 만듭니다.whsec_... 시크릿을 사용하여 {t}.{rawBody}의 HMAC-SHA256 16진수 다이제스트를 계산하고 crypto.timingSafeEqual을 사용하여 임의의 v1과 비교합니다.import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyKanorioWebhook(rawBody, signatureHeader, secret) { if (!signatureHeader || !secret) return false; const parts = signatureHeader.split(",").map((part) => part.split("=")); const t = parts.find(([key]) => key === "t")?.[1]; const signatures = parts .filter(([key]) => key === "v1") .map(([, value]) => value) .filter(Boolean); if (!t || signatures.length === 0) return false; const timestampSec = Number(t); if (!Number.isFinite(timestampSec) || Math.abs(Date.now() / 1000 - timestampSec) > 300) { return false; } const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const expectedBuf = Buffer.from(expected, "utf8"); return signatures.some((v1) => { const v1Buf = Buffer.from(v1, "utf8"); return v1Buf.length === expectedBuf.length && timingSafeEqual(expectedBuf, v1Buf); }); }
cacheTags 업데이트모든 Webhook 이벤트는 동일한 최상위 JSON 구조를 공유하며, 이벤트별 필드는 data 내부에 있습니다.
{ "version": "1", "id": "3f9c2a8b1e4d7c0f9a2b3c4d5e6f7a8b", "event": "collections.item.published", "occurredAt": "2026-10-21T08:30:00.000Z", "websiteId": "cmk1examplewebsiteid", "title": "Collection item published: Sourdough Guide", "summary": "blog/sourdough-guide (published)", "data": { "collectionId": "col_01", "collectionKey": "blog", "itemId": "item_01", "slug": "sourdough-guide", "status": "published", "publicVersion": "1729500000000", "cacheTags": ["site:cmk1examplewebsiteid", "collection:blog", "item:item_01"] }, "actionUrl": "https://app.kanorio.com/data/blog" }
data.cacheTags를 사용한 온디맨드 캐시 업데이트(ISR)모든 콘텐츠 및 컬렉션 변경 이벤트(collections.item.*, collections.items.bulkChanged, collections.schema.changed, content.page.*, content.site.published)는 data 객체에 publicVersion과 cacheTags를 포함합니다(콘텐츠 전달 API 응답의 X-Kanorio-Cache-Tags 헤더와 일치).
content.page.updated에 previousSlug가 포함되고 cacheTags에 redirects가 추가되어, 외부 프론트엔드가 이전 경로와 리다이렉트 캐시를 모두 무효화할 수 있습니다.payload.data.cacheTags의 각 태그에 대해 revalidateTag(tag)를 호출하여 영향을 받는 페이지를 몇 초 내에 정확하게 업데이트할 수 있습니다(전체 예시는 참조).시스템 이벤트 레지스트리에서 자동으로 생성되는 Webhook 참조 페이지를 방문하십시오. 구독 가능한 모든 이벤트, 트리거 조건, 필터 지원, data 예시가 나열되어 있습니다.