웹훅
웹훅은 메시지 발송 결과, 그룹 처리 완료, 팩스 수신 등의 이벤트가 발생했을 때 지정한 URL로 데이터를 실시간 전달하는 기능입니다. 폴링 없이도 이벤트를 즉시 수신할 수 있어 효율적인 시스템 연동이 가능합니다.
지원 이벤트
사용 가능한 이벤트 목록은
GET /webhook/v1/eventsAPI로도 조회할 수 있습니다.
웹훅 등록
요청
POST /webhook/v1/outgoing
파라미터
요청 예시
curl -X POST https://api.solapi.com/webhook/v1/outgoing \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"eventId": "SINGLE-REPORT",
"url": "https://your-server.com/webhook",
"secret": "my-webhook-secret"
}'
응답 예시
{
"webhookId": "WH01WH250321...",
"eventId": "SINGLE-REPORT",
"url": "https://your-server.com/webhook",
"status": "ACTIVE",
"dateCreated": "2026-03-21T05:00:00.000Z"
}
계정당 등록 가능한 웹훅 수는 제한되어 있습니다. (기본 2개)
웹훅 수신 서버 구현
웹훅 데이터는 등록한 URL로 HTTP POST 요청으로 전달됩니다.
핵심 규칙
HTTP 200 응답을 반환하세요. 200번대 상태 코드를 반환하면 성공으로 처리됩니다. 응답 본문(body)은 무시됩니다.
5초 이내에 응답하세요. 타임아웃이 발생하면 실패로 간주되어 재시도됩니다.
비동기로 처리하세요. 수신한 데이터를 큐나 DB에 저장한 뒤 즉시 200을 반환하고, 실제 비즈니스 로직은 백그라운드에서 처리하는 것을 권장합니다.
Node.js (Express) 예시
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const events = req.body; // JSON 배열
// 즉시 200 응답 반환
res.status(200).send('OK');
// 이후 비동기 처리
for (const event of events) {
console.log(`메시지 ${event.messageId} 상태: ${event.statusCode}`);
// TODO: DB 저장, 알림 발송 등
}
});
app.listen(3000);
Python (Flask) 예시
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
events = request.get_json()
for event in events:
print(f"메시지 {event['messageId']} 상태: {event['statusCode']}")
# TODO: 비동기 처리 큐에 추가
return 'OK', 200
보안 (Secret 검증)
웹훅 등록 시 secret을 설정하면, 매 요청마다 X-Solapi-Secret 헤더에 SHA1 해시값이 포함되어 전송됩니다. 이를 통해 요청이 실제로 Solapi에서 보낸 것인지 검증할 수 있습니다.
검증 방법
웹훅 등록 시 설정한
secret문자열을 SHA1으로 해시합니다.수신한 요청의
X-Solapi-Secret헤더 값과 비교합니다.값이 일치하면 정상 요청입니다.
Node.js 검증 예시
const crypto = require('crypto');
const MY_SECRET = 'my-webhook-secret';
const expectedHash = crypto.createHash('sha1').update(MY_SECRET).digest('hex');
app.post('/webhook', (req, res) => {
const receivedHash = req.headers['x-solapi-secret'];
if (receivedHash !== expectedHash) {
return res.status(401).send('Unauthorized');
}
// 검증 통과 — 정상 처리
res.status(200).send('OK');
});
Python 검증 예시
import hashlib
MY_SECRET = 'my-webhook-secret'
expected_hash = hashlib.sha1(MY_SECRET.encode()).hexdigest()
@app.route('/webhook', methods=['POST'])
def webhook():
received_hash = request.headers.get('X-Solapi-Secret')
if received_hash != expected_hash:
return 'Unauthorized', 401
return 'OK', 200
재시도 정책
웹훅 전송이 실패하면 (200번대 외의 응답 또는 타임아웃) 자동으로 재시도합니다.
재시도 스케줄
총 8회 시도합니다. (최초 1회 + 재시도 7회)
대기 시간은 지수 백오프 방식으로 2배씩 증가합니다.
3회 이상 실패하면 계정 이메일/SMS로 알림이 발송됩니다.
8회 모두 실패하면 웹훅이 비활성화(INACTIVE)됩니다. 콘솔에서 수동으로 재활성화해야 합니다.
페이로드 형식
모든 웹훅 데이터는 JSON 배열 형태로 전달됩니다. 한 번의 요청에 여러 건의 이벤트가 포함될 수 있습니다.
공통 요청 헤더
메시지 리포트 (SINGLE-REPORT)
개별 메시지의 발송 결과입니다.
필드 설명
[
{
"messageId": "M4V202212291118233WFNUDAXMX1A001",
"groupId": "G4V20221229111823FFGLDPAQEP3FF86",
"type": "ATA",
"to": "01000000001",
"from": "029302266",
"statusCode": "4000",
"statusMessage": "수신 완료",
"dateProcessed": "2022-12-29T02:18:40.713Z",
"dateReported": "2022-12-29T02:18:45.000Z",
"dateReceived": "2022-12-29T02:18:44.000Z",
"networkCode": "90901",
"customFields": {
"name": "홍길동"
}
}
]
그룹 리포트 (GROUP-REPORT)
그룹 내 모든 메시지 처리가 완료된 후의 요약 리포트입니다.
주요 필드 설명
[
{
"groupId": "G4V20221229111612XSXKODV6QRNJTJ5",
"accountId": "111111111111",
"apiVersion": "4",
"status": "COMPLETE",
"count": {
"total": 1,
"sentTotal": 1,
"sentFailed": 0,
"sentSuccess": 1,
"sentPending": 0,
"sentReplacement": 0,
"refund": 0,
"registeredFailed": 0,
"registeredSuccess": 1
},
"countForCharge": {
"sms": {},
"lms": {},
"mms": {},
"ata": { "82": 1 },
"cta": {},
"cti": {},
"nsa": {},
"rcs_sms": {},
"rcs_lms": {},
"rcs_mms": {},
"rcs_tpl": {}
},
"balance": {
"requested": 8,
"replacement": 0,
"refund": 0,
"sum": 8
},
"point": {
"requested": 0,
"replacement": 0,
"refund": 0,
"sum": 0
},
"dateSent": "2022-12-29T02:16:16.813Z",
"dateCompleted": "2022-12-29T02:16:21.683Z"
}
]
팩스 수신 (FAX-RECEIVE)
팩스가 수신되었을 때 전달되는 데이터입니다.
필드 설명
[
{
"statusCode": "4000",
"fileId": "ST01FZ230414025210521Z1IazzVahab",
"url": "https://cool-storage.s3.ap-northeast-2.amazonaws.com/486/ST01FZ230414025210521Z1IazzVabcd",
"from": "020000001",
"to": "030309351234",
"pageCount": 2,
"dateReceived": "2023-04-14T01:22:25.000Z"
}
]
API 레퍼런스
웹훅 관리
웹훅 수정 예시
curl -X PUT https://api.solapi.com/webhook/v1/outgoing/WH01WH250321... \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://new-server.com/webhook",
"status": "ACTIVE"
}'
비활성화된 웹훅은
status를"ACTIVE"로 변경하여 재활성화할 수 있습니다.
참고: 코드표
networkCode (통신사 코드)
주요 statusCode (메시지 상태 코드)
전체 상태 코드 목록은 TODO메시지 상태 코드 문서를 참고하세요.
FAQ
Q: 웹훅이 비활성화되었습니다. 어떻게 하나요?
8회 연속 실패하면 웹훅이 자동으로 비활성화됩니다. 수신 서버의 문제를 해결한 뒤 PUT API로 status를 "ACTIVE"로 변경하거나, 콘솔에서 재활성화하세요.
Q: 한 번의 웹훅 요청에 여러 건의 데이터가 올 수 있나요?
네. 페이로드는 항상 JSON 배열이며, 짧은 시간에 다수의 이벤트가 발생하면 한 요청에 묶여서 전달될 수 있습니다. 최대 페이로드 크기는 약 500KB입니다.
Q: HTTPS가 아닌 HTTP URL도 사용할 수 있나요?
가능하지만, 보안을 위해 HTTPS 사용을 강력히 권장합니다.
Q: 웹훅 테스트는 어떻게 하나요?
POST /webhook/v1/outgoing-test API를 사용하면 실제 이벤트 없이도 테스트 페이로드를 수신 서버로 전송할 수 있습니다.
Q: 수신 서버 응답이 느리면 어떻게 되나요?
기본 타임아웃은 5초입니다. 5초 이내에 응답하지 않으면 실패로 처리되어 재시도 대상이 됩니다. 데이터를 빠르게 저장한 뒤 즉시 200을 반환하고, 실제 처리는 비동기로 수행하세요.