Sending has been an API from day one. Registration and review were not — attaching
a Kakao channel, creating an Alimtalk template and submitting it, uploading sender
number paperwork all meant someone logging into a console. For a business building
a product on top of messaging that was the bottleneck: every new merchant meant
"go over there, sign up, and register".

**Not any more.** Registration and review are API now. Your customers only see
your screens.

## What the API covers

| Task | Endpoint |
| --- | --- |
| Kakao channel verify / register / sync | `/v2/kakao-senders` |
| Alimtalk template CRUD, review, approval cancel | `/v2/notice-templates` |
| Brand message template CRUD | `/v2/brand-templates` |
| Kakao image upload | `/v2/kakao-images/{type}` |
| Sender number filing (**all types**) | `/v2/senders` |
| SMS snippet templates | `/v2/message-templates` |
| Opt-out (080) lookup | `/v2/rejected-numbers` |
| Event webhook subscription | `/v2/webhook` |

Exactly one step still involves a person, and it happens **on your screen**: the
Kakao channel verification code. Kakao texts it to the channel administrator and
Sendgo cannot see it either. Your user types it into your form.

## Subscribe to webhooks first

Registration and review are **asynchronous**. Set up delivery before you start.

```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"}'
```

The `secret` in that response is shown **once**. Store it immediately.

Verify signatures against the raw bytes:

```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)          // raw bytes, not a re-encoded object
        .digest('hex');

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

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

    // The same event can arrive twice — dedupe on deliveryId.
    queue.add(event, { data, deliveryId });

    res.sendStatus(204);   // Process in a queue; slow handlers pile up retries.
});
```

Available events:

| Event | Fires when |
| --- | --- |
| `sender.status_changed` | Sender number approved or rejected |
| `notice_template.inspection_status_changed` | Alimtalk review result |
| `kakao_sender.status_changed` | Channel blocked or dormant |
| `kakao_sender.brand_message_status_changed` | Brand message M/N application result |

Use `POST /v2/webhook/test` to confirm the wiring first.

## Registering a Kakao channel

Two calls.

```bash
# Step 1 — Kakao texts a verification code to the channel administrator
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"}'
```

The response does not contain the code. Collect it **on your screen**, then:

```bash
# Step 2 — create the sender profile
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"
      }'
```

Look up `categoryCode` with `GET /api/v2/kakao-senders/categories`. The
`kakaoSenderKey` in the response is what every later Alimtalk and brand message
call uses.

The prerequisites match the console — the channel must exist on
[Kakao Business](https://business.kakao.com) and be converted to a **business
channel**.

### Re-read channel status on a schedule

If Kakao blocks a channel, sending starts failing quietly.

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

Run it daily from cron. Subscribing to `kakao_sender.status_changed` tells you
the moment it changes.

## Filing a sender number

Start by asking what each type requires.

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

**Every type can be filed over the API.** For mobile types the console's PASS
identity verification is replaced by an **ID document** that a Sendgo operator
checks.

| Type | Identity check | Required documents |
| --- | --- | --- |
| `personal_other` (personal landline) | none | usage certificate |
| `personal_mobile` | ID document | usage certificate + ID |
| `team_main` (company landline / corporate phone) | none | usage certificate |
| `team_representative_mobile` | ID document | usage certificate + ID |
| `team_emp_mobile` | ID document | usage certificate + ID + employment certificate |
| `team_other_company` (delegated) | none | usage certificate + 5 delegation documents |

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

A filed number comes back `PENDING`. **Numbers filed with documents are never
auto-approved** — unlike the console's PASS path, which approves personal and
representative mobiles immediately. The result arrives as
`sender.status_changed`.

On rejection, `rejectionReason` carries the reason. Show it to the user, fix the
paperwork, file again.

Calling `POST /api/v2/senders/validate` first lets you report format and
duplicate problems back immediately.

## Alimtalk templates and review

```bash
curl -X POST "https://sendgo.io/api/v2/notice-templates" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "kakaoSenderKey": "abc1234567890def",
        "templateName": "Order received",
        "templateContent": "#{name}, order #{orderNo} has been received.",
        "templateMessageType": "BA",
        "templateEmphasizeType": "NONE",
        "categoryCode": "001001",
        "messagePurpose": "order_delivery",
        "legalBasis": "transaction",
        "benefitOrigin": "none",
        "expiryType": "none",
        "optInReviewConfirmed": true,
        "ctaClearConfirmed": true,
        "policyConfirmed": true
      }'
```

Those last seven fields are **Sendgo's own policy gate**, separate from Kakao's
review, and the API does not bypass them. Reasons come back in `errors.reasons`
and are safe to show the user verbatim. Wording that trips this gate **is usually
rejected by Kakao too** — being told instantly beats waiting days.

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

The outcome arrives as `notice_template.inspection_status_changed`. Without
webhooks, poll `/sync`.

```
created    REG  ← cannot send
submitted  REQ  ← under Kakao review (cancellable)
approved   APR  ← can send
rejected   REJ  ← read comments, fix, resubmit
```

In practice 30 minutes to one business day. **Do not block a deployment pipeline
on it.**

### Templates with images

A brand message `imageUrl` must be a **Kakao-hosted URL**. Upload first, then use
the URL you get back.

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

Alimtalk image templates take the file directly on the create call (multipart).

## Syncing opt-outs

Advertising messages must not go to opted-out numbers. The send API filters them,
but **your own database needs the same state** — otherwise you send and get
filtered every time while your screen still says "subscribed".

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

Daily is enough.

## An onboarding flow that never leaves your product

1. Tell the merchant to open a Kakao business channel (your screen)
2. Take channel id + admin phone → `POST /kakao-senders/token` (automated)
3. **Verification-code screen** → `POST /kakao-senders` (your screen + automated)
4. **Document upload screen** → `POST /senders` (your screen + automated)
5. Bulk-create your standard template set with `POST /notice-templates` (automated)
6. Submit each with `POST .../inspection` (automated)
7. Receive approvals by webhook and notify the merchant (automated)

**sendgo.io never appears in that list.**

## Next steps

- [Registering an Alimtalk template and passing review](/en/cookbook/alimtalk-template) — which wordings get rejected
- [Sender numbers and sender profiles](/en/cookbook/sender-number) — the two kinds of key
- [Error codes and retry strategy](/en/cookbook/error-handling)