본문으로 건너뛰기

SMS 발송 결과 웹훅

블럭스가 발송한 SMS/LMS가 유저에게 도달하거나 도달에 실패할 때 외부 시스템으로 실시간 전달받는 기능입니다. 처리 결과가 확정될 때마다 등록된 URL로 HTTP POST 요청이 전송됩니다.

정보

현재는 SMS 직접 발송 API(POST /notifications/send-sms)로 보낸 건만 결과 웹훅이 전송됩니다. 캠페인·시나리오 발송 결과는 추후 지원 예정입니다.

이벤트 타입

event_type발생 시점
notification:sms:received유저 단말기에 메시지가 도달한 시점
notification:sms:receive_failed유저 단말기까지 도달하지 못한 경우 (수신 차단, 잘못된 번호 등)

한 번의 SMS 발송에 대해 received 또는 receive_failed 중 한 건의 웹훅이 전송됩니다.

이벤트 등록

받고 싶은 이벤트 타입과 콜백 URL을 블럭스 담당자에게 전달하면 등록됩니다. 이벤트 타입별로 서로 다른 URL을 등록할 수 있습니다.

페이로드

웹훅은 다음 형태의 JSON을 HTTP POST로 전송합니다. 여러 이벤트를 한 번에 묶어 보낼 수 있도록 events 배열에 담아 전송하며, 현재는 한 번의 요청에 1건만 담겨 옵니다. 향후 배치 발송이 도입되면 배열 길이가 늘어날 수 있으므로 항상 배열로 파싱해 주세요.

{
"events": [
{
"event_type": "notification:sms:received",
"payload": {
"application_id": "65f1a2b3c4d5e6f7a8b9c0d1",
"blux_user_id": "65f1a2b3c4d5e6f7a8b9c0d2",
"user_id": "user_123",
"notification_id": "65f1a2b3c4d5e6f7a8b9c0d3",
"phone_number": "01012345678",
"status": "received",
"received_at": "2026-05-11T10:23:45.123Z"
}
}
]
}

필드

필드타입필수설명
eventsarray필수결과 이벤트 배열. 현재는 1건씩 전송
events[].event_typestring필수위 2종 중 하나
events[].payload.application_idstring필수블럭스 application 식별자
events[].payload.blux_user_idstring선택블럭스 user 식별자 (외부 직접 발송처럼 매핑이 없으면 누락)
events[].payload.user_idstring선택등록된 경우 고객사 자체 user_id
events[].payload.notification_idstring필수블럭스 notification 식별자
events[].payload.phone_numberstring필수수신자 전화번호
events[].payload.statusstring필수received / receive_failed
events[].payload.received_atstring필수단말기 도달(시도) 시각 (ISO 8601, UTC)
events[].payload.failed_reasonstring선택receive_failed인 경우 사유

응답

10초 이내로 200 응답을 보내주세요.

  • 200 응답을 받으면 전송 성공으로 처리됩니다.
  • 200 응답을 보내지 않으면 재전송됩니다.
  • 4xx 응답은 영구 실패로 간주되어 재전송하지 않습니다.

응답 본문은 비어 있어도 됩니다. 무거운 후처리는 큐에 위임하고 빠르게 200 응답을 보내주세요.

재전송 정책

최초 전송이 실패하면 최대 6회까지 웹훅을 재전송합니다. 재전송 간격은 점점 길어집니다.

시도간격 (이전 시도로부터)
2회약 2~4초 후
3회약 4~8초 후
4회약 8~16초 후
5회약 16~32초 후
6회약 32~64초 후
7회약 64~128초 후

최초 전송으로부터 5분이 지나면 더 이상 재전송하지 않습니다.

같은 이벤트가 두 번 도달하는 경우

네트워크 환경에 따라 같은 이벤트가 두 번 도달할 수 있습니다. 외부 시스템에서는 notification_idevent_type을 키로 사용해 이미 처리한 이벤트는 다시 처리하지 않도록 구현해주세요.

운영 환경에서는 처리한 이벤트 키를 Redis 또는 DB에 기록하길 권장합니다.

  • Redis: SET <key> 1 NX EX 86400 형태로 24시간 동안만 키를 보관 (NX 옵션으로 처음 set일 때만 성공)
  • DB: 처리 이력 테이블에 (notification_id, event_type) 유니크 인덱스를 두고 insert 충돌 시 skip

보안

콜백 URL은 HTTPS로만 등록할 수 있습니다. HTTP URL은 등록 시 거절됩니다.

발신자 검증 서명은 현재 제공하지 않습니다. 콜백 URL은 추측이 어려운 경로로 설정하고, 필요하다면 받는 쪽에서 IP 화이트리스트 등의 보호를 추가해주세요.

수신 예제

Node.js (Express)

import express from "express";

const app = express();
app.use(express.json());

const processedEvents = new Set();

app.post("/blux/sms-webhook", (req, res) => {
const events = Array.isArray(req.body?.events) ? req.body.events : [];

for (const { event_type, payload } of events) {
const eventKey = `${payload.notification_id}:${event_type}`;
if (processedEvents.has(eventKey)) continue;
processedEvents.add(eventKey);

console.log(`[${event_type}] ${payload.phone_number}${payload.status}`);
}

res.status(200).send();
});

app.listen(3000);

Python (FastAPI)

from fastapi import FastAPI, Request

app = FastAPI()
processed_events = set()

@app.post("/blux/sms-webhook")
async def receive_webhook(request: Request):
body = await request.json()
events = body.get("events") or []

for event in events:
event_type = event["event_type"]
payload = event["payload"]

event_key = f"{payload['notification_id']}:{event_type}"
if event_key in processed_events:
continue
processed_events.add(event_key)

print(f"[{event_type}] {payload['phone_number']}{payload['status']}")

return {}