발송은 처음부터 API였습니다. 그런데 그 앞 단계 — 카카오 채널을 붙이고, 알림톡
템플릿을 만들어 검수를 넣고, 발신번호 서류를 올리는 일 — 은 사람이 콘솔에
들어와야 했습니다. 메시지를 얹어 서비스를 만드는 사업자에게는 이게 병목이었죠.
입점사가 늘 때마다 "여기 가서 가입하고 등록하세요"를 반복해야 했으니까요.

**이제 그럴 필요가 없습니다.** 등록·심사 전부가 API입니다. 고객은 여러분
화면만 봅니다.

## 무엇이 API로 되나

| 작업 | 엔드포인트 |
| --- | --- |
| 카카오 채널 인증·등록·동기화 | `/v2/kakao-senders` |
| 알림톡 템플릿 CRUD·검수 요청·승인 취소 | `/v2/notice-templates` |
| 브랜드메시지 템플릿 CRUD | `/v2/brand-templates` |
| 카카오 이미지 업로드 | `/v2/kakao-images/{type}` |
| 발신번호 심사 접수 (**전 유형**) | `/v2/senders` |
| 문자 상용구 템플릿 | `/v2/message-templates` |
| 수신거부(080) 조회 | `/v2/rejected-numbers` |
| 이벤트 웹훅 구독 | `/v2/webhook` |

사람이 개입하는 지점은 하나뿐이고, 그마저도 **여러분 화면에서** 끝납니다 —
카카오 채널 인증번호입니다. 카카오가 채널 관리자 휴대폰으로 SMS를 보내고
샌드고도 그 값을 볼 수 없습니다. 사용자가 그 번호를 여러분 화면에 입력하면
됩니다.

## 웹훅부터 구독한다

등록·심사는 **비동기**입니다. 먼저 받을 준비를 해 두는 편이 낫습니다.

```bash
curl -X PUT "https://sendgo.io/api/v2/webhook" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://reseller.example.com/hooks/sendgo"}'
```

응답의 `secret` 은 **이때 한 번만** 나옵니다. 즉시 저장하세요.

받는 쪽은 원본 바이트로 서명을 검증합니다.

```javascript
app.post('/hooks/sendgo', express.raw({ type: 'application/json' }), (req, res) => {
    const expected = crypto
        .createHmac('sha256', process.env.SENDGO_WEBHOOK_SECRET)
        .update(req.body)          // 파싱한 객체가 아니라 받은 바이트 그대로
        .digest('hex');

    if (expected !== req.get('X-Sendgo-Signature')) return res.sendStatus(401);

    const { event, data, deliveryId } = JSON.parse(req.body.toString('utf8'));

    // 같은 이벤트가 두 번 올 수 있다 — deliveryId 로 걸러낸다.
    queue.add(event, { data, deliveryId });

    res.sendStatus(204);   // 처리는 큐로. 여기서 오래 끌면 재시도가 쌓인다.
});
```

구독할 수 있는 이벤트:

| 이벤트 | 언제 |
| --- | --- |
| `sender.status_changed` | 발신번호 승인·반려 |
| `notice_template.inspection_status_changed` | 알림톡 검수 결과 |
| `kakao_sender.status_changed` | 채널 차단·휴면 |
| `kakao_sender.brand_message_status_changed` | 브랜드메시지 M/N 신청 결과 |

`POST /v2/webhook/test` 로 배선을 먼저 확인하세요.

## 카카오 채널 등록

두 번 호출합니다.

```bash
# 1단계 — 카카오가 관리자 휴대폰으로 인증번호 SMS 발송
curl -X POST "https://sendgo.io/api/v2/kakao-senders/token" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"yellowId":"@my-channel","phoneNumber":"01012345678"}'
```

응답에 인증번호는 없습니다. **여러분 화면에서** 사용자가 입력하게 한 뒤:

```bash
# 2단계 — 인증번호로 발신프로필 생성
curl -X POST "https://sendgo.io/api/v2/kakao-senders" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "token": "123456",
        "yellowId": "@my-channel",
        "phoneNumber": "01012345678",
        "categoryCode": "001001"
      }'
```

`categoryCode` 는 `GET /api/v2/kakao-senders/categories` 로 조회합니다.
응답의 `kakaoSenderKey` 가 이후 모든 알림톡·브랜드메시지 호출에 쓰는 값입니다.

사전 조건은 콘솔과 같습니다 — [카카오 비즈니스](https://business.kakao.com)에서
채널을 만들고 **비즈니스 채널**로 전환해 둬야 합니다.

### 채널 상태는 주기적으로 다시 읽으세요

채널이 카카오 쪽에서 차단되면 발송이 조용히 실패하기 시작합니다.

```bash
curl -X POST "https://sendgo.io/api/v2/kakao-senders/sync" \
  -H "Authorization: Bearer $TOKEN"
```

하루 한 번 크론으로 돌리세요. `kakao_sender.status_changed` 웹훅을 구독해
두면 변화가 있을 때 알려 줍니다.

## 발신번호 심사 접수

먼저 어떤 유형에 무슨 서류가 필요한지 확인합니다.

```bash
curl "https://sendgo.io/api/v2/senders/number-types" \
  -H "Authorization: Bearer $TOKEN"
```

**모든 유형을 API로 접수할 수 있습니다.** 휴대폰 계열은 콘솔의 PASS 본인인증
대신 **신분증 사본**을 받아 sendgo 운영자가 직접 확인합니다.

| 유형 | 본인확인 | 필수 서류 |
| --- | --- | --- |
| `personal_other` (개인 유선) | 불필요 | 이용증명원 |
| `personal_mobile` (개인 휴대폰) | 신분증 | 이용증명원 + 신분증 |
| `team_main` (자사 유선/법인폰) | 불필요 | 이용증명원 |
| `team_representative_mobile` (대표자 휴대폰) | 신분증 | 이용증명원 + 신분증 |
| `team_emp_mobile` (재직자 휴대폰) | 신분증 | 이용증명원 + 신분증 + 재직증명서 |
| `team_other_company` (타사 위임) | 불필요 | 이용증명원 + 수임/위임 5종 |

```bash
curl -X POST "https://sendgo.io/api/v2/senders" \
  -H "Authorization: Bearer $TOKEN" \
  -F "senderAlias=대표자 휴대폰" \
  -F "senderNumberType=team_representative_mobile" \
  -F "phoneE164=01012345678" \
  -F "csuCertificate=@csu.pdf" \
  -F "identityDocument=@id-card.jpg"
```

접수되면 `status: "PENDING"` 입니다. **서류 경로로 들어온 건은 자동 승인되지
않습니다** — 콘솔의 PASS 경로가 개인·대표자 휴대폰을 즉시 승인하는 것과
다릅니다. 운영자 확인 후 `sender.status_changed` 웹훅으로 결과가 옵니다.

반려되면 `rejectionReason` 에 사유가 담깁니다. 그대로 사용자에게 보여 주고
서류를 보완해 재접수하면 됩니다.

등록 전 `POST /api/v2/senders/validate` 로 형식과 중복을 미리 확인해 두면
사용자에게 빠르게 되돌려 줄 수 있습니다.

## 알림톡 템플릿 등록과 검수 요청

```bash
curl -X POST "https://sendgo.io/api/v2/notice-templates" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "kakaoSenderKey": "abc1234567890def",
        "templateName": "주문 접수 안내",
        "templateContent": "#{name}님, 주문 #{orderNo}이 접수되었습니다.",
        "templateMessageType": "BA",
        "templateEmphasizeType": "NONE",
        "categoryCode": "001001",
        "messagePurpose": "order_delivery",
        "legalBasis": "transaction",
        "benefitOrigin": "none",
        "expiryType": "none",
        "optInReviewConfirmed": true,
        "ctaClearConfirmed": true,
        "policyConfirmed": true
      }'
```

아래 일곱 개는 **샌드고 자체 정책 게이트**입니다. 카카오 심사와 별개이며
API라고 우회되지 않습니다.

| 필드 | 뜻 |
| --- | --- |
| `messagePurpose` | 메시지 목적 |
| `legalBasis` | 발송 근거 |
| `benefitOrigin` | 혜택 발생 경위 |
| `expiryType` | 소멸 유형 |
| `optInReviewConfirmed` | 사전동의 검토 확인 |
| `ctaClearConfirmed` | 유도 문구 미포함 확인 |
| `policyConfirmed` | 발신자 정책 확인 |

목적과 본문이 어긋나면 `POLICY_VALIDATION_FAILED` 로 저장 자체가 막힙니다.
응답 `errors.reasons` 에 사유가 한국어로 담기니 그대로 사용자에게 보여 주면
됩니다. 여기서 걸리는 문안은 **카카오 심사에서도 거의 반려**되므로, 며칠
기다렸다 반려당하는 것보다 즉시 아는 편이 낫습니다.

```bash
curl -X POST "https://sendgo.io/api/v2/notice-templates/TPL-20260911-0001/inspection" \
  -H "Authorization: Bearer $TOKEN"
```

검수 결과는 `notice_template.inspection_status_changed` 웹훅으로 옵니다.
웹훅을 쓰지 않는다면 `/sync` 로 폴링하세요.

```
등록      REG  ← 발송 불가
검수 요청  REQ  ← 카카오 심사 중 (취소 가능)
승인      APR  ← 발송 가능
반려      REJ  ← comments 확인 후 수정·재요청
```

실무에서는 30분에서 1영업일 걸립니다. **배포 파이프라인 안에서 동기적으로
기다리게 만들지 마세요.**

### 이미지가 들어가는 템플릿

브랜드메시지의 `imageUrl` 은 **카카오가 호스팅하는 URL**이어야 합니다.
먼저 올리고 받은 URL을 넣습니다.

```bash
curl -X POST "https://sendgo.io/api/v2/kakao-images/default" \
  -H "Authorization: Bearer $TOKEN" \
  -F "image=@banner.jpg"
# → data.imageUrl
```

알림톡 이미지 템플릿은 등록 호출에 파일을 함께 실으면 됩니다(multipart).

## 수신거부 동기화

광고성 메시지는 수신거부한 번호로 못 보냅니다. 발송 API가 알아서 제외하지만,
**여러분 DB의 수신 상태도 맞춰야** 합니다 — 그러지 않으면 매번 보내고 매번
걸러지는 것을 반복하고, 여러분 화면에서는 여전히 "수신 동의"로 보입니다.

```bash
curl "https://sendgo.io/api/v2/rejected-numbers?since=2026-09-01&count=500" \
  -H "Authorization: Bearer $TOKEN"
```

하루 한 번이면 충분합니다.

## 온보딩 플로우 설계 예시

입점사가 여러분 화면만으로 연동을 마치는 순서입니다.

1. 입점사에게 카카오 비즈니스 채널 개설 안내 (여러분 화면)
2. 채널 아이디 + 관리자 휴대폰 입력 → `POST /kakao-senders/token` (자동)
3. **인증번호 입력 화면** → `POST /kakao-senders` (여러분 화면 + 자동)
4. **발신번호 서류 업로드 화면** → `POST /senders` (여러분 화면 + 자동)
5. 표준 템플릿 세트를 `POST /notice-templates` 로 일괄 등록 (자동)
6. 각각 `POST .../inspection` 으로 검수 요청 (자동)
7. 웹훅으로 승인·반려를 받아 입점사에게 알림 (자동)

**sendgo.io 가 등장하는 단계가 없습니다.**

## 다음 단계

- [알림톡 템플릿 등록과 심사 통과하기](/ko/cookbook/alimtalk-template) — 어떤 문안이 반려되는지
- [발신번호와 발신프로필 등록](/ko/cookbook/sender-number) — 두 가지 키의 차이
- [오류 코드와 재시도 전략](/ko/cookbook/error-handling)