WA Backend Docs

Dokumentasi publik untuk setup, API, dashboard, dan Baileys Toolbox.

Simple WhatsApp OTP Backend

Backend WhatsApp OTP ringan untuk aplikasi internal delivery project. Project ini memakai Node.js, Express, dan @whiskeysockets/baileys tanpa Puppeteer, tanpa Chromium, tanpa browser/headless Chrome, dan tanpa whatsapp-web.js.

Fitur

Struktur Folder

.
|-- server.js
|-- package.json
|-- package-lock.json
|-- ecosystem.config.js
|-- .env.example
|-- scripts/
|-- src/
|   |-- config/
|   |-- controllers/
|   |-- middleware/
|   |-- routes/
|   |-- services/
|   `-- utils/
`-- sessions/

Folder sessions/ dibuat otomatis saat bot dijalankan dan tidak boleh dihapus kecuali ingin login ulang.

Catatan Penting untuk Shared Hosting

Di Hostinger shared hosting, biasanya Anda tidak perlu dan tidak bisa setup PM2, Nginx, systemd, atau Certbot manual. Jalankan app lewat menu Node.js di hPanel.

Pastikan paket hosting Anda menyediakan Node.js versi 20 atau lebih baru. Baileys terbaru membutuhkan Node.js >=20.0.0. Jika hPanel hanya menyediakan Node.js versi lebih rendah, app ini tidak akan jalan stabil dan perlu upgrade paket/fitur Node.js atau pindah ke VPS.

Environment

Copy file environment jika Anda deploy via SSH:

cp .env.example .env
nano .env

Jika memakai hPanel Node.js App, Anda bisa isi variable dari menu Environment Variables.

Minimal isi yang wajib:

NODE_ENV=production
PORT=3000
API_KEY=ganti-dengan-secret-yang-panjang
ADMIN_PASSWORD=ganti-dengan-password-dashboard
SESSION_DIR=./sessions
SESSION_BACKUP_DIR=./session-backups
SESSION_BACKUP_ENABLED=true
SESSION_BACKUP_INTERVAL_MINUTES=30
SESSION_BACKUP_KEEP=10
SESSION_LOCK_ENABLED=true
SESSION_LOCK_STALE_MINUTES=10
CORS_ORIGIN=*

Catatan: beberapa shared hosting mengatur PORT otomatis. Jika Hostinger memberi port sendiri di hPanel, gunakan port dari hPanel atau biarkan environment Hostinger yang menentukan.

Deploy di Hostinger Shared Hosting

1. Upload Project

Upload semua file project ke folder aplikasi Node.js, misalnya:

/home/username/domains/wa.example.com/nodeapp/

Jangan upload folder node_modules/. Folder itu dibuat ulang oleh npm install.

File yang perlu ada di hosting:

server.js
package.json
package-lock.json
src/
scripts/
.env
.env.example
ecosystem.config.js
README.md

2. Buat Node.js App di hPanel

Di Hostinger hPanel:

1. Buka website/domain Anda 2. Masuk ke menu Advanced atau Node.js 3. Klik Create Application 4. Pilih Node.js versi 20 atau lebih baru 5. Application mode: Production 6. Application root: folder project, contoh nodeapp 7. Application startup file: server.js 8. Application URL: domain/subdomain yang dipakai, contoh https://wa.example.com

3. Set Environment Variables

Tambahkan variable berikut di hPanel Node.js App atau lewat file .env:

NODE_ENV=production
API_KEY=ganti-dengan-secret-yang-panjang
ADMIN_PASSWORD=ganti-dengan-password-dashboard
SESSION_DIR=./sessions
SESSION_BACKUP_DIR=./session-backups
SESSION_BACKUP_ENABLED=true
SESSION_BACKUP_INTERVAL_MINUTES=30
SESSION_BACKUP_KEEP=10
SESSION_LOCK_ENABLED=true
SESSION_LOCK_STALE_MINUTES=10
WHATSAPP_BROWSER_NAME=Internal Delivery Bot
OTP_EXPIRES_MINUTES=5
OTP_RATE_LIMIT_WINDOW_MINUTES=15
OTP_RATE_LIMIT_MAX=3
OTP_VERIFY_MAX_ATTEMPTS=5
REQUEST_BODY_LIMIT=1mb
LOG_LEVEL=info
BAILEYS_LOG_LEVEL=silent
RECONNECT_BASE_DELAY_MS=3000
RECONNECT_MAX_DELAY_MS=60000
RECONNECT_STABLE_AFTER_MS=60000
RECONNECT_UNSTABLE_THRESHOLD=5
RECONNECT_COOLDOWN_MS=300000
RECONNECT_ON_CONNECTION_REPLACED=false

4. Install Dependency

Di hPanel Node.js App, klik tombol Run NPM Install jika tersedia.

Jika Anda punya SSH:

cd ~/domains/wa.example.com/nodeapp
npm install
npm run check

5. Start atau Restart App

Di hPanel Node.js App, klik Start atau Restart.

Jika memakai SSH dan Hostinger menyediakan command app manager, tetap prioritaskan tombol restart dari hPanel karena proses Node.js shared hosting dikelola oleh Hostinger.

Dashboard Web

Buka domain/subdomain app:

https://wa.example.com/

Halaman awal akan meminta password admin dari environment variable:

ADMIN_PASSWORD=ganti-dengan-password-dashboard

Setelah password benar, dashboard akan menampilkan:

Password admin hanya untuk dashboard. Endpoint REST utama tetap memakai API_KEY.

Jika butuh health check JSON, gunakan:

https://wa.example.com/health

Baileys Toolbox

Backend ini juga punya toolbox advanced berbasis GUI dan REST API berdasarkan dokumentasi Baileys serta type definitions package yang terpasang. Toolbox ini berguna untuk operator internal yang ingin memakai kemampuan Baileys selain OTP.

Endpoint dengan API key:

GET /baileys/capabilities
POST /baileys/action

Endpoint dashboard dengan password admin:

GET /admin/api/baileys/capabilities
POST /admin/api/baileys/action

Kategori action yang tersedia:

Dokumentasi lengkap payload ada di BAILEYS_TOOLBOX.md.

Di dashboard, semua action tersebut muncul sebagai daftar kategori dan action. Parameter sederhana menjadi field GUI otomatis, sedangkan parameter kompleks seperti array/object tetap memakai textarea JSON agar cocok dengan struktur asli Baileys.

Cara Scan QR di Shared Hosting

Cara paling mudah adalah buka dashboard di /, masukkan ADMIN_PASSWORD, lalu scan QR yang tampil di halaman.

QR juga tetap dicetak ke log aplikasi karena Baileys memakai printQRInTerminal: true.

Di hPanel:

1. Buka menu Node.js App 2. Pilih aplikasi bot ini 3. Buka Application Logs atau Logs 4. Cari QR WhatsApp yang muncul saat app pertama kali start 5. Scan dari WhatsApp:

WhatsApp > Settings > Linked Devices > Link a Device

Setelah berhasil scan, session tersimpan di folder sessions/. Restart normal tidak perlu scan QR lagi.

Agar Session Tidak Hilang

Session WhatsApp disimpan di folder:

sessions/

Backup otomatis disimpan di:

session-backups/

Jangan hapus kedua folder ini saat upload ulang project ke Hostinger. Kalau upload lewat File Manager, upload file source saja dan biarkan folder sessions/ serta session-backups/ tetap ada.

Dashboard punya panel Session Guard untuk:

Setelah restore backup, restart aplikasi dari hPanel agar Baileys reconnect memakai session yang dipulihkan.

Jika Bot Down Up Terus

Kalau status terlihat down, lalu up 1 detik, lalu down lagi terus-menerus, penyebab paling sering adalah dua proses Node.js memakai folder sessions/ yang sama, atau WhatsApp menganggap koneksi lama digantikan koneksi baru.

App ini punya pengaman:

Langkah perbaikan di Hostinger shared hosting:

1. Buka hPanel > Node.js App 2. Klik Stop 3. Tunggu 10-20 detik sampai proses lama benar-benar mati 4. Klik Start 5. Buka dashboard dan cek panel Status serta Session Guard

Jangan menjalankan app yang sama dari hPanel dan SSH secara bersamaan. Satu nomor WhatsApp dan satu folder sessions/ hanya boleh dipakai oleh satu proses aktif.

Jika QR tidak muncul:

1. Cek endpoint /status 2. Pastikan status bukan open 3. Restart app dari hPanel 4. Buka logs lagi 5. Jika status logged_out, hapus folder sessions/, lalu restart app

Restart Bot di Shared Hosting

Restart normal:

hPanel > Node.js App > Restart

Login ulang dari nol:

hPanel > Node.js App > Stop
hapus folder sessions/
hPanel > Node.js App > Start
buka logs dan scan QR baru

Jika punya SSH:

cd ~/domains/wa.example.com/nodeapp
rm -rf sessions

Lalu restart dari hPanel.

Melihat Logs di Shared Hosting

Gunakan:

hPanel > Node.js App > Logs

Jika tersedia SSH, lokasi log bisa berbeda tergantung konfigurasi Hostinger. Cara paling aman tetap lewat hPanel.

SSL dan Domain di Hostinger Shared Hosting

Untuk shared hosting, SSL tidak perlu di-setup dengan Certbot. Aktifkan dari hPanel:

hPanel > Websites > Manage > Security > SSL

Pastikan domain/subdomain aplikasi Node.js sudah memakai HTTPS sebelum dipakai dari Laravel atau aplikasi internal.

Reverse Proxy di Shared Hosting

Tidak perlu setup Nginx manual. hPanel Node.js App sudah menghubungkan URL aplikasi ke process Node.js.

Gunakan konfigurasi Nginx hanya kalau Anda pindah ke VPS.

Opsional: PM2 Jika Nanti Pindah ke VPS

File ecosystem.config.js sudah disediakan, tapi tidak dipakai di Hostinger shared hosting.

Jika nanti pindah ke VPS:

npm install -g pm2
pm2 start ecosystem.config.js
pm2 save
pm2 startup

Untuk logs PM2:

pm2 logs simple-wa-otp-backend

API Authentication

Semua endpoint API selain / wajib memakai header:

x-api-key: ganti-dengan-secret-yang-panjang

Atau:

Authorization: Bearer ganti-dengan-secret-yang-panjang

Endpoint

GET /status

curl -X GET https://wa.example.com/status \
  -H "x-api-key: ganti-dengan-secret-yang-panjang"

Contoh response:

{
  "success": true,
  "message": "Status fetched",
  "status": "open",
  "connected": true,
  "qrAvailable": false,
  "user": {
    "id": "628xxxx@s.whatsapp.net",
    "name": "Bot"
  },
  "reconnectAttempts": 0,
  "unstableDisconnects": 0,
  "nextReconnectAt": null,
  "sessionLock": {
    "enabled": true,
    "active": true,
    "pid": 12345
  },
  "lastDisconnectReason": null
}

POST /send-otp

Body:

{
  "phone": "628xxxx"
}

Nomor 08xxxx otomatis dinormalisasi menjadi 628xxxx.

Format pesan WhatsApp:

Kode OTP Anda: 123456. Berlaku 5 menit.

Request:

curl -X POST https://wa.example.com/send-otp \
  -H "Content-Type: application/json" \
  -H "x-api-key: ganti-dengan-secret-yang-panjang" \
  -d '{"phone":"081234567890"}'

Response:

{
  "success": true,
  "message": "OTP sent",
  "otp": "hidden"
}

POST /verify-otp

Body:

{
  "phone": "628xxxx",
  "otp": "123456"
}

Request:

curl -X POST https://wa.example.com/verify-otp \
  -H "Content-Type: application/json" \
  -H "x-api-key: ganti-dengan-secret-yang-panjang" \
  -d '{"phone":"081234567890","otp":"123456"}'

Response:

{
  "success": true,
  "message": "OTP verified",
  "verified": true
}

POST /send-message

Body:

{
  "phone": "628xxxx",
  "message": "Pesanan Anda sedang dikirim."
}

Request:

curl -X POST https://wa.example.com/send-message \
  -H "Content-Type: application/json" \
  -H "x-api-key: ganti-dengan-secret-yang-panjang" \
  -d '{"phone":"081234567890","message":"Pesanan Anda sedang dikirim."}'

Response:

{
  "success": true,
  "message": "Message sent",
  "to": "6281234567890",
  "messageId": "ABCDEF123456"
}

Rate Limit OTP

Default:

OTP_RATE_LIMIT_WINDOW_MINUTES=15
OTP_RATE_LIMIT_MAX=3

Artinya satu nomor hanya bisa meminta OTP maksimal 3 kali per 15 menit.

OTP disimpan sementara dengan Map() memory cache. Jika process restart, OTP yang belum diverifikasi akan hilang. Ini sesuai untuk service ringan di satu shared hosting selama process hanya berjalan satu instance.

Contoh Integrasi Laravel

Tambahkan ke .env Laravel:

WA_OTP_BASE_URL=https://wa.example.com
WA_OTP_API_KEY=ganti-dengan-secret-yang-panjang

Tambahkan ke config/services.php:

'wa_otp' => [
    'base_url' => env('WA_OTP_BASE_URL'),
    'api_key' => env('WA_OTP_API_KEY'),
],

Kirim OTP:

use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'x-api-key' => config('services.wa_otp.api_key'),
])->post(config('services.wa_otp.base_url') . '/send-otp', [
    'phone' => $user->phone,
]);

if ($response->successful() && $response->json('success')) {
    // OTP terkirim
}

Verifikasi OTP:

use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'x-api-key' => config('services.wa_otp.api_key'),
])->post(config('services.wa_otp.base_url') . '/verify-otp', [
    'phone' => $request->phone,
    'otp' => $request->otp,
]);

if ($response->successful() && $response->json('verified')) {
    // OTP valid
}

Kirim notifikasi sederhana:

use Illuminate\Support\Facades\Http;

Http::withHeaders([
    'x-api-key' => config('services.wa_otp.api_key'),
])->post(config('services.wa_otp.base_url') . '/send-message', [
    'phone' => $order->customer_phone,
    'message' => 'Pesanan Anda sedang dikirim.',
]);

Catatan Production

Baileys Toolbox

Baileys Toolbox

File ini merangkum kemampuan Baileys yang diekspos oleh backend ini melalui whitelist action. Sumber utama:

Catatan: backend ini tidak mengekspos raw socket method seperti query, sendRawMessage, sendNode, atau relayMessage karena terlalu mudah merusak session atau melanggar batas WhatsApp. Semua action di bawah tetap butuh WhatsApp terhubung kecuali status tertentu.

Endpoint

REST API dengan API key:

GET /baileys/capabilities
POST /baileys/action

Dashboard admin dengan password:

GET /admin/api/baileys/capabilities
POST /admin/api/baileys/action

Di dashboard /, semua action juga tersedia sebagai GUI:

1. Buka halaman utama. 2. Login dengan ADMIN_PASSWORD. 3. Buka panel Baileys Toolbox. 4. Pilih kategori. 5. Pilih action. 6. Isi field yang muncul otomatis. 7. Jalankan action.

Field sederhana dirender sebagai input/select/checkbox. Field array atau object dirender sebagai textarea JSON karena struktur datanya mengikuti tipe Baileys seperti WAMessageKey, contacts, atau ChatModification.

Session WhatsApp sendiri diamankan lewat panel Session Guard di dashboard. Panel itu berada di luar Baileys Toolbox karena tugasnya menjaga folder multi-file auth state agar tidak hilang saat deploy atau restart.

Body standar action:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "type": "text",
    "text": "Halo dari Baileys toolbox"
  }
}

Account

Contoh cek nomor:

{
  "action": "account.onWhatsApp",
  "params": {
    "phones": ["081234567890"]
  }
}

Messages

message.send mendukung content builder berikut:

Text:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "type": "text",
    "text": "Pesanan Anda sedang dikirim."
  }
}

Image dari URL:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "type": "image",
    "url": "https://example.com/image.jpg",
    "caption": "Bukti pengiriman"
  }
}

Document dari URL:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "type": "document",
    "url": "https://example.com/invoice.pdf",
    "mimetype": "application/pdf",
    "fileName": "invoice.pdf",
    "caption": "Invoice"
  }
}

Location:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "type": "location",
    "latitude": -8.6705,
    "longitude": 115.2126,
    "name": "Lokasi kurir",
    "address": "Denpasar"
  }
}

Poll:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "type": "poll",
    "name": "Pilih jadwal antar",
    "values": ["Pagi", "Siang", "Sore"],
    "selectableCount": 1
  }
}

Raw content object untuk format Baileys yang belum dibuatkan builder:

{
  "action": "message.send",
  "params": {
    "to": "081234567890",
    "content": {
      "text": "Raw content tetap lewat sendMessage Baileys"
    }
  }
}

Action lain:

Presence

Contoh typing:

{
  "action": "presence.update",
  "params": {
    "type": "composing",
    "to": "081234567890"
  }
}

Profile

Contoh ambil foto profil:

{
  "action": "profile.pictureUrl",
  "params": {
    "jid": "081234567890",
    "type": "image"
  }
}

Privacy

Contoh block:

{
  "action": "privacy.updateBlockStatus",
  "params": {
    "jid": "081234567890",
    "action": "block"
  }
}

Groups

Contoh list group:

{
  "action": "group.list",
  "params": {}
}

Contoh tambah peserta:

{
  "action": "group.participantsUpdate",
  "params": {
    "jid": "1203630xxxx@g.us",
    "participants": ["081234567890"],
    "action": "add"
  }
}

Business

Contoh business profile:

{
  "action": "business.profile",
  "params": {
    "jid": "081234567890"
  }
}

Newsletter / Channel

Contoh metadata:

{
  "action": "newsletter.metadata",
  "params": {
    "type": "jid",
    "key": "1203630xxxx@newsletter"
  }
}

Chat, Contacts, Calls

Contoh call link:

{
  "action": "call.createLink",
  "params": {
    "type": "video"
  }
}

Cara Cek Daftar Terbaru dari API

curl -X GET https://wa.example.com/baileys/capabilities \
  -H "x-api-key: API_KEY_KAMU"

Response berisi daftar action, kategori, parameter ringkas, dan URL sumber dokumentasi.