API

웹훅

작성일 2026.03.21 | 수정일 2026.03.21

웹훅은 메시지 발송 결과, 그룹 처리 완료, 팩스 수신 등의 이벤트가 발생했을 때 지정한 URL로 데이터를 실시간 전달하는 기능입니다. 폴링 없이도 이벤트를 즉시 수신할 수 있어 효율적인 시스템 연동이 가능합니다.

지원 이벤트

NameDescription
SINGLE-REPORT개별 메시지의 발송 결과가 확정되면 호출됩니다.
GROUP-REPORT그룹 내 모든 메시지 처리가 완료되면 호출됩니다.
FAX-RECEIVE팩스가 수신되면 호출됩니다.

사용 가능한 이벤트 목록은 GET /webhook/v1/events API로도 조회할 수 있습니다.


웹훅 등록

요청

POST /webhook/v1/outgoing

파라미터

NameTypeRequiredDescription
eventIdStringtrue수신할 이벤트 ID (SINGLE-REPORT, GROUP-REPORT, FAX-RECEIVE)
urlStringtrue이벤트 데이터를 수신할 URL (HTTPS 권장)
secretString수신 서버에서 요청의 출처를 검증하기 위한 비밀 키

요청 예시

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 요청으로 전달됩니다.

핵심 규칙

  1. HTTP 200 응답을 반환하세요. 200번대 상태 코드를 반환하면 성공으로 처리됩니다. 응답 본문(body)은 무시됩니다.

  2. 5초 이내에 응답하세요. 타임아웃이 발생하면 실패로 간주되어 재시도됩니다.

  3. 비동기로 처리하세요. 수신한 데이터를 큐나 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에서 보낸 것인지 검증할 수 있습니다.

검증 방법

  1. 웹훅 등록 시 설정한 secret 문자열을 SHA1으로 해시합니다.

  2. 수신한 요청의 X-Solapi-Secret 헤더 값과 비교합니다.

  3. 값이 일치하면 정상 요청입니다.

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번대 외의 응답 또는 타임아웃) 자동으로 재시도합니다.

재시도 스케줄

시도대기 시간누적 경과
1차 (최초)즉시-
2차15분15분
3차30분45분
4차1시간1시간 45분
5차2시간3시간 45분
6차4시간7시간 45분
7차8시간15시간 45분
8차(최종)16시간31시간 45분
  • 8회 시도합니다. (최초 1회 + 재시도 7회)

  • 대기 시간은 지수 백오프 방식으로 2배씩 증가합니다.

  • 3회 이상 실패하면 계정 이메일/SMS로 알림이 발송됩니다.

  • 8회 모두 실패하면 웹훅이 비활성화(INACTIVE)됩니다. 콘솔에서 수동으로 재활성화해야 합니다.


페이로드 형식

모든 웹훅 데이터는 JSON 배열 형태로 전달됩니다. 한 번의 요청에 여러 건의 이벤트가 포함될 수 있습니다.

공통 요청 헤더

헤더설명
Content-Typeapplication/json
X-Solapi-Event-Name이벤트 이름 (예: SINGLE-REPORT)
X-Solapi-SecretSecret의 SHA1 해시값 (secret 설정 시에만 포함)

메시지 리포트 (SINGLE-REPORT)

개별 메시지의 발송 결과입니다.

필드 설명

NameTypeDescription
messageIdString메시지 고유 ID
groupIdString메시지가 속한 그룹 ID
typeString메시지 유형 (SMS, LMS, MMS, ATA, CTA, CTI, NSA, RCS_SMS, RCS_LMS, RCS_MMS, RCS_TPL, FAX, VOICE)
toString수신 번호
fromString발신 번호
statusCodeString메시지 상태 코드 (하단 코드표 참고)
statusMessageString상태 메시지
dateProcessedString처리 완료 시각 (ISO 8601)
dateReportedString리포트 수신 시각 (ISO 8601)
dateReceivedString단말 수신 시각 (ISO 8601)
networkCodeString통신사 코드 (하단 코드표 참고)
customFieldsObject발송 시 설정한 사용자 정의 필드
[
  {
    "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)

그룹 내 모든 메시지 처리가 완료된 후의 요약 리포트입니다.

주요 필드 설명

NameTypeDescription
groupIdString그룹 고유 ID
accountIdString계정 ID
statusString그룹 상태 (COMPLETE 등)
countObject메시지 건수 통계
totalNumber전체 건수
sentSuccessNumber발송 성공 건수
sentFailedNumber발송 실패 건수
sentPendingNumber처리 대기 건수
countForChargeObject메시지 유형별 과금 건수
balanceObject잔액 사용 내역
pointObject포인트 사용 내역
dateSentString발송 시각 (ISO 8601)
dateCompletedString처리 완료 시각 (ISO 8601)
[
  {
    "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)

팩스가 수신되었을 때 전달되는 데이터입니다.

필드 설명

NameTypeDescription
statusCodeString상태 코드
fileIdString수신된 팩스 파일 ID
urlString팩스 파일 다운로드 URL
fromString발신 번호
toString수신 번호
pageCountNumber팩스 페이지 수
dateReceivedString팩스 수신 시각 (ISO 8601)
[
  {
    "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 레퍼런스

웹훅 관리

메서드엔드포인트설명
POST/webhook/v1/outgoing웹훅 등록
GET/webhook/v1/outgoing웹훅 목록 조회
PUT/webhook/v1/outgoing/:webhookId웹훅 수정
DELETE/webhook/v1/outgoing/:webhookId웹훅 삭제
GET/webhook/v1/events사용 가능한 이벤트 목록 조회
POST/webhook/v1/outgoing-test테스트 웹훅 전송

웹훅 수정 예시

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 (통신사 코드)

코드통신사
45005SKT
45008KT
45006LG U+
45099기타
90901카카오
90902네이버
99999알 수 없음

주요 statusCode (메시지 상태 코드)

코드설명
2000정상 접수
3000발송 중 (처리 대기)
4000수신 완료 (성공)
5000수신 실패

전체 상태 코드 목록은 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을 반환하고, 실제 처리는 비동기로 수행하세요.