콘텐츠로 이동
지원

웹훅

웹훅을 사용하면 LabelGrid 계정에서 이벤트가 발생할 때 실시간 알림을 받을 수 있습니다. 웹훅으로 워크플로를 자동화하고 외부 시스템과 연동하세요.

개발자용: API를 통해 웹훅을 프로그래밍 방식으로 관리할 수도 있습니다. 엔드포인트와 예시는 LabelGrid API 문서를 참고하세요.

  1. 웹훅을 설정합니다 - URL과 수신할 이벤트를 지정합니다
  2. 이벤트가 발생합니다 - 예를 들어 릴리스가 매장에 배포됩니다
  3. LabelGrid이 POST 요청을 보냅니다 - 서버가 이벤트 데이터를 받습니다
  4. 시스템이 이를 처리합니다 - 이벤트를 기반으로 워크플로를 자동화합니다

  1. 오른쪽 위 모서리의 프로필 아이콘을 클릭합니다
  2. 드롭다운 메뉴에서 Webhooks를 선택합니다

  1. Create Webhook을 클릭합니다
  2. 이 웹훅을 식별할 Name을 입력합니다
  3. 알림을 받을 URL을 입력합니다
  4. 이 웹훅을 트리거할 Events를 선택합니다
  5. 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웹훅이 마지막으로 호출된 시점
  1. 웹훅 행에서 Edit 동작을 클릭합니다
  2. 이름, URL 또는 이벤트를 수정합니다
  3. Save를 클릭합니다

웹훅을 삭제하지 않고 활성 상태만 전환합니다.

  • Active - 웹훅이 알림을 받습니다
  • Inactive - 웹훅이 일시 중지되어 알림을 받지 않습니다
  1. 웹훅 행에서 Delete 동작을 클릭합니다
  2. 삭제를 확인합니다

프로덕션에서 웹훅에 의존하기 전에 먼저 테스트하세요.

  1. 웹훅에서 Test 동작을 클릭합니다
  2. LabelGrid이 지정한 URL로 테스트 페이로드를 보냅니다
  3. 엔드포인트가 이를 제대로 받아 처리하는지 확인합니다

웹훅 활동을 모니터링하고 문제를 진단하세요.

  1. 웹훅에서 View Logs 동작을 클릭합니다
  2. 모든 웹훅 전송 기록을 확인합니다

각 로그 항목에는 다음이 표시됩니다.

필드설명
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 타입입니다.

릴리스 배포가 매장별로 최종 성공 상태에 도달하면 매장당 한 번씩 발생합니다.

{
"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_idinteger이 배포 시도의 내부 큐 ID
release_idinteger배포된 릴리스
release_catstring | null릴리스 카탈로그 참조 번호
outlet_idinteger대상 매장 ID
outlet_namestring | null사람이 읽을 수 있는 매장 이름(예: "Spotify")
statusstring이 이벤트에서는 항상 "complete"

릴리스 배포가 매장별로 최종 실패 상태에 도달하면 매장당 한 번씩 발생합니다. 페이로드는 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."
}
}
필드타입설명
statusstringerror, fault, rejected, batch_exception 중 하나
messagestring | null매장 또는 배포 파이프라인에서 전달한 실패 사유

테이크다운 요청이 매장별로 성공하면 매장당 한 번씩 발생합니다. 형태는 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
}
}

릴리스가 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"
}
}

릴리스가 검수 상태 사이를 이동할 때마다 발생합니다.

{
"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_statusstring이전 상태. draft, to_review, approved, rejected, require_changes, audit 중 하나
new_statusstring새 상태. 동일한 값 집합

정산서가 생성되어 확인할 수 있게 되면 발생합니다.

{
"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_idinteger내부 결제 요청 ID
invoice_numberstring정산서 인보이스 참조 번호
periodstring | null기간 종료일(ISO 8601 날짜, YYYY-MM-DD)
amountnumbercurrency 기준 정산 금액
total_due_usdnumberUSD로 환산한 정산 합계
currencystringISO 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-AgentLabelGrid-Webhooks/1.0
Content-Typeapplication/json
  • 알고리즘: HMAC-SHA256
  • 인코딩: 소문자 16진수
  • 접두사: 없음. 값은 sha256=...가 아니라 16진수 다이제스트 그 자체입니다
  • 서명 대상: 원본 JSON 요청 본문 전체(본문 자체에 타임스탬프가 속성으로 포함되어 있으므로, 타임스탬프도 암묵적으로 서명됩니다)
  1. JSON 파싱이나 변환에 앞서 원본 요청 본문을 읽으세요. 파싱한 JSON을 다시 직렬화하면 바이트가 달라져 서명이 깨질 수 있습니다.
  2. HMAC-SHA256(raw_body, your_webhook_secret)을 계산하고 소문자 16진수 다이제스트를 구하세요.
  3. 일정 시간 비교(constant-time comparison)로 X-Webhook-Signature와 비교하세요.
  4. (권장) X-Webhook-Timestamp가 재전송 허용 시간을 넘긴 경우 요청을 거부하세요. 5분을 권장합니다.
$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 minutes
if (abs(time() - strtotime($timestamp)) > 300) {
http_response_code(401);
exit('Stale delivery');
}
$payload = json_decode($rawBody, true);
// ... process the event
http_response_code(200);
const crypto = require('crypto');
// Express: capture raw body BEFORE any JSON middleware
app.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);
});
import hmac, hashlib
from datetime import datetime, timezone
raw_body = request.get_data() # Flask: bytes, before any JSON parsing
signature = 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 → 230초
2 → 31분
3 → 42분
4 → 54분
5 → 68분
6 → 716분
7 → 832분
8 → 964분
9 → 10128분

각 간격에는 0~30초의 지터(jitter)가 포함됩니다. 10번의 시도(누적 약 4.5시간) 후에도 실패하면 해당 전송은 영구 실패로 기록되며 더 이상 재시도하지 않습니다.

웹훅에 누적 실패 전송이 100건 쌓이면 자동으로 비활성화됩니다. 엔드포인트를 고친 뒤 웹훅 목록에서 직접 다시 활성화하세요. 비활성화된 웹훅을 다시 활성화하면 실패 횟수가 초기화됩니다.

  • 2xx 응답을 빠르게(10초 이내) 반환하세요
  • 응답을 보낸 뒤 데이터를 비동기로 처리하세요
  • 모든 요청의 서명을 검증하세요(웹훅 서명 검증 참고)
  • 웹훅 목록에서 실패 횟수를 모니터링하세요
  • 누락된 이벤트를 조사할 때는 전송 로그를 확인하세요

  • 릴리스가 공개되면 Slack 메시지 보내기
  • 배포가 실패하면 팀에 이메일 보내기
  • 내부 대시보드 업데이트
  • 릴리스가 배포되면 마케팅 캠페인 시작하기
  • 새 콘텐츠가 준비되면 웹사이트 업데이트하기
  • 외부 프로젝트 관리 도구로 상태 동기화하기
  • 배포 실패 시 즉시 알림 받기
  • 배포 진행 상황을 실시간으로 추적하기
  • 검수 상태 변경 모니터링하기

  1. 상태 확인 - 웹훅이 Active 상태인가요?
  2. URL 확인 - 엔드포인트에 인터넷에서 접근할 수 있나요?
  3. 이벤트 확인 - 올바른 이벤트가 선택되어 있나요?
  4. 로그 검토 - 기록된 오류가 있나요?
  1. 엔드포인트 확인 - 200 OK를 반환하고 있나요?
  2. 응답 시간 확인 - 타임아웃 안에 응답하고 있나요?
  3. 오류 메시지 검토 - 무엇이 실패하고 있나요?
  4. 수동 테스트 - 테스트 웹훅을 보내 보세요

웹훅 시크릿이 노출된 경우:

  1. 웹훅 설정에서 Regenerate Secret을 클릭합니다
  2. 새 시크릿으로 애플리케이션을 업데이트합니다
  3. 이전 시크릿은 즉시 작동을 멈춥니다

웹훅에 관해 궁금한 점이 있으면 지원팀에 문의하세요.

아직 LabelGrid를 사용하지 않으세요?

방금 읽으신 내용은 모두 저희 플랫폼에서 이용하실 수 있어요.

LabelGrid로 할 수 있는 일 보기 →