웹훅 연동
SORI API 웹훅은 SORI 플랫폼에서 발생하는 주요 이벤트를 사내 시스템에 알려 줍니다. 엔드포인트를 연결하면 캠페인 활동, 디바이스 인증, 관리 작업을 거의 실시간으로 처리할 수 있습니다.
개요
SORI API는 중요한 이벤트가 발생하는 즉시 구조화된 JSON 페이로드를 전송하는 웹훅 기능을 제공합니다. 웹훅은 SORI 콘솔에서 설정하며, 다음과 같은 카테고리를 지원합니다.
- 캠페인 이벤트(`campaign.*`) 디바이스에서 캠페인이 인식되거나 사용자가 액션 URL로 이동하면 트리거됩니다.
- 인증 이벤트(`authentication`) 디바이스가 SORI 인증 플로우를 완료하면 발생합니다.
- 관리 이벤트(`admin.material.*`) 콘솔에서 소재를 생성·수정·삭제할 때 전송되는 선택적 알림입니다.
실시간 알림을 사용하면 다음과 같은 이점을 얻을 수 있습니다.
- 즉시 분석 활동이 발생하는 즉시 캠페인 지표를 추적·분석합니다.
- 인증 감사 디바이스 로그인과 사내 세션 기록을 대조합니다.
- 사기 탐지 비정상적인 패턴을 즉시 감지합니다.
- 라이브 대시보드 최신 데이터로 대시보드를 갱신합니다.
웹훅 설정
SORI에서는 두 가지 방식으로 웹훅 엔드포인트를 구성할 수 있습니다.
전역 웹훅 URL
- 설정 섹션에서 구성합니다.
- 기본적으로 모든 캠페인에 적용됩니다.
- 캠페인에서 별도로 지정하지 않았을 때 사용되는 기본 엔드포인트 역할입니다.
캠페인별 웹훅 URL
- 각 캠페인의 고급 설정에서 구성합니다.
- 해당 캠페인에서는 전역 URL보다 우선합니다.
- 캠페인마다 다른 처리가 필요할 때 유용합니다.
이벤트 구독 (설정 → Webhooks)
계정 설정에서 어떤 이벤트 카테고리를 수신할지 선택할 수 있습니다.
- 캠페인 이벤트: 인프레션과 클릭 활동을 포함한
campaign.*알림을 구독합니다. - 인증 이벤트: 디바이스 로그인 시 발생하는
authentication이벤트를 구독합니다. - 관리 이벤트: 콘솔에서 발생하는
admin.material.*알림을 구독합니다.
새 계정은 캠페인 이벤트와 인증 이벤트가 기본으로 활성화되어 있습니다. 관리자는 언제든지 체크박스를 조정할 수 있으며, 변경 사항은 이후 웹훅 전송에 즉시 반영됩니다.
웹훅 설정 절차
- HTTPS를 사용하는 웹훅 수신 엔드포인트를 준비합니다.
- 다음 중 하나를 구성합니다.
- 설정 화면에서 전역 웹훅 URL을 등록합니다.
- 각 캠페인 설정에서 개별 웹훅 URL을 등록합니다.
- 설정 → Webhooks 에서 수신할 이벤트 카테고리를 선택합니다.
- JSON 페이로드가 포함된 POST 요청을 수락하고 5초 이내에 응답하도록 엔드포인트를 구현합니다.
이벤트 유형
각 웹훅 페이로드에는 표준화된 event 필드가 포함됩니다. 신규 연동에서는 이 값을 기준으로 분기하는 것을 권장합니다.
캠페인 인프레션 이벤트(campaign.impression)
캠페인이 사용자 디바이스에 표시될 때 전송됩니다. 캠페인 이벤트 를 활성화하면 수신할 수 있습니다.
캠페인 클릭 이벤트(campaign.click)
사용자가 캠페인을 탭하고 액션 URL로 이동할 때 전송됩니다. 캠페인 이벤트 를 활성화하면 수신할 수 있습니다.
인증 이벤트(authentication)
디바이스가 SORI 인증을 완료한 뒤 전송됩니다. 인증 요청에 포함된 쿼리 파라미터가 metadata 에 담기므로, 사내 세션 ID 등과 매칭할 수 있습니다.
웹훅 이벤트 처리
웹훅을 수신하면 이벤트 세부 정보가 담긴 JSON 페이로드가 전달됩니다. 아래는 각 이벤트 유형의 예시입니다. null 로 표시된 필드는 값이 없을 경우 생략될 수 있습니다.
페이로드 스키마
별도 언급이 없는 한 캠페인 웹훅에는 아래 필드가 포함됩니다.
| 필드 | 타입 | 설명 |
|---|---|---|
event | string | campaign.impression, campaign.click, authentication 과 같은 정규 이벤트 이름입니다. |
event_type | string | 과거 호환성을 위한 event 복제 필드입니다. |
account_id | string | 캠페인을 소유한 SORI 계정 식별자입니다. |
created_at | ISO 8601 string | 이벤트 객체가 서버에서 생성된 시각입니다. |
timestamp | ISO 8601 string | 전송 직전에 추가되는 타임스탬프로, 멱등성 검사에 활용할 수 있습니다. |
activity_id | string | 특정 노출/클릭을 구분하는 고유 ID입니다(캠페인 이벤트). |
device_id | string | 이벤트를 발생시킨 디바이스 또는 세션입니다. |
platform | string | SDK가 보고한 플랫폼(Android, iOS 등)입니다. |
metadata | object | SDK 또는 인증 요청이 전달한 커스텀 키-값 쌍입니다. |
geolocation.viewer_country | string | CloudFront GeoIP 기반 국가 정보이며, 알 수 없으면 Unknown 입니다. |
geolocation.viewer_region | string | GeoIP로 추정한 지역/주 정보입니다. |
geolocation.viewer_city | string | GeoIP로 추정한 도시 정보입니다. |
campaign.id | string | 콘솔 ID와 일치하는 표준 캠페인 식별자입니다. |
campaign.name | string | 이벤트 시점의 캠페인 표시 이름입니다. |
campaign.tags | string[] | 캠페인에 연결된 태그 목록이며, 없으면 빈 배열입니다. |
campaign.image | string (URL) | 설정된 경우 캠페인 썸네일 HTTPS URL입니다. |
material.id | string | 인식된 소재(material) 식별자입니다(캠페인 이벤트). |
material.name | string | 소재 이름이며, 해당 소재가 없으면 생략됩니다. |
material.tags | string[] | 소재에 저장된 태그 목록이며, 없으면 빈 배열입니다. |
material.image | string (URL) | 존재할 경우 소재 미리보기 이미지 URL입니다. |
레거시 필드 안내
event_type, campaign_id, campaign_name, material_id, material_name 최상위 필드는 하위 호환을 위해 제공되지만 곧 제거될 예정입니다. 신규 연동에서는 event 필드와 campaign/material 오브젝트를 기준으로 구현하십시오. 아래 예시는 가독성을 위해 해당 키를 생략했습니다.
인프레션 이벤트 예시:
{
"event": "campaign.impression",
"account_id": "acc_123456",
"created_at": "2025-10-14T05:25:12.421Z",
"activity_id": "act_7890",
"device_id": "device_abc",
"platform": "Android",
"metadata": {
"session": "abcd-1234"
},
"geolocation": {
"viewer_country": "United States",
"viewer_region": "Michigan",
"viewer_city": "Ann Arbor"
},
"campaign": {
"id": "cmp_1234",
"name": "Fall Promotion",
"tags": [
"retail",
"autumn"
],
"image": "https://cdn.soriapi.com/campaigns/cmp_1234.png"
},
"material": {
"id": "mat_5678",
"name": "Audio Spot 15s",
"tags": [
"audio",
"15s"
],
"image": "https://cdn.soriapi.com/materials/mat_5678.png"
},
"timestamp": "2025-10-14T05:25:12.421Z"
}클릭 이벤트 예시:
{
"event": "campaign.click",
"account_id": "acc_123456",
"created_at": "2025-10-14T05:26:03.009Z",
"activity_id": "act_7890",
"device_id": "device_abc",
"platform": "Android",
"metadata": {
"session": "abcd-1234",
"utm_source": "push"
},
"geolocation": {
"viewer_country": "United States",
"viewer_region": "Michigan",
"viewer_city": "Ann Arbor"
},
"campaign": {
"id": "cmp_1234",
"name": "Fall Promotion",
"tags": [
"retail",
"autumn"
],
"image": "https://cdn.soriapi.com/campaigns/cmp_1234.png"
},
"material": {
"id": "mat_5678",
"name": "Audio Spot 15s",
"tags": [
"audio",
"15s"
],
"image": "https://cdn.soriapi.com/materials/mat_5678.png"
},
"timestamp": "2025-10-14T05:26:03.009Z"
}인증 이벤트 예시:
{
"event": "authentication",
"account_id": "acc_123456",
"created_at": "2025-10-14T05:15:44.832Z",
"device_id": "device_abc",
"platform": "Android",
"metadata": {
"app_version": "1.8.0",
"session": "abcd-1234"
},
"geolocation": {
"viewer_country": "United States",
"viewer_region": "Michigan",
"viewer_city": "Ann Arbor"
},
"timestamp": "2025-10-14T05:15:44.832Z"
}커스텀 메타데이터
metadata 필드는 캠페인 이벤트에서는 SDK가 전달하는 키-값 쌍을, 인증 이벤트에서는 인증 요청의 쿼리 파라미터를 담는 맵입니다. 사용자나 디바이스를 식별하거나 부가 정보를 전달할 때 활용할 수 있으며, 선택 사항입니다. 자세한 내용은 Metadata Provider 섹션을 참고하세요.
위치 정보 안내
위치 정보는 geolocation 오브젝트에 포함되어 있습니다. CloudFront GeoIP 추정치에 기반하므로 항상 정확하지 않을 수 있습니다. 값을 확인할 수 없는 경우 Unknown 으로 전달됩니다.
응답 요구 사항
웹훅 엔드포인트는 다음 사항을 준수해야 합니다.
- 수신을 확인하기 위해
2xx상태 코드로 응답합니다. - 가능하다면 비동기로 처리하여 블로킹을 피합니다.
- 타임스탬프나 activity ID를 활용해 멱등하게 처리합니다.
- 타임아웃을 피하기 위해 5초 이내에 응답합니다.
보안 고려 사항
- 데이터 보호를 위해 가능한 한 HTTPS를 사용하세요.
- 수신한 페이로드 구조와
event값을 검증하세요. - 웹훅 URL을 기밀 정보로 취급하세요.
app_id와secret_key를 설정한 경우, 페이로드 사본으로X-SORI-Signature헤더를 검증해 진위 여부를 확인하세요.
오류 처리
엔드포인트가 이벤트를 수신하지 못하면 SORI API Server는 다음과 같이 동작합니다.
- 실패한 전송을 최대 3회 재시도합니다.
- 재시도 간격은 지수 백오프로 증가합니다.
- 모든 재시도가 실패하면 해당 이벤트를 폐기합니다.
구현 예시
Python(FastAPI)
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/webhook")
async def webhook(request: Request):
payload = await request.json()
event = payload.get("event")
if event == "campaign.impression":
# 인프레션 이벤트 처리
pass
elif event == "campaign.click":
# 클릭 이벤트 처리
pass
elif event == "authentication":
# 인증 이벤트 처리
pass
return {"status": "success"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=3000)Node.js(Express)
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhook", (req, res) => {
const payload = req.body;
const event = payload.event;
if (event === "campaign.impression") {
// 인프레션 이벤트 처리
} else if (event === "campaign.click") {
// 클릭 이벤트 처리
} else if (event === "authentication") {
// 인증 이벤트 처리
}
res.status(200).send({ status: "success" });
});
app.listen(3000, () => {
console.log("Server is running on port 3000");
});