JavaScript Required

SunWa requires JavaScript to run. Please enable JavaScript in your browser settings.

Dokumentasi SunWa

Dua credential, dua tujuan. Device Token untuk bot per-sesi, API Key untuk manajemen akun.

.env
SUNWA_URL=https://api.sunwa.web.id/api
SUNWA_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

1

Pesan Masuk

User kirim pesan ke nomor WhatsApp kamu

2

WEBHOOK

SunWa POST ke webhook URL kamu: messageId, from, content, type, media

POST https://your-server.com/webhook
3

REST API — Balas

Panggil API pakai Device Token atau return JSON langsung di response webhook

POST /send + Authorization: sunwa_token_...
Response: {"reply": "Halo!", "quoted": true}
server.js — Contoh lengkap Node.jsexpress + crypto
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

Device Token

Per-sesi — untuk bot builder

Header: Authorization: sunwa_token_...

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 — Device Token
curl -X POST $SUNWA_URL/send \
  -H "Authorization: $SUNWA_DEVICE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"to":"6281234567890","message":"Hello from SunWa!"}'

Endpoint

18
MetodePath / Keterangan
POST
/sendUnified send — auto-detect type dari field

Request

// 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

Request

{
  "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)

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

Request

{
  "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
POST
/sessions/messages/locationKirim lokasi
POST
/sessions/messages/contactsKirim kartu kontak
POST
/sessions/messages/reactionKirim reaksi emoji
POST
/sessions/messages/editEdit pesan
POST
/sessions/messages/deleteHapus pesan
POST
/sessions/messages/presenceKirim typing/online

Request

{
  "presence": "composing",
  "to": "6281234567890"
}

Response 200

{
  "success": true,
  "data": {
    "presence": "composing",
    "to": "6281234567890@s.whatsapp.net"
  }
}
POST
/sessions/messages/read-receiptMark as read

Request

{
  "to": "6281234567890",
  "messageId": "3EB0A1B2C3D4E5F6"
}

Response 200

{
  "success": true,
  "data": { "read": true }
}
POST
/sessions/messages/check-numberCek nomor di WA
GET
/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
GET
/sessions/groupsGrup sesi ini (+ search)
POST
/sessions/groups/syncSync grup dari WhatsApp
GET
/auth/meInfo akun & sesi saat ini

Response 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.receivedmessage.sentmessage.deliveredmessage.readsession.connectedsession.disconnectedsession.qr

Message event → webhookUrl. Session event → webhookBackupUrl.

Inline Reply

Return JSON di response webhook (message.received only).

{ "reply": "Terima kasih!", "quoted": true }
{ "imageUrl": "https://...", "reply": "caption" }
{ "reply": "Tunggu...", "delay": 2000 }

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 storage
url

File siap pakai (hosted SunWa)

whatsAppUrl

CDN WhatsApp (encrypted)

encryptionKey

Base64 AES key untuk decrypt

Verifikasi Signature

Setiap webhook dikirim dengan HMAC-SHA256. Verifikasi di server kamu.

X-SunWa-Signature: sha256=4f1c...
X-SunWa-Event: message.received
X-SunWa-Session: session-uuid

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

API Key (Master)

Level akun — untuk manajemen & integrasi multi-sesi

Header: X-Api-Key: sunwa_keymaster_...

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.

curl — API Key
# 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

25
MetodePath / Keterangan
GET
/sessionsList semua sesi

Response 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

Request

{
  "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
PUT
/sessions/:idUpdate nama / webhook
DELETE
/sessions/:idHapus sesi
POST
/sessions/:id/connectHubungkan via QR
POST
/sessions/:id/connect/pairingHubungkan via pairing code
POST
/sessions/:id/disconnectPutuskan + hapus auth
POST
/sessions/:id/offlineOffline sementara
POST
/sessions/:id/onlineResume dari offline
POST
/sessions/:id/reconnectReconnect tanpa scan
GET
/sessions/:id/statusStatus koneksi runtime

Response 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

Response 200

{
  "success": true,
  "data": {
    "token": "sunwa_token_a1b2c3d4e5f6...",
    "prefix": "sunwa_token_a1b2"
  }
}
GET
/sessions/:id/tokenInfo Device Token
POST
/sessions/:id/token/rotateRotasi Device Token
DELETE
/sessions/:id/tokenHapus Device Token
POST
/sendKirim — body { session, to, message }
PUT
/sessions/:idUpdate nama / webhook config
GET
/sessions/:id/webhook-secretAmbil webhook secret
POST
/sessions/:id/webhook/testTest webhook
GET
/sessions/:id/webhook/historyRiwayat delivery webhook
DELETE
/sessions/:id/webhook/historyHapus riwayat webhook
GET
/sessions/all/contactsSemua kontak lintas sesi

Response 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
GET
/auth/meInfo akun (plan, authMethod)
Error Codes
{
"success": false,
"error": "human-readable message",
"code": "MACHINE_CODE"
}
HTTPCodeArtinya
400SESSION_LIMITJumlah sesi melebihi batas plan
400SESSION_ID_REQUIREDPakai API Key tapi tidak ada session di body
400SESSION_NOT_CONNECTEDSesi belum terhubung ke WhatsApp
400TOKEN_EXISTSToken sudah ada — gunakan /token/rotate
401INVALID_TOKENDevice Token tidak valid
401INVALID_API_KEYAPI Key tidak valid
403SCOPE_FORBIDDENDevice Token dipakai di luar cakupannya
403PLAN_UPGRADE_REQUIREDFitur membutuhkan upgrade plan
404Resource tidak ditemukan
429PLAN_LIMITBatas pengiriman pesan tercapai
429Too many requests
500Server error