웹훅
웹훅을 사용하면 LabelGrid 계정에서 이벤트가 발생할 때 실시간 알림을 받을 수 있습니다. 웹훅으로 워크플로를 자동화하고 외부 시스템과 연동하세요.
개발자용: API를 통해 웹훅을 프로그래밍 방식으로 관리할 수도 있습니다. 엔드포인트와 예시는 LabelGrid API 문서를 참고하세요.
웹훅 작동 방식
섹션 제목: “웹훅 작동 방식”- 웹훅을 설정합니다 - URL과 수신할 이벤트를 지정합니다
- 이벤트가 발생합니다 - 예를 들어 릴리스가 매장에 배포됩니다
- LabelGrid이 POST 요청을 보냅니다 - 서버가 이벤트 데이터를 받습니다
- 시스템이 이를 처리합니다 - 이벤트를 기반으로 워크플로를 자동화합니다
웹훅 접근하기
섹션 제목: “웹훅 접근하기”- 오른쪽 위 모서리의 프로필 아이콘을 클릭합니다
- 드롭다운 메뉴에서 Webhooks를 선택합니다
웹훅 만들기
섹션 제목: “웹훅 만들기”- Create Webhook을 클릭합니다
- 이 웹훅을 식별할 Name을 입력합니다
- 알림을 받을 URL을 입력합니다
- 이 웹훅을 트리거할 Events를 선택합니다
- Create를 클릭합니다
웹훅 시크릿
섹션 제목: “웹훅 시크릿”웹훅을 만들면 시크릿 키를 받게 됩니다. 들어오는 요청이 실제로 LabelGrid에서 온 것인지 확인하는 데 사용하세요.
- 시크릿을 안전하게 보관하세요
- 들어오는 요청의 서명을 검증하세요
- 노출되면 시크릿을 재발급하세요
사용 가능한 이벤트
섹션 제목: “사용 가능한 이벤트”웹훅이 다음 이벤트를 수신하도록 설정하세요. **이벤트 식별자(Event Identifier)**는 페이로드의 event 속성과 X-Webhook-Event 헤더에 표시되는 값입니다.
| 이벤트 식별자 | 설명 |
|---|---|
delivery.completed | 릴리스가 매장에 성공적으로 배포되었을 때 발생 |
delivery.failed | 매장 배포가 실패했을 때 발생 |
takedown.completed | 테이크다운 요청이 완료되었을 때 발생 |
release.review.status_changed | 릴리스 검수 상태가 바뀌었을 때 발생 |
release.distributed | 릴리스가 배포되었을 때 발생 |
payment.statement_ready | 정산서를 확인할 수 있게 되었을 때 발생 |
하나의 웹훅에 여러 이벤트를 선택할 수도 있고, 이벤트 유형별로 웹훅을 따로 만들 수도 있습니다.
웹훅 관리
섹션 제목: “웹훅 관리”웹훅 목록 보기
섹션 제목: “웹훅 목록 보기”웹훅 목록에는 다음이 표시됩니다.
| 열 | 설명 |
|---|---|
| Name | 지정한 웹훅 이름 |
| URL | 알림이 전송되는 위치 |
| Events | 설정된 이벤트 수 |
| Status | 활성 또는 비활성 |
| Success / Fail | 성공 및 실패한 전송 횟수 |
| Last Triggered | 웹훅이 마지막으로 호출된 시점 |
웹훅 수정하기
섹션 제목: “웹훅 수정하기”- 웹훅 행에서 Edit 동작을 클릭합니다
- 이름, URL 또는 이벤트를 수정합니다
- Save를 클릭합니다
활성화 / 비활성화
섹션 제목: “활성화 / 비활성화”웹훅을 삭제하지 않고 활성 상태만 전환합니다.
- Active - 웹훅이 알림을 받습니다
- Inactive - 웹훅이 일시 중지되어 알림을 받지 않습니다
웹훅 삭제하기
섹션 제목: “웹훅 삭제하기”- 웹훅 행에서 Delete 동작을 클릭합니다
- 삭제를 확인합니다
웹훅 테스트하기
섹션 제목: “웹훅 테스트하기”프로덕션에서 웹훅에 의존하기 전에 먼저 테스트하세요.
- 웹훅에서 Test 동작을 클릭합니다
- LabelGrid이 지정한 URL로 테스트 페이로드를 보냅니다
- 엔드포인트가 이를 제대로 받아 처리하는지 확인합니다
웹훅 로그 보기
섹션 제목: “웹훅 로그 보기”웹훅 활동을 모니터링하고 문제를 진단하세요.
- 웹훅에서 View Logs 동작을 클릭합니다
- 모든 웹훅 전송 기록을 확인합니다
로그 세부 정보
섹션 제목: “로그 세부 정보”각 로그 항목에는 다음이 표시됩니다.
| 필드 | 설명 |
|---|---|
| Event Type | 이 전송을 트리거한 이벤트 |
| Response Status | 서버에서 반환한 HTTP 상태 코드 |
| Duration | 요청에 걸린 시간 |
| Attempt | 재시도 횟수 |
| Timestamp | 전송이 발생한 시점 |
웹훅 페이로드 형식
섹션 제목: “웹훅 페이로드 형식”이벤트가 발생하면 LabelGrid이 지정한 URL로 JSON 페이로드를 담은 POST 요청을 보냅니다.
{ "event": "delivery.completed", "timestamp": "2026-05-05T10:00:00+00:00", "webhook_id": "123", "data": { // Event-specific data }}timestamp 필드는 ISO 8601 형식을 사용합니다. webhook_id는 설정한 웹훅의 ID이며 X-Webhook-Id 헤더와 일치합니다.
이벤트 페이로드
섹션 제목: “이벤트 페이로드”data 객체의 구조는 event 유형에 따라 달라집니다. 아래의 모든 필드 타입은 페이로드에 직렬화된 형태의 JSON 타입입니다.
delivery.completed
섹션 제목: “delivery.completed”릴리스 배포가 매장별로 최종 성공 상태에 도달하면 매장당 한 번씩 발생합니다.
{ "event": "delivery.completed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "complete" }}| 필드 | 타입 | 설명 |
|---|---|---|
distro_queue_id | integer | 이 배포 시도의 내부 큐 ID |
release_id | integer | 배포된 릴리스 |
release_cat | string | null | 릴리스 카탈로그 참조 번호 |
outlet_id | integer | 대상 매장 ID |
outlet_name | string | null | 사람이 읽을 수 있는 매장 이름(예: "Spotify") |
status | string | 이 이벤트에서는 항상 "complete" |
delivery.failed
섹션 제목: “delivery.failed”릴리스 배포가 매장별로 최종 실패 상태에 도달하면 매장당 한 번씩 발생합니다. 페이로드는 delivery.completed와 같으며 message 필드가 추가됩니다.
{ "event": "delivery.failed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "error", "message": "Outlet rejected the delivery: missing ISRC." }}| 필드 | 타입 | 설명 |
|---|---|---|
status | string | error, fault, rejected, batch_exception 중 하나 |
message | string | null | 매장 또는 배포 파이프라인에서 전달한 실패 사유 |
takedown.completed
섹션 제목: “takedown.completed”테이크다운 요청이 매장별로 성공하면 매장당 한 번씩 발생합니다. 형태는 delivery.completed와 같으며 takedown: true 플래그가 추가됩니다.
{ "event": "takedown.completed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "complete", "takedown": true }}release.distributed
섹션 제목: “release.distributed”릴리스가 distributed 배포 상태로 전환될 때 릴리스당 한 번 발생합니다. distributed로 전환되는 순간에만 발생하며, 이미 배포된 릴리스를 이후에 다시 저장할 때는 발생하지 않습니다.
{ "event": "release.distributed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "release_cat": "ABC123", "release_title": "Summer EP", "delivery_status": "distributed" }}release.review.status_changed
섹션 제목: “release.review.status_changed”릴리스가 검수 상태 사이를 이동할 때마다 발생합니다.
{ "event": "release.review.status_changed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "release_cat": "ABC123", "release_title": "Summer EP", "previous_status": "to_review", "new_status": "approved" }}| 필드 | 타입 | 설명 |
|---|---|---|
previous_status | string | 이전 상태. draft, to_review, approved, rejected, require_changes, audit 중 하나 |
new_status | string | 새 상태. 동일한 값 집합 |
payment.statement_ready
섹션 제목: “payment.statement_ready”정산서가 생성되어 확인할 수 있게 되면 발생합니다.
{ "event": "payment.statement_ready", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "payment_request_id": 1024, "invoice_number": "INV-2026-001", "period": "2026-04-30", "amount": 1234.56, "total_due_usd": 1234.56, "currency": "USD" }}| 필드 | 타입 | 설명 |
|---|---|---|
payment_request_id | integer | 내부 결제 요청 ID |
invoice_number | string | 정산서 인보이스 참조 번호 |
period | string | null | 기간 종료일(ISO 8601 날짜, YYYY-MM-DD) |
amount | number | currency 기준 정산 금액 |
total_due_usd | number | USD로 환산한 정산 합계 |
currency | string | ISO 4217 통화 코드(기본값 USD) |
웹훅 서명 검증
섹션 제목: “웹훅 서명 검증”모든 웹훅 전송에는 서명이 포함되어 있어 실제로 LabelGrid에서 온 것인지 확인할 수 있습니다. 이벤트를 처리하기 전에 항상 서명을 검증하세요.
요청 헤더
섹션 제목: “요청 헤더”모든 웹훅 POST 요청에는 다음 헤더가 포함됩니다.
| 헤더 | 설명 |
|---|---|
X-Webhook-Signature | 원본 요청 본문의 HMAC-SHA256 값(소문자 16진수, 알고리즘 접두사 없음) |
X-Webhook-Timestamp | 전송의 ISO 8601 타임스탬프(본문의 timestamp 속성과 같은 값) |
X-Webhook-Event | 이벤트 식별자(예: delivery.completed) |
X-Webhook-Id | 전송을 받는 웹훅의 ID |
User-Agent | LabelGrid-Webhooks/1.0 |
Content-Type | application/json |
알고리즘
섹션 제목: “알고리즘”- 알고리즘: HMAC-SHA256
- 인코딩: 소문자 16진수
- 접두사: 없음. 값은
sha256=...가 아니라 16진수 다이제스트 그 자체입니다 - 서명 대상: 원본 JSON 요청 본문 전체(본문 자체에 타임스탬프가 속성으로 포함되어 있으므로, 타임스탬프도 암묵적으로 서명됩니다)
검증 절차
섹션 제목: “검증 절차”- JSON 파싱이나 변환에 앞서 원본 요청 본문을 읽으세요. 파싱한 JSON을 다시 직렬화하면 바이트가 달라져 서명이 깨질 수 있습니다.
HMAC-SHA256(raw_body, your_webhook_secret)을 계산하고 소문자 16진수 다이제스트를 구하세요.- 일정 시간 비교(constant-time comparison)로
X-Webhook-Signature와 비교하세요. - (권장)
X-Webhook-Timestamp가 재전송 허용 시간을 넘긴 경우 요청을 거부하세요. 5분을 권장합니다.
PHP 예시
섹션 제목: “PHP 예시”$rawBody = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $signature)) { http_response_code(401); exit('Invalid signature');}
// Reject deliveries older than 5 minutesif (abs(time() - strtotime($timestamp)) > 300) { http_response_code(401); exit('Stale delivery');}
$payload = json_decode($rawBody, true);// ... process the eventhttp_response_code(200);Node.js 예시
섹션 제목: “Node.js 예시”const crypto = require('crypto');
// Express: capture raw body BEFORE any JSON middlewareapp.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const rawBody = req.body; // Buffer const signature = req.header('X-Webhook-Signature') || ''; const timestamp = req.header('X-Webhook-Timestamp') || '';
const expected = crypto .createHmac('sha256', webhookSecret) .update(rawBody) .digest('hex');
const sigBuf = Buffer.from(signature, 'hex'); const expBuf = Buffer.from(expected, 'hex');
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) { return res.status(401).send('Invalid signature'); }
if (Math.abs(Date.now() - new Date(timestamp).getTime()) > 5 * 60 * 1000) { return res.status(401).send('Stale delivery'); }
const payload = JSON.parse(rawBody.toString('utf8')); // ... process the event res.sendStatus(200);});Python 예시
섹션 제목: “Python 예시”import hmac, hashlibfrom datetime import datetime, timezone
raw_body = request.get_data() # Flask: bytes, before any JSON parsingsignature = request.headers.get('X-Webhook-Signature', '')timestamp = request.headers.get('X-Webhook-Timestamp', '')
expected = hmac.new( webhook_secret.encode('utf-8'), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature): return ('Invalid signature', 401)
delivery_time = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))if abs((datetime.now(timezone.utc) - delivery_time).total_seconds()) > 300: return ('Stale delivery', 401)
return ('', 200) # process the event, then ack자주 빠지는 함정
섹션 제목: “자주 빠지는 함정”- 해싱 전에 본문을 다시 직렬화하는 것. JSON을 자동으로 파싱하는 프레임워크(Express의
express.json(), Laravel의 기본 요청 본문 처리)는 원본 바이트를 잃어버립니다. 원본 본문을 먼저 확보하세요. - 일정 시간 비교가 아닌 비교(
==,===)를 사용하는 것. 타이밍 공격에 취약합니다. 항상hash_equals(PHP),crypto.timingSafeEqual(Node),hmac.compare_digest(Python) 또는 사용 언어의 동등한 함수를 사용하세요. sha256=접두사를 기대하는 것. 헤더 값은 접두사 없이 16진수 다이제스트뿐입니다.- 타임스탬프 검사를 건너뛰는 것. 검사하지 않으면 유출된 서명이 무기한 재전송될 수 있습니다.
한도 및 신뢰성
섹션 제목: “한도 및 신뢰성”요청 한도
섹션 제목: “요청 한도”| 한도 | 값 |
|---|---|
| 요청 타임아웃 | 10초 |
| 최대 페이로드 크기 | 64 KB |
| 사용자당 최대 웹훅 수 | 10개 |
엔드포인트가 10초 이내에 응답하지 않으면 해당 전송은 실패로 처리되어 재시도됩니다.
재시도 일정
섹션 제목: “재시도 일정”엔드포인트가 2xx 이외의 상태를 반환하거나 타임아웃되면 LabelGrid은 지수 백오프 방식으로 재시도합니다.
| 시도 | 재시도 전 대기 시간 |
|---|---|
| 1 → 2 | 30초 |
| 2 → 3 | 1분 |
| 3 → 4 | 2분 |
| 4 → 5 | 4분 |
| 5 → 6 | 8분 |
| 6 → 7 | 16분 |
| 7 → 8 | 32분 |
| 8 → 9 | 64분 |
| 9 → 10 | 128분 |
각 간격에는 0~30초의 지터(jitter)가 포함됩니다. 10번의 시도(누적 약 4.5시간) 후에도 실패하면 해당 전송은 영구 실패로 기록되며 더 이상 재시도하지 않습니다.
자동 비활성화
섹션 제목: “자동 비활성화”웹훅에 누적 실패 전송이 100건 쌓이면 자동으로 비활성화됩니다. 엔드포인트를 고친 뒤 웹훅 목록에서 직접 다시 활성화하세요. 비활성화된 웹훅을 다시 활성화하면 실패 횟수가 초기화됩니다.
신뢰성을 높이는 모범 사례
섹션 제목: “신뢰성을 높이는 모범 사례”- 2xx 응답을 빠르게(10초 이내) 반환하세요
- 응답을 보낸 뒤 데이터를 비동기로 처리하세요
- 모든 요청의 서명을 검증하세요(웹훅 서명 검증 참고)
- 웹훅 목록에서 실패 횟수를 모니터링하세요
- 누락된 이벤트를 조사할 때는 전송 로그를 확인하세요
활용 사례
섹션 제목: “활용 사례”자동 알림
섹션 제목: “자동 알림”- 릴리스가 공개되면 Slack 메시지 보내기
- 배포가 실패하면 팀에 이메일 보내기
- 내부 대시보드 업데이트
워크플로 자동화
섹션 제목: “워크플로 자동화”- 릴리스가 배포되면 마케팅 캠페인 시작하기
- 새 콘텐츠가 준비되면 웹사이트 업데이트하기
- 외부 프로젝트 관리 도구로 상태 동기화하기
모니터링 및 알림
섹션 제목: “모니터링 및 알림”- 배포 실패 시 즉시 알림 받기
- 배포 진행 상황을 실시간으로 추적하기
- 검수 상태 변경 모니터링하기
문제 해결
섹션 제목: “문제 해결”웹훅이 이벤트를 받지 못할 때
섹션 제목: “웹훅이 이벤트를 받지 못할 때”- 상태 확인 - 웹훅이 Active 상태인가요?
- URL 확인 - 엔드포인트에 인터넷에서 접근할 수 있나요?
- 이벤트 확인 - 올바른 이벤트가 선택되어 있나요?
- 로그 검토 - 기록된 오류가 있나요?
실패 횟수가 많을 때
섹션 제목: “실패 횟수가 많을 때”- 엔드포인트 확인 - 200 OK를 반환하고 있나요?
- 응답 시간 확인 - 타임아웃 안에 응답하고 있나요?
- 오류 메시지 검토 - 무엇이 실패하고 있나요?
- 수동 테스트 - 테스트 웹훅을 보내 보세요
시크릿 재발급
섹션 제목: “시크릿 재발급”웹훅 시크릿이 노출된 경우:
- 웹훅 설정에서 Regenerate Secret을 클릭합니다
- 새 시크릿으로 애플리케이션을 업데이트합니다
- 이전 시크릿은 즉시 작동을 멈춥니다
도움이 필요하신가요?
섹션 제목: “도움이 필요하신가요?”웹훅에 관해 궁금한 점이 있으면 지원팀에 문의하세요.