Webhook
분석된 결과를 원하는 callback URL로 전달해주는 기능으로, Session API에 Create Session API에서 파라미터에 적어주면, 아래와 같은 포맷으로 분석 결과를 전달합니다.
안전한 처리를 위한 권장 사항 (멱등성 및 순서 보장)네트워크 지연이나 재시도로 인해 이미 처리된 웹훅 요청이 중복 전달되거나, 이벤트 순서가 뒤바뀐 상태로 도착할 수 있습니다.시스템의 데이터 정합성을 보장하기 위해, 아래 가이드에 따라 이벤트의 멱등성과 순서 검증 로직을 구현해 주세요.
1. 중복 처리 방지 (이벤트 별 멱등성 키 설정)웹훅의 중복 수신을 안전하게 처리하기 위한 고유 키(Unique Key) 기준은 이벤트 타입에 따라 다릅니다.
SESSION_COMPLETE
- 수면 세션 종료 시, 세션당 한 번만 발생하는 이벤트입니다.
- 고유 키 조합:
session_id + eventINFERENCE_COMPLETE(실시간 수면 분석 완료 이벤트)
- 한 세션 내에서 AI 모델의 수면 예측 결과가 갱신될 때마다 반복적으로 발생하는 이벤트입니다. 따라서 이벤트의 고유성을 보장하기 위해 시퀀스 번호까지 함께 관리해야 합니다.
- 고유 키 조합:
session_id + event + inference_seq_num
2. 이벤트 순서 보장 (Order Guarantee)네트워크 환경에 따라 후속 이벤트가 선행 이벤트보다 먼저 도착할 수 있습니다. 특히
INFERENCE_COMPLETE이벤트는 순서가 중요한 이벤트이므로, 수신 페이로드 내inference_seq_num값을 기준으로 이벤트의 선후 관계를 검증해야 합니다.
재시도 정책
웹훅 요청은 수신 서버가 2xx 상태 코드를 반환한 경우 성공으로 간주됩니다. 다음의 경우 Asleep은 자동으로 재시도합니다.
- 응답 상태 코드가 408, 429 또는 5xx인 경우
- 네트워크 연결이 끊기거나 타임아웃이 발생한 경우
재시도는 최대 15회까지 수행되며, 이후에는 웹훅 전송은 최종 실패 처리됩니다. 재시도 간격은 아래 시퀀스를 기반으로 동작합니다.
(10, 10, 30, 10, 10, 480, 10, 10, 2430, 10, 10, 7680, 10, 10)
Request
Method
POST
Header
Field | Type | Description |
|---|---|---|
x-api-key | String | 데이터 업로드 또는 세션 종료 시 사용한 API Key |
x-user-id | String | 수면 세션을 생성한 user id |
X-Asleep-Timestamp | String | 웹훅 발송 시각 (Unix timestamp, 초) - 웹훅 시크릿 발급 시에만 포함 |
X-Asleep-Signautre | String | HMAC-SHA256 서명. 웹훅 시크릿 발급 시에만 포함 자세한 내용은 Webhook HMAC 서명 검증하기 참고 |
Body
Field | Type | Description |
|---|---|---|
event | String ( | Webhook 이벤트 타입 |
version | String | Webhook 버전 |
data | Webhook Data Object | 웹훅 데이터 |
INFERENCE_COMPLETE일 경우 Webhook Data Object
Field | Type | Description |
|---|---|---|
user_id | String | user id |
session_id | String | session id |
seq_num | Int | 오디오 데이터 업로드 순서 번호 |
inference_seq_num | Int | seq_num을 5분 단위로 변환한 번호 |
sleep_stages | [Int] | 직전 5분 단위의 수면 단계 결과. 가장 최근 분석된 10개의 값을 전달받음 |
snoring_stages | [Int] | 직전 5분 단위의 코골이 결과. 가장 최근 분석된 10개의 값을 전달받음 |
{
"event": "INFERENCE_COMPLETE",
"version": "V3",
"data": {
"user_id": "G-20250115025029-vLErWBfQNtnfvgDccFOQ",
"session_id": "20250115025029_fvivn",
"seq_num": 39,
"inference_seq_num": 3,
"sleep_stages": [0], // omitted
"breath_stages": null,
"snoring_stages": [0] // omitted
}
}SESSION_COMPLETE일 경우 Webhook Data Object
| Field | Type | Description |
|---|---|---|
| data | Sleep Data Object | Data API의 Get Session의 응답과 같은 형태 (user_id 만 추가되어 있음) |
{
"event": "SESSION_COMPLETE",
"version": "V3",
"data": {
"timezone": "UTC",
"peculiarities": ["NO_BREATHING_STABILITY"],
"missing_data_ratio": 0.0,
"user_id": "G-20250115025029-vLErWBfQNtnfvgDccFOQ",
"session": {
"id": "20250115025029_fvivn",
"state": "COMPLETE",
"start_time": "2025-01-15T02:50:29+00:00",
"end_time": "2025-01-15T03:50:29+00:00",
"unexpected_end_time": null,
"created_timezone": "UTC",
"sleep_stages": [0], // omitted
"breath_stages": null,
"snoring_stages": [0] // omitted
},
"stat": {
"sleep_time": "2025-01-15T03:05:29+00:00",
"wake_time": "2025-01-15T03:26:29+00:00",
"sleep_index": 50,
"sleep_latency": 900,
"wakeup_latency": 1440,
"light_latency": 0,
"deep_latency": null,
"rem_latency": null,
"time_in_bed": 3600,
"time_in_sleep_period": 1260,
"time_in_sleep": 1080,
"time_in_wake": 180,
"time_in_light": 1080,
"time_in_deep": 0,
"time_in_rem": 0,
"time_in_stable_breath": null,
"time_in_unstable_breath": null,
"time_in_snoring": 0,
"time_in_no_snoring": 1260,
"sleep_efficiency": 0.3,
"sleep_ratio": 0.86,
"wake_ratio": 0.14,
"light_ratio": 0.86,
"deep_ratio": 0.0,
"rem_ratio": 0.0,
"stable_breath_ratio": null,
"unstable_breath_ratio": null,
"snoring_ratio": 0.0,
"no_snoring_ratio": 1.0,
"breathing_index": null,
"breathing_pattern": null,
"waso_count": 1,
"longest_waso": 180,
"sleep_cycle_count": 0,
"sleep_cycle": null,
"sleep_cycle_time": [],
"unstable_breath_count": null,
"snoring_count": 0
}
}
}Updated 1 day ago
