발송 결과를 폴링하지 않고 받으려면 웹훅을 걸어요. 수신 URL 과 구독할 이벤트를 지정하면 상태가 확정될 때 부트페이가 요청을 보내요.
알림톡 웹훅은 전용 수신 URL 을 따로 둬요. 주문 웹훅 URL 에 알림톡 이벤트를 태우면 그 수신 서버가 모르는 payload 를 받게 되어 기존 연동이 깨져요. 반드시 별도 엔드포인트를 준비해요.
발송 요청에 webhook_url 을 실으면 그 건의 발송 성공·실패·예약취소 웹훅은 그 주소로만 가고 이 설정은 쓰이지 않아요. 이 설정을 꺼 두었거나 그 이벤트를 구독하지 않았더라도 보내요.
| 상황 | 웹훅이 가는 곳 |
|---|---|
발송 요청에 webhook_url 있음 |
그 주소로만 (발송 결과·예약취소) |
| 없음 | 이 설정의 수신 URL (구독·켜짐 판정 그대로) |
| 템플릿 검수 결과·발송 제외 등록 | 언제나 이 설정의 수신 URL |
발송마다 주소를 지정해서 쓴다면 수신 URL 을 저장하지 않아도 돼요. 이때 서명 검증용 시크릿은 시크릿 재발급으로 설정 없이도 받을 수 있고, 설정 조회는 configured: false 로 나와요.
1설정 조회
코드 예제
curl -X GET "https://message.bootapi.com/alimtalk/webhook" \
-H "Authorization: Basic {base64(client_key:secret_key)}"bashrequire 'bootpay'
commerce = BootpayStore::RestClient.new(client_key: 'your-commerce-client-key', secret_key: 'your-commerce-secret-key')
response = commerce.alimtalk_webhook_detail
puts response.dataruby응답
아직 설정하지 않았다면 configured: false 만 내려와요.
{
"configured": true,
"url": "https://example.com/hooks/alimtalk",
"events": [301, 302, 303, 310, 311],
"enabled": true,
"secret": "whsec_1a2b3c********",
"last_sent_at": "2026-08-27T10:00:05+09:00",
"last_status": 200,
"consecutive_failures": 0,
"auto_disabled_at": null,
"retry_count": 10
}jsonretry_count 는 수신 서버가 2xx 를 돌려주지 않을 때 건마다 최대 몇 번까지 보낼지예요(첫 전송 포함, 기본 10). 아래 2. 설정 저장에서 바꿀 수 있어요.
시크릿은 앞 12자만 보여요. 원문은 시크릿 재발급 응답에서만 볼 수 있어요. 시각 필드 형식은 응답의 시각 형식을 봐요.
2설정 저장
요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
url |
String | 선택 | 수신 URL. https 만 허용해요 |
events |
Array | 선택 | 구독할 이벤트 코드. 처음 저장할 때 생략하면 기본 구독셋이 적용돼요. 빈 배열 [] 은 무시되고 기존 구독이 유지되니, 기본셋으로 되돌리려면 그 코드들을 직접 보내요 |
enabled |
Boolean | 선택 | 활성 여부 |
retry_count |
Integer | 선택 | 건별 재시도 횟수. 1~25 사이로 지정해요(기본 10). 생략하면 기존 값이 유지돼요 |
최초 저장 시 서명 시크릿이 자동 발급돼요.
retry_count 는 저장한 뒤 새로 만들어지는 전송 건부터 적용돼요. 이미 큐에 있는 건은 만들어질 때의 횟수를 따라요.
이벤트 코드 (events)
events 배열에 넣는 값이에요. enum 키가 아니라 숫자 그대로 주고받아요.
| 코드 | 본문의 event |
이벤트 | 기본 구독 |
|---|---|---|---|
300 |
alimtalk.send.requested |
발송 접수 | 미구독 |
301 |
alimtalk.send.succeeded |
전달 성공 | 구독 |
302 |
alimtalk.send.failed |
전달 실패 | 구독 |
303 |
alimtalk.send.canceled |
예약 취소 | 구독 |
310 |
alimtalk.template.approved |
검수 승인 | 구독 |
311 |
alimtalk.template.rejected |
검수 반려 | 구독 |
320 |
alimtalk.optout.registered |
발송 제외 목록 등록 | 미구독 |
events 배열에는 숫자를 넣지만, 수신 서버가 받는 본문의 event 와 X-Bootpay-Webhook-Event 헤더에는 위 문자열이 담겨요. 숫자도 event_code 로 함께 와요.
접수(300)와 발송 제외 등록(320)은 건당 발화라 노이즈가 커서 명시적으로 구독해야만 발송돼요. 목록에 없는 값은 저장할 때 조용히 버려져요.
지금은 300 을 구독해도 이 이벤트가 발송되지 않아요. 접수 여부는 발송 API 응답의 status: "requested" 로 판단하고, 확정 결과는 301·302 로 받아요.
코드 예제
curl -X PUT "https://message.bootapi.com/alimtalk/webhook" \
-H "Authorization: Basic {base64(client_key:secret_key)}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/alimtalk",
"events": [301, 302, 303, 310, 311],
"enabled": true,
"retry_count": 5
}'bashresponse = commerce.alimtalk_webhook_update(
url: 'https://example.com/hooks/alimtalk',
events: [301, 302, 303, 310, 311],
enabled: true,
retry_count: 5
)
puts response.dataruby서명 검증
요청에 아래 헤더가 붙어요.
X-Bootpay-Webhook-Event: alimtalk.send.succeeded
X-Bootpay-Delivery-Id: 68b0f2a1c3d4e5f6a7b8c9e1
X-Bootpay-Timestamp: 1756272000
X-Bootpay-Signature: sha256=HMAC_SHA256(secret, "{X-Bootpay-Timestamp}.{raw_body}")수신 서버에서 같은 방식으로 계산해 비교하고, 타임스탬프가 5분 이상 지난 요청은 거부해요(replay 방지). 서명은 파싱하기 전 원본 본문(raw body) 으로 계산해요. sha256= 뒤의 값은 HMAC-SHA256 결과를 소문자 16진수(hex)로 적은 것이에요. X-Bootpay-Delivery-Id 는 전송 이력의 delivery_id 와 같아요.
payload
본문은 아래 봉투에 이벤트별 data 가 담긴 모양이에요.
| 필드 | 타입 | 설명 |
|---|---|---|
| event | String | 이벤트 별칭 (예: alimtalk.send.succeeded) |
| event_code | Integer | 이벤트 코드 (예: 301) |
| occurred_at | String | 발생 시각 (ISO8601) |
| data | Object | 이벤트별 본문 (아래) |
발송 이벤트(301·302·303)의 data 는 발송내역 목록의 항목과 같은 카드예요. 그래서 조회·목록·웹훅을 파서 하나로 처리할 수 있어요. 다만 시각 필드는 웹훅에서 ISO8601 로 와서 REST 응답과 형식이 달라요 — 응답의 시각 형식을 봐요.
검수 이벤트(310·311)의 data 예요.
| 필드 | 타입 | 설명 |
|---|---|---|
| template_code | String | 템플릿 코드 |
| name | String | 템플릿 이름 |
| inspection_status | Integer | 검수 상태 숫자 (3 승인 · 4 등록 거절 · 5 승인 반려). REST 응답의 "approved" 같은 키가 아니라 숫자로 와요 |
| vendor_status | Integer | 등록 상태 (0 등록전 · 1 대기 · 2 정상 · 3 중단) |
| comments | Array | 검수 반려 사유 |
| synced_at | String | 마지막으로 상태를 맞춘 시각 |
발송 제외 등록 이벤트(320)의 data 예요.
| 필드 | 타입 | 설명 |
|---|---|---|
| phone | String | 제외 등록된 번호 (숫자만) |
| scope | Integer | 2 프로젝트 |
| source | Integer | 등록 경로 (1 랜딩 · 2 API · 3 관리자 · 4 신고) |
| reason | String | 사유 |
| opted_out_at | String | 등록 시각 |
자동 비활성
연속 실패가 10회(기본값)에 이르면 웹훅이 자동으로 비활성화돼요. 죽은 URL 로 영원히 재시도하지 않기 위해서예요. 실패는 수신 서버가 2xx 가 아닌 HTTP 응답을 돌려준 경우만 세고, 재시도도 한 번씩 세요. 연결 실패·타임아웃처럼 응답 자체가 없으면 세지 않아서 자동 비활성이 걸리지 않아요.
auto_disabled_at 에 시각이 찍히고, URL 을 고쳐 다시 저장하면 실패 카운터와 auto_disabled_at 이 초기화돼요. 이때 enabled 는 false 로 남으니 다시 받으려면 enabled: true 를 함께 보내요.
발송 요청에 실은 webhook_url 의 실패는 이 카운터에 들어가지 않아요. 그 주소가 죽어 있어도 여기 저장한 수신 URL 은 그대로 살아 있어요(주소가 서로 같을 때만 함께 세요). 대신 건별 주소에는 자동 비활성이 없으니, 주소를 바꿀 때는 발송 요청 쪽을 고쳐요.
에러 코드
| 코드 | error_code | 메시지 | 대처 방법 |
|---|---|---|---|
3028 |
WEBHOOK_URL_INVALID |
웹훅 URL 형식 오류 | https:// 로 시작하는 URL 인지 확인해요 |
11304 |
WEBHOOK_RETRY_COUNT_INVALID |
웹훅 재시도는 최소 1회부터 최대 25회까지 설정이 가능합니다 | retry_count 를 1~25 사이 정수로 보내요 |
다음 단계
설정을 마쳤다면 테스트 발송으로 수신 서버가 제대로 받는지 확인해요.
