기업회원 신청 서류 안내
신청자 구분에 따라 재직증명서 또는 위임장을 함께 제출해 주세요.
신청자 구분에 따라 재직증명서 또는 위임장을 함께 제출해 주세요.
A PRACTICAL GUIDE
Register Kakao channels, submit Alimtalk templates for review, file sender numbers, and sync opt-outs without ever sending anyone to sendgo.io. Includes webhook delivery of review outcomes.
POST /api/v2/notice-templatesSending 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.
| 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.
Registration and review are asynchronous. Set up delivery before you start.
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:
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.
Two calls.
# 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:
# 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 and be converted to a business channel.
If Kakao blocks a channel, sending starts failing quietly.
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.
Start by asking what each type requires.
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 |
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 "[email protected]" \
-F "[email protected]"
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.
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.
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.
A brand message imageUrl must be a Kakao-hosted URL. Upload first, then use
the URL you get back.
curl -X POST "https://sendgo.io/api/v2/kakao-images/default" \
-H "Authorization: Bearer $TOKEN" \
-F "[email protected]"
# → data.imageUrl
Alimtalk image templates take the file directly on the create call (multipart).
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".
curl "https://sendgo.io/api/v2/rejected-numbers?since=2026-09-01&count=500" \
-H "Authorization: Bearer $TOKEN"
Daily is enough.
POST /kakao-senders/token (automated)POST /kakao-senders (your screen + automated)POST /senders (your screen + automated)POST /notice-templates (automated)POST .../inspection (automated)sendgo.io never appears in that list.
How Kakao Alimtalk template review works, why informational-only matters, how to lay out variables, and the wordings that get rejected.
Every error code returned by the Sendgo send endpoints, and how to tell a failure worth retrying from one that will fail identically every time.
Shorten long URLs to fit inside an SMS and measure who clicked. Creating short links, reading click stats, and splitting stats per campaign.