WA·Gateway

Dokumentasi API

Integrasi WhatsApp Gateway

WA Gateway menyediakan REST API JSON dan stream Server-Sent Events. Aplikasi apa pun yang bisa melakukan HTTP request bisa mengirim & menerima pesan WhatsApp.

Langkah 1

Tautkan

POST /api/login lalu scan QR dari /api/status

Langkah 2

Dengarkan

GET /api/events untuk pesan masuk realtime

Langkah 3

Balas

POST /api/send ke field chat dari pesan

Base URL & format

Semua endpoint berada di bawah prefix /api. Request & response memakai JSON (Content-Type: application/json). Base URL dashboard ini:

Base URL
https://wa-be.jackzstore.id

Error selalu berbentuk objek dengan satu field error dan status HTTP 4xx/5xx:

Error
{ "error": "whatsapp belum terhubung" }

Keamanan

API belum memiliki autentikasi. Siapa pun yang bisa menjangkau port backend dapat mengirim pesan atas nama nomor Anda. Jalankan di jaringan privat, atau pasang di belakang reverse proxy dengan API key / basic auth. Atur origin yang diizinkan lewat env CORS_ORIGINS.
GET/api/health

Health check

Memastikan proses backend hidup. Tidak bergantung pada status WhatsApp.

Request
curl https://wa-be.jackzstore.id/api/health
Response 200
{ "status": "ok" }
GET/api/status

Status koneksi

Mengembalikan objek WAState. Saat status qr, field qr berisi gambar PNG (data URL) yang bisa langsung dipakai di <img src>.

Request
curl https://wa-be.jackzstore.id/api/status
Response 200
{
  "status": "connected",
  "jid": "6281234567890@s.whatsapp.net",
  "pushName": "Toko Sejahtera"
}
POST/api/login

Mulai login QR

Memulai pairing. QR berganti otomatis tiap ~20 detik; ikuti lewat event state atau polling /api/status. Mengembalikan 202 Accepted.

Request
curl -X POST https://wa-be.jackzstore.id/api/login
Response 202
{ "status": "connecting" }
400 Sudah login, atau gagal membuka koneksi
POST/api/pair-phone

Login dengan kode pairing

Alternatif QR: menghasilkan kode 8 karakter yang dimasukkan di HP lewat Perangkat tertaut → Tautkan dengan nomor telepon.

BodyTipeKeterangan
phone*stringNomor yang akan ditautkan. Format 08xx atau 628xx; karakter non-digit diabaikan.
Request
curl -X POST https://wa-be.jackzstore.id/api/pair-phone \
  -H 'Content-Type: application/json' \
  -d '{"phone":"6281234567890"}'
Response 200
{ "code": "ABCD-1234" }
400 Body tidak valid, nomor tidak valid, atau sudah login
POST/api/send

Kirim pesan teks

Mengirim teks ke nomor atau grup dan mengembalikan objek WAMessage yang terkirim. Nomor dicek dulu dengan IsOnWhatsApp.

BodyTipeKeterangan
to*stringNomor (08xx / 628xx) atau JID lengkap: …@s.whatsapp.net untuk pribadi, …@g.us untuk grup.
message*stringIsi pesan teks.
Request
curl -X POST https://wa-be.jackzstore.id/api/send \
  -H 'Content-Type: application/json' \
  -d '{"to":"081234567890","message":"Halo dari API"}'
Response 200
{
  "id": "3EB0C431D2A1B6F1A2B3",
  "chat": "6281234567890@s.whatsapp.net",
  "sender": "6281234567890@s.whatsapp.net",
  "text": "Halo, pesanan saya sudah dikirim?",
  "type": "text",
  "fromMe": true,
  "isGroup": false,
  "timestamp": "2026-10-13T09:41:00+07:00"
}
400 Body tidak valid, field kosong, atau nomor tidak valid502 Nomor tidak terdaftar di WhatsApp / gagal mengirim503 WhatsApp belum terhubung
GET/api/messages

Daftar pesan

Pesan masuk & keluar yang ditampung di memori (maks. 500, hilang saat backend restart). Gunakan since agar hanya pesan baru yang diambil, misalnya saat resync setelah koneksi SSE terputus.

QueryTipeKeterangan
sinceintegerUnix timestamp dalam milidetik. Hanya pesan dengan timestamp ≥ nilai ini yang dikembalikan.
Request
curl https://wa-be.jackzstore.id/api/messages?since=1791866460000
Response 200
[
  {
    "id": "3EB0C431D2A1B6F1A2B3",
    "chat": "6281234567890@s.whatsapp.net",
    "sender": "6281234567890@s.whatsapp.net",
    "pushName": "Rina",
    "text": "Halo, pesanan saya sudah dikirim?",
    "type": "text",
    "fromMe": false,
    "isGroup": false,
    "timestamp": "2026-10-13T09:41:00+07:00"
  }
]
400 Parameter since bukan angka
POST/api/broadcasts

Mulai broadcast

Mengirim satu pesan ke banyak nomor secara bertahap di server (tetap berjalan walau klien terputus). Nomor duplikat dibuang, nomor tidak valid langsung ditandai gagal. Hanya satu broadcast yang boleh aktif. Progres dikirim lewat event broadcast.

BodyTipeKeterangan
tostring[]Daftar nomor (08xx / 628xx) atau JID tanpa personalisasi.
recipients{ to, vars? }[]Penerima dengan variabel personalisasi, mis. { "to": "0812…", "vars": { "nama": "Rina" } }. Bisa digabung dengan to; total maks. 5000.
message*stringTemplate pesan: {nama}, {nama:default}, dan spintax {Halo|Hai}. Lihat bagian Personalisasi.
delayMin*integerDelay minimum antar pesan (detik, ≥ 1).
delayMax*integerDelay maksimum (detik). Delay diacak di antara min dan max.
batchSizeintegerIstirahat setiap N pesan. 0 = tanpa istirahat.
batchSleepintegerLama istirahat (detik) setelah setiap batch.
Request
curl -X POST https://wa-be.jackzstore.id/api/broadcasts \
  -H 'Content-Type: application/json' \
  -d '{"recipients":[{"to":"081234567890","vars":{"nama":"Rina","kota":"Bandung"}},{"to":"089876543210","vars":{"nama":"Budi"}}],"message":"{Halo|Hai} {nama:Kak}! Promo untuk {kota:kotamu} sudah dimulai.","delayMin":10,"delayMax":20,"batchSize":20,"batchSleep":300}'
Response 202
{
  "id": "a1b2c3d4e5f6",
  "status": "running",
  "total": 2, "sent": 0, "failed": 0,
  "settings": { "delayMin": 10, "delayMax": 20, "batchSize": 20, "batchSleep": 300 },
  "recipients": [
    { "to": "6281234567890", "vars": { "nama": "Rina", "kota": "Bandung" }, "status": "pending" },
    { "to": "6289876543210", "vars": { "nama": "Budi" }, "status": "pending" }
  ]
}
400 Input tidak valid (pesan kosong, delay < 1, dll.)409 Masih ada broadcast yang berjalan atau dijeda503 WhatsApp belum terhubung
GET/api/broadcasts/{id}

Detail & progres broadcast

Status per nomor (pending, sent, failed + alasan). GET /api/broadcasts mengembalikan ringkasan 20 broadcast terakhir (disimpan di memori, hilang saat backend restart).

Request
curl https://wa-be.jackzstore.id/api/broadcasts/a1b2c3d4e5f6
Response 200
{
  "id": "a1b2c3d4e5f6",
  "status": "running",
  "note": "Istirahat setelah 20 pesan",
  "nextAt": "2026-10-13T10:05:00+07:00",
  "total": 120, "sent": 40, "failed": 1,
  "recipients": [
    { "to": "6281234567890", "status": "sent", "sentAt": "…",
      "text": "Hai Rina! Promo untuk Bandung sudah dimulai." },
    …
  ]
}
404 Broadcast tidak ditemukan
POST/api/broadcasts/{id}/{action}

Jeda, lanjutkan, batalkan

action berisi pause, resume, atau cancel. Bila WhatsApp terputus di tengah jalan, broadcast otomatis dijeda dan nomor yang sedang diproses tidak dilewati; panggil resume setelah terhubung.

Request
curl -X POST https://wa-be.jackzstore.id/api/broadcasts/a1b2c3d4e5f6/pause
Response 200
{ "id": "a1b2c3d4e5f6", "status": "paused", … }
400 Status tidak sesuai aksi, atau aksi tidak dikenal
POST/api/logout

Logout

Memutus tautan perangkat dan menghapus sesi dari database. Perlu scan ulang untuk login.

Request
curl -X POST https://wa-be.jackzstore.id/api/logout
Response 200
{ "status": "disconnected" }
400 Belum login
GET/api/events

Server-Sent Events

Koneksi HTTP yang tetap terbuka dan mengirim event saat sesuatu terjadi. Saat tersambung, server langsung mengirim satu event state. Komentar : ping dikirim tiap 20 detik agar koneksi tidak diputus proxy.

EventTipeKeterangan
stateWAStateStatus koneksi berubah (QR baru, terhubung, logout, …).
messageWAMessagePesan masuk, atau pesan keluar (termasuk yang dikirim dari HP). Pesan broadcast tidak dikirim lewat event ini.
broadcast{ broadcast, index?, recipient? }Progres broadcast: ringkasan terbaru, plus nomor yang baru diproses.
Setelah reconnect, WhatsApp dapat mengirim ulang pesan yang tertunda selama offline dengan timestamp lama. Bandingkan timestamp dengan waktu aplikasi Anda mulai mendengarkan bila hanya ingin memproses pesan baru. Dashboard memakai cara ini.
const since = Date.now()
const es = new EventSource("https://wa-be.jackzstore.id/api/events")

es.addEventListener("state", (e) => {
  const state = JSON.parse(e.data)
  console.log("status:", state.status)
})

es.addEventListener("message", (e) => {
  const msg = JSON.parse(e.data)
  // Abaikan pesan lama (mis. pesan offline yang baru tersinkron)
  if (new Date(msg.timestamp).getTime() < since) return
  console.log(msg.pushName ?? msg.sender, ":", msg.text)
})

Objek WAState

FieldTipeKeterangan
status*"disconnected" | "connecting" | "qr" | "connected"Status koneksi saat ini.
jidstringJID akun yang tertaut (hanya saat connected).
pushNamestringNama profil WhatsApp akun.
qrstringQR code sebagai data URL PNG (hanya saat status qr).
qrExpirynumberKedaluwarsa QR, Unix milidetik.
errorstringAlasan terakhir terputus / gagal pairing.

Objek WAMessage

FieldTipeKeterangan
id*stringID pesan dari WhatsApp.
chat*stringJID room: nomor lawan bicara atau grup (…@g.us).
sender*stringJID pengirim (di grup berbeda dengan chat).
pushNamestringNama profil pengirim (pesan masuk saja).
text*stringTeks / caption / nama file. Kosong untuk audio, stiker, dsb.
type*stringtext, image, video, document, audio, sticker, location, contact, poll, reaction, other.
fromMe*booleantrue bila dikirim dari akun ini.
isGroup*booleantrue bila pesan grup.
timestamp*string (RFC 3339)Waktu pesan menurut server WhatsApp.
Contoh
{
  "id": "3EB0C431D2A1B6F1A2B3",
  "chat": "6281234567890@s.whatsapp.net",
  "sender": "6281234567890@s.whatsapp.net",
  "pushName": "Rina",
  "text": "Halo, pesanan saya sudah dikirim?",
  "type": "text",
  "fromMe": false,
  "isGroup": false,
  "timestamp": "2026-10-13T09:41:00+07:00"
}

Format nomor & JID

WhatsApp mengidentifikasi akun & grup dengan JID. Field to menerima nomor biasa atau JID:

081234567890→ 6281234567890@s.whatsapp.net
+62 812-3456-7890→ 6281234567890@s.whatsapp.net
1203630…@g.usdipakai apa adanya
2384…@liddipakai apa adanya

Untuk membalas pesan, kirim ke field chat dari pesan tersebut, sehingga balasan grup masuk ke grup dan balasan pribadi ke pengirim.

Personalisasi broadcast

Field message pada broadcast adalah template yang dirender di server untuk setiap penerima. Teks hasil render disimpan di field text tiap penerima.

{nama}Nilai variabel nama dari vars penerima (huruf besar/kecil tidak berpengaruh). Kosong bila tidak ada.
{nama:Kak}Seperti di atas, tetapi memakai “Kak” bila nilainya kosong.
{nomor}Nomor tujuan (selalu tersedia).
{Halo|Hai|Hei}Spintax: satu opsi dipilih acak per penerima, agar pesan tidak identik.
Contoh
Template : {Halo|Hai} {nama:Kak}! Pesanan {invoice} siap diambil di {cabang}.
vars     : { "nama": "Rina", "invoice": "INV-1042", "cabang": "Bandung" }
Hasil    : Hai Rina! Pesanan INV-1042 siap diambil di Bandung.

Di dashboard, variabel diambil dari tabel yang ditempel: baris pertama adalah header dengan kolom nomor (nomor, no_hp, phone, wa, …), dan setiap kolom lain otomatis menjadi variabel.

Penjadwalan pesan

Gateway mengirim pesan seketika saat /api/send dipanggil, sehingga penjadwalan cukup dilakukan dari scheduler yang sudah Anda pakai: cron, task scheduler aplikasi, atau workflow tool seperti n8n.

# Setiap tanggal 1 pukul 08.00: pengingat tagihan
0 8 1 * *  curl -s -X POST https://wa-be.jackzstore.id/api/send \
  -H 'Content-Type: application/json' \
  -d '{"to":"081234567890","message":"Tagihan bulan ini sudah terbit"}'

# Setiap hari kerja pukul 07.30: laporan ke grup tim
30 7 * * 1-5  /usr/local/bin/kirim-laporan.sh
Beri jeda beberapa detik antar pesan saat mengirim ke banyak nomor, dan kirim hanya ke kontak yang memang mengharapkan pesan Anda. Pola kirim massal yang agresif adalah penyebab utama nomor diblokir WhatsApp.

Contoh bot auto-reply

Mendengarkan pesan pribadi baru dan membalas ping dengan pong. Pola yang sama bisa dipakai untuk meneruskan pesan ke webhook, CRM, atau LLM.

// bot.mjs — Node 18+
import { EventSource } from "eventsource" // npm i eventsource

const API = "https://wa-be.jackzstore.id"
const startedAt = Date.now()
const es = new EventSource(`${API}/api/events`)

es.addEventListener("message", async (e) => {
  const msg = JSON.parse(e.data)
  if (msg.fromMe || msg.isGroup) return
  if (new Date(msg.timestamp).getTime() < startedAt) return

  if (msg.text.trim().toLowerCase() === "ping") {
    await fetch(`${API}/api/send`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ to: msg.chat, message: "pong 🏓" }),
    })
  }
})