웹훅
휴대폰이 실시간 현황을 추가하거나, 사용자가 끄거나 삭제할 때 백엔드로 보내는 서명된 호출이에요.
웹훅 엔드포인트는 서비스가 POST를 보내는 URL이에요. 엔드포인트는 대시보드에서 추가해요. 엔드포인트마다 서명 시크릿(whs_ + 48자리 hex)이 따로 있고, 식별자를 붙일 수 있어요. 한 팀 안의 두 엔드포인트는 같은 식별자를 쓸 수 없어요. URL은 http 또는 https여야 해요.
식별자로 라우팅하기
webhookIdentifier로 시작한 실시간 현황은 모든 이벤트를 그 식별자를 가진 엔드포인트 하나로 보내요. 식별자 없이 시작한 실시간 현황은 식별자가 없는 모든 엔드포인트로 이벤트를 보내요. 일치하는 엔드포인트가 없는 이벤트는 버려져요.
테스트도 이 방법으로 해요. 별도의 테스트 환경은 없어요. 운영 엔드포인트는 식별자 없이 두고, 테스트용 실시간 현황은 테스트 엔드포인트의 식별자로 시작하세요:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
curl -X POST https://api.webliveactivity.com/api/start \ -H 'Content-Type: application/json' \ -H 'x-api-key: wla_your_key' \ -d '{ "widgetContainerId": "acme-orders", "webhookIdentifier": "staging", "brand": { "title": "Order #1234" }, "state": { "layout": "progress", "primaryText": "Order received", "progress": 0.1 } }'
이벤트
| 이벤트 | 전송 시점 |
|---|---|
active | 휴대폰이 실시간 현황을 열어 화면에 표시됐을 때(휴대폰당 한 번), 또는 사용자가 앱에서 다시 켰을 때. |
removed | 사용자가 앱에서 실시간 현황을 끄거나, 삭제하거나, 앱 데이터를 지웠을 때. |
POST /api/end와 재시작은 웹훅을 보내지 않아요.
전송 형식
각 이벤트는 JSON 본문을 담은 POST 한 번이에요:
1 2 3 4 5 6 7 8
{ "activityId": "act-8f2cqa4mx7rd", "group": "order-tracker", "event": "active", "active": true, "webhookIdentifier": "staging", "at": "2026-07-30T14:25:19.991Z" }
| 필드 | 의미 |
|---|---|
activityId | 실시간 현황이에요. reuse: true를 쓰면 start가 반환한 id가 아니라 휴대폰의 자체 사본이에요. |
group | 실시간 현황의 그룹이에요. 설정하지 않았으면 ""예요. |
event | active 또는 removed. |
active | 그 시점의 실시간 현황 화면 표시 플래그예요. 이 플래그가 아니라 event를 기준으로 처리하세요. |
webhookIdentifier | start에서 지정한 식별자예요. 설정하지 않았으면 null이에요. |
at | 이벤트가 발생한 시각이에요(ISO 시간 형식). |
그리고 헤더 세 개:
| 헤더 | 의미 |
|---|---|
X-WebActivities-Event | 이벤트 이름: active 또는 removed. |
X-WebActivities-Timestamp | 이 시도를 보낸 시각(Unix epoch 초)이에요. 재시도할 때마다 새 값이 들어가요. 오래된 타임스탬프는 거부하세요. |
X-WebActivities-Signature | 아래에서 설명하는 HMAC 서명이에요. |
서명 확인하기
서명은 타임스탬프와 원본 요청 본문을 엔드포인트의 시크릿으로 계산한 HMAC-SHA256이에요. 타임스탬프가 서명된 페이로드 안에 있으므로, 가로챈 전송을 나중에 새 타임스탬프로 재전송할 수 없어요.
1 2 3 4 5 6 7 8 9 10 11 12
const { createHmac, timingSafeEqual } = require('node:crypto'); // rawBody is the request body EXACTLY as received, before any JSON parsing. const verify = (secret, headers, rawBody) => { const timestamp = headers['x-webactivities-timestamp']; const expected = 'sha256=' + createHmac('sha256', secret) .update(timestamp + '.' + rawBody) .digest('hex'); const presented = Buffer.from(headers['x-webactivities-signature']); return expected.length === presented.length && timingSafeEqual(Buffer.from(expected), presented); };
잘못된 서명이 올바른 서명에 대해 아무것도 노출하지 않도록 상수 시간으로 비교하고(timingSafeEqual), 재전송 공격을 막으려면 타임스탬프를 로컬 시계와 비교하세요.
재시도
- 엔드포인트는 8초 안에 2xx로 응답해야 해요. 리디렉션은 실패로 처리돼요.
- 실패한 전송은 총 5번 시도하며, 시도 사이에 1분, 2분, 4분, 8분을 기다려요.
- 사설 또는 내부 주소를 가리키는 URL은 거부돼요.
- 대시보드의 전송 로그에는 90일 동안의 모든 전송이 표시되고, 다시 보낼 수도 있어요.
빠르게 응답하기
먼저 이벤트를 저장하고 200으로 응답한 다음 처리하세요. 요청 안에서 오래 걸리는 작업을 하면 8초 제한을 넘겨, 재시도가 중복으로 도착할 수 있어요. 재시도는 순서가 바뀌어 도착할 수 있으니 at 필드를 이벤트의 실제 시각으로 쓰세요.









