Dokumentasi SunWa
Dua credential, dua tujuan. Device Token untuk bot per-sesi, API Key untuk manajemen akun.
SUNWA_URL=https://api.sunwa.web.id/apiSUNWA_DEVICE_TOKEN=Device Token — terikat ke satu sesi WhatsApp (sunwa_token_...)SUNWA_API_KEY=API Key — akses level akun (sunwa_keymaster_...)SUNWA_WEBHOOK_SECRET=Secret untuk verifikasi webhook (plaintext, salin dari dashboard)Cara Kerja
Pesan Masuk
User kirim pesan ke nomor WhatsApp kamu
WEBHOOK
SunWa POST ke webhook URL kamu: messageId, from, content, type, media
REST API — Balas
Panggil API pakai Device Token atau return JSON langsung di response webhook
import express from 'express'
import { createHmac } from 'crypto'
const app = express()
app.use(express.json())
const SUNWA_URL = process.env.SUNWA_URL // https://api.sunwa.app
const TOKEN = process.env.SUNWA_DEVICE_TOKEN // sunwa_token_...
const SECRET = process.env.SUNWA_WEBHOOK_SECRET // plaintext hex from dashboard
const headers = { 'Authorization': TOKEN, 'Content-Type': 'application/json' }
// Helper — panggil SunWa API
const sunwa = (path, body) =>
fetch(`${SUNWA_URL}${path}`, {
method: 'POST', headers, body: JSON.stringify(body),
}).then(r => r.json())
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
// Terima webhook dari SunWa
app.post('/webhook', async (req, res) => {
// 1. Verifikasi signature
const body = JSON.stringify(req.body)
const sig = req.headers['x-sunwa-signature']
const expected = `sha256=${createHmac('sha256', SECRET).update(body).digest('hex')}`
if (sig !== expected) return res.status(403).end()
const { event, data } = req.body
if (event !== 'message.received') return res.status(200).end()
// Segera ACK webhook — proses di background
res.status(200).json({ ok: true })
const { from, content, messageId } = data
// 2. Mark as read
await sunwa('/sessions/messages/read-receipt', { to: from, messageId })
// 3. Typing indicator
await sunwa('/sessions/messages/presence', { presence: 'composing', to: from })
await sleep(1000 + Math.random() * 2000) // ketik 1-3 detik
// 4. Stop typing
await sunwa('/sessions/messages/presence', { presence: 'paused', to: from })
// 5. Balas
await sunwa('/send', {
to: from,
message: `Halo ${from}, ada yang bisa dibantu?`,
quotedMessageId: messageId,
})
})
app.listen(3000, () => console.log('Webhook server ready 🚀'))Device Token
Per-sesi — untuk bot builder
Bisa:
- ✓ Kirim semua tipe pesan
- ✓ Baca kontak & grup sesi ini
- ✓ Cek info akun
Tidak bisa:
- ✗ Manajemen sesi (create/delete)
- ✗ Akses lintas sesi
- ✗ Generate/rotasi token
- ✗ Webhook config, test, & history
Dapatkan Device Token di Console → Sessions → Setup.
curl -X POST $SUNWA_URL/send \
-H "Authorization: $SUNWA_DEVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to":"6281234567890","message":"Hello from SunWa!"}'Endpoint
POST/sendUnified send — auto-detect type dari field▶
/sendUnified send — auto-detect type dari fieldRequest
// Text
{ "to": "6281234567890", "message": "Halo!" }
// Media + caption (type auto-inferred from URL)
{ "to": "6281234567890", "url": "https://example.com/photo.jpg", "message": "Caption" }
// Poll
{ "to": "6281234567890", "pollName": "Makan dimana?", "options": ["Sushi", "Ramen"] }
// Location
{ "to": "6281234567890", "latitude": -6.2, "longitude": 106.8, "locationName": "Jakarta" }
// Reaction
{ "to": "6281234567890", "messageId": "3EB0...", "reaction": "👍" }
// Quoted reply
{ "to": "6281234567890", "message": "Setuju!", "quotedMessageId": "3EB0..." }Response 200
{
"success": true,
"data": {
"messageId": "3EB0A1B2C3D4E5F6",
"to": "6281234567890@s.whatsapp.net",
"status": "sent"
}
}POST/sessions/messages/textKirim teks▶
/sessions/messages/textKirim teksRequest
{
"to": "6281234567890",
"message": "Halo dari SunWa!"
}Response 200
{
"success": true,
"data": {
"messageId": "3EB0A1B2C3D4E5F6",
"to": "6281234567890@s.whatsapp.net",
"status": "sent"
}
}POST/sessions/messages/media-batchKirim file (upload / dari URL)▶
/sessions/messages/media-batchKirim file (upload / dari URL)Request
// multipart/form-data (max 10 files) // Field: to = "6281234567890" // Field: caption = "Ini caption (opsional)" // Field: sendAsDocument = "false" (opsional) // Files: photo.jpg, document.pdf // curl example: // curl -X POST /api/sessions/messages/media-batch \ // -H "Authorization: Bearer sunwa_token_xxx" \ // -F "to=6281234567890" \ // -F "caption=Ini caption" \ // -F "files=@photo.jpg"
Response 200
{
"success": true,
"data": {
"sent": 2,
"failed": 0,
"total": 2,
"results": [
{ "fileName": "photo.jpg", "messageId": "3EB0A1...", "status": "sent" },
{ "fileName": "document.pdf", "messageId": "3EB0A2...", "status": "sent" }
]
}
}POST/sessions/messages/quotedReply dengan quote▶
/sessions/messages/quotedReply dengan quoteRequest
{
"to": "6281234567890",
"message": "Ini balasan",
"quotedMessageId": "3EB0A1B2C3D4E5F6"
}Response 200
{
"success": true,
"data": {
"messageId": "3EB0C3D4E5F6A7B8",
"to": "6281234567890@s.whatsapp.net",
"status": "sent",
"type": "text"
}
}POST/sessions/messages/pollKirim polling
/sessions/messages/pollKirim pollingPOST/sessions/messages/locationKirim lokasi
/sessions/messages/locationKirim lokasiPOST/sessions/messages/contactsKirim kartu kontak
/sessions/messages/contactsKirim kartu kontakPOST/sessions/messages/reactionKirim reaksi emoji
/sessions/messages/reactionKirim reaksi emojiPOST/sessions/messages/editEdit pesan
/sessions/messages/editEdit pesanPOST/sessions/messages/deleteHapus pesan
/sessions/messages/deleteHapus pesanPOST/sessions/messages/presenceKirim typing/online▶
/sessions/messages/presenceKirim typing/onlineRequest
{
"presence": "composing",
"to": "6281234567890"
}Response 200
{
"success": true,
"data": {
"presence": "composing",
"to": "6281234567890@s.whatsapp.net"
}
}POST/sessions/messages/read-receiptMark as read▶
/sessions/messages/read-receiptMark as readRequest
{
"to": "6281234567890",
"messageId": "3EB0A1B2C3D4E5F6"
}Response 200
{
"success": true,
"data": { "read": true }
}POST/sessions/messages/check-numberCek nomor di WA
/sessions/messages/check-numberCek nomor di WAGET/sessions/contactsKontak sesi ini (+ search)▶
/sessions/contactsKontak sesi ini (+ search)Response 200
{
"success": true,
"data": [
{
"id": "uuid-...",
"jid": "6281234567890@s.whatsapp.net",
"name": "Budi",
"phone": "6281234567890",
"isBusiness": false
}
],
"meta": {
"count": 42,
"lastSyncedAt": "2025-01-15T10:30:00.000Z"
}
}POST/sessions/contacts/syncSync kontak dari WhatsApp
/sessions/contacts/syncSync kontak dari WhatsAppGET/sessions/groupsGrup sesi ini (+ search)
/sessions/groupsGrup sesi ini (+ search)POST/sessions/groups/syncSync grup dari WhatsApp
/sessions/groups/syncSync grup dari WhatsAppGET/auth/meInfo akun & sesi saat ini▶
/auth/meInfo akun & sesi saat iniResponse 200
{
"success": true,
"data": {
"id": "uuid-...",
"email": "user@example.com",
"name": "Budi",
"role": "user",
"plan": "pro",
"planExpiresAt": "2025-12-31T23:59:59.000Z",
"isActive": true,
"authMethod": "device-key",
"apiKeySessionId": null,
"planLimits": {
"sessions": 5,
"messagesPerDay": 5000,
"mediaUploadMB": 16,
"documentUploadMB": 50
}
}
}Webhook
Event, inline reply, dan verifikasi
Event
Message event → webhookUrl. Session event → webhookBackupUrl.
Inline Reply
Return JSON di response webhook (message.received only).
delay — jeda sebelum balas (ms, max 10000)
imageMimeType — MIME type untuk imageUrl (default: image/jpeg)
Payload message.received
{
"event": "message.received",
"sessionId": "uuid-...",
"timestamp": "2026-06-14T12:00:00.000Z",
"data": {
"messageId": "3EB0...",
"from": "62812@s.whatsapp.net",
"senderJid": "62812@s.whatsapp.net",
"content": "Halo",
"type": "text",
"timestamp": "2026-06-14T12:00:00.000Z",
// For media messages (image/video/audio/document):
"media": {
"url": "https://...",
"mimeType": "image/jpeg",
"fileSize": 12345,
"fileName": "photo.jpg",
"whatsAppUrl": "https://mmg.whatsapp.net/...",
"encryptionKey": "base64..."
}
}
}Decrypt Media dari Webhook
Setiap pesan media (foto/video/audio/dokumen) menyertakan whatsAppUrl + encryptionKey. Download dari CDN WhatsApp dan decrypt sendiri untuk disimpan ke storage kamu.
import { downloadMediaMessage } from '@whiskeysockets/baileys'
// Atau gunakan library AES-256-CBC decryption manual
// Dari webhook payload:
const { whatsAppUrl, encryptionKey, mimeType } = data.media
// 1. Download encrypted blob
const encBuffer = await fetch(whatsAppUrl).then(r => r.arrayBuffer())
// 2. Decrypt with AES-256-CBC (key = base64decode(encryptionKey))
// 3. Strip 16-byte HKDF header, save decrypted file
// 4. Upload to your S3/R2/GCS
// url field = already-decrypted URL hosted by SunWa (ready to use)
// whatsAppUrl + encryptionKey = raw CDN access for self-hosted storageurlFile siap pakai (hosted SunWa)
whatsAppUrlCDN WhatsApp (encrypted)
encryptionKeyBase64 AES key untuk decrypt
Verifikasi Signature
Setiap webhook dikirim dengan HMAC-SHA256. Verifikasi di server kamu.
Node.js
import { createHmac } from 'crypto'
app.post('/webhook', (req, res) => {
const body = JSON.stringify(req.body)
const sig = req.headers['x-sunwa-signature']
if (!sig) return res.status(401).end()
const expected = `sha256=${createHmac('sha256', process.env.SUNWA_WEBHOOK_SECRET).update(body).digest('hex')}`
if (sig !== expected) return res.status(403).end()
// verified ✓
}PHP
$sig = $_SERVER['HTTP_X_SUNWA_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('SUNWA_WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig)) {
http_response_code(403); exit;
}
// verified ✓API Key (Master)
Level akun — untuk manajemen & integrasi multi-sesi
Bisa:
- ✓ Buat, hapus, kelola sesi
- ✓ Generate & rotasi Device Token
- ✓ Kirim pesan ke sesi manapun (sertakan session di body)
- ✓ Akses kontak & grup lintas sesi
- ✓ Semua yang bisa Device Token
Dapatkan API Key di Console → Developer. Membutuhkan plan Pro/Enterprise.
# API Key — sertakan "session" (sessionId) untuk memilih device
curl -X POST $SUNWA_URL/send \
-H "X-Api-Key: $SUNWA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"session":"$SESSION_ID","to":"6281234567890","message":"Hello!"}'Endpoint
GET/sessionsList semua sesi▶
/sessionsList semua sesiResponse 200
{
"success": true,
"data": [
{
"id": "uuid-...",
"name": "Customer Support",
"phone": "6281234567890",
"status": "connected",
"runtimeStatus": "connected",
"webhookUrl": "https://your-server.com/webhook",
"webhookActive": true,
"tokenPrefix": "sunwa_token_a1b2",
"lastConnected": "2025-01-15T08:00:00.000Z",
"totalUptime": 86400,
"totalDowntime": 120,
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}POST/sessionsBuat sesi baru▶
/sessionsBuat sesi baruRequest
{
"name": "Customer Support"
}Response 200
{
"success": true,
"data": {
"id": "uuid-...",
"name": "Customer Support",
"phone": null,
"status": "disconnected",
"createdAt": "2025-01-15T10:00:00.000Z"
}
}GET/sessions/:idDetail sesi
/sessions/:idDetail sesiPUT/sessions/:idUpdate nama / webhook
/sessions/:idUpdate nama / webhookDELETE/sessions/:idHapus sesi
/sessions/:idHapus sesiPOST/sessions/:id/connectHubungkan via QR
/sessions/:id/connectHubungkan via QRPOST/sessions/:id/connect/pairingHubungkan via pairing code
/sessions/:id/connect/pairingHubungkan via pairing codePOST/sessions/:id/disconnectPutuskan + hapus auth
/sessions/:id/disconnectPutuskan + hapus authPOST/sessions/:id/offlineOffline sementara
/sessions/:id/offlineOffline sementaraPOST/sessions/:id/onlineResume dari offline
/sessions/:id/onlineResume dari offlinePOST/sessions/:id/reconnectReconnect tanpa scan
/sessions/:id/reconnectReconnect tanpa scanGET/sessions/:id/statusStatus koneksi runtime▶
/sessions/:id/statusStatus koneksi runtimeResponse 200
{
"success": true,
"data": {
"status": "connected",
"phone": "6281234567890",
"platform": "android",
"batteryLevel": 85,
"isCharging": true,
"connectedAt": "2025-01-15T08:00:00.000Z",
"uptime": 3600
}
}POST/sessions/:id/tokenGenerate Device Token▶
/sessions/:id/tokenGenerate Device TokenResponse 200
{
"success": true,
"data": {
"token": "sunwa_token_a1b2c3d4e5f6...",
"prefix": "sunwa_token_a1b2"
}
}GET/sessions/:id/tokenInfo Device Token
/sessions/:id/tokenInfo Device TokenPOST/sessions/:id/token/rotateRotasi Device Token
/sessions/:id/token/rotateRotasi Device TokenDELETE/sessions/:id/tokenHapus Device Token
/sessions/:id/tokenHapus Device TokenPOST/sendKirim — body { session, to, message }
/sendKirim — body { session, to, message }PUT/sessions/:idUpdate nama / webhook config
/sessions/:idUpdate nama / webhook configGET/sessions/:id/webhook-secretAmbil webhook secret
/sessions/:id/webhook-secretAmbil webhook secretPOST/sessions/:id/webhook/testTest webhook
/sessions/:id/webhook/testTest webhookGET/sessions/:id/webhook/historyRiwayat delivery webhook
/sessions/:id/webhook/historyRiwayat delivery webhookDELETE/sessions/:id/webhook/historyHapus riwayat webhook
/sessions/:id/webhook/historyHapus riwayat webhookGET/sessions/all/contactsSemua kontak lintas sesi▶
/sessions/all/contactsSemua kontak lintas sesiResponse 200
{
"success": true,
"data": [
{
"id": "uuid-...",
"sessionId": "uuid-...",
"jid": "6281234567890@s.whatsapp.net",
"name": "Budi",
"phone": "6281234567890",
"isBusiness": false,
"session": {
"id": "uuid-...",
"name": "Customer Support",
"phone": "6289876543210"
}
}
],
"meta": {
"count": 10,
"total": 42,
"page": 1,
"perPage": 10,
"totalPages": 5
}
}GET/sessions/all/groupsSemua grup lintas sesi
/sessions/all/groupsSemua grup lintas sesiGET/auth/meInfo akun (plan, authMethod)
/auth/meInfo akun (plan, authMethod)SESSION_LIMITJumlah sesi melebihi batas planSESSION_ID_REQUIREDPakai API Key tapi tidak ada session di bodySESSION_NOT_CONNECTEDSesi belum terhubung ke WhatsAppTOKEN_EXISTSToken sudah ada — gunakan /token/rotateINVALID_TOKENDevice Token tidak validINVALID_API_KEYAPI Key tidak validSCOPE_FORBIDDENDevice Token dipakai di luar cakupannyaPLAN_UPGRADE_REQUIREDFitur membutuhkan upgrade plan—Resource tidak ditemukanPLAN_LIMITBatas pengiriman pesan tercapai—Too many requests—Server error