Ringkasan
REST API untuk mengirim & memverifikasi kode OTP lewat WhatsApp, memakai Baileys dengan satu nomor WhatsApp khusus. Semua respons berformat JSON.
- Base URL
- https://wa.zaintech.id
- Autentikasi
- Header
X-API-Key - Content-Type
- application/json
{ "success": true, "data": { … } }.
Gagal selalu { "success": false, "error": "KODE_ERROR", "message": "penjelasan" }
dengan HTTP status yang sesuai.
Autentikasi
Setiap endpoint di bawah /api wajib menyertakan API key. Dua cara yang diterima:
X-API-Key: <API_KEY>
# atau
Authorization: Bearer <API_KEY>Tanpa key yang benar server membalas 401 UNAUTHORIZED. Key disimpan di
/usr/share/nginx/html/wa/.env pada variabel API_KEYS (boleh lebih dari satu, dipisah koma).
Format nomor
Nomor dinormalkan otomatis ke format internasional tanpa +. Semua bentuk berikut valid dan
menghasilkan 6281234567890:
| Input | Hasil |
|---|---|
081234567890 | 6281234567890 |
81234567890 | 6281234567890 |
+62 812-3456-7890 | 6281234567890 |
6281234567890 | 6281234567890 |
Kode negara default 62 (ubah lewat DEFAULT_COUNTRY_CODE di .env).
Nomor luar negeri tetap harus diawali + atau kode negaranya.
Hubungkan nomor WhatsApp
Gateway perlu dipasangkan sekali dengan nomor khusus. Sesi tersimpan di folder auth/ dan
bertahan setelah restart. Masukkan API key di bawah untuk memakai panel ini.
- —
- belum dimuat
Di HP: WhatsApp → Perangkat tertaut → Tautkan perangkat → Tautkan dengan nomor telepon, lalu masukkan kode 8 digit ini.
Alur OTP
- Backend Anda memanggil
POST /api/otp/senddengan nomor tujuan. - Gateway membuat kode acak, menyimpan hash-nya, lalu mengirim pesan WhatsApp.
- Pengguna memasukkan kode di aplikasi Anda.
- Backend memanggil
POST /api/otp/verify. Kode terpakai langsung dihapus (sekali pakai).
Endpoint OTP
Membuat kode OTP dan mengirimkannya ke nomor tujuan lewat WhatsApp.
| Field | Tipe | Keterangan | |
|---|---|---|---|
phone | wajib | string | Nomor tujuan. |
appName | opsional | string | Nama layanan di isi pesan. Default Zaintech. |
length | opsional | number | Panjang kode, 4–10. Default 6. |
ttlSeconds | opsional | number | Masa berlaku, 30–3600. Default 300. |
template | opsional | string | Teks pesan. Wajib memuat {{code}}. Placeholder lain: {{app}}, {{ttl}}, {{ttlSeconds}}, {{phone}}. |
reference | opsional | string | Penanda bebas milik Anda, dikembalikan saat verifikasi. |
curl -X POST https://wa.zaintech.id/api/otp/send \
-H "X-API-Key: $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"081234567890","appName":"Zaintech","ttlSeconds":300}'const res = await fetch('https://wa.zaintech.id/api/otp/send', {
method: 'POST',
headers: {
'X-API-Key': process.env.WA_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone: '081234567890', appName: 'Zaintech' }),
})
const { success, data } = await res.json()
// data.requestId, data.expiresAt$res = Http::withHeaders(['X-API-Key' => config('services.wa.key')])
->post('https://wa.zaintech.id/api/otp/send', [
'phone' => $request->phone,
'appName' => 'Zaintech',
]);
if (! $res->json('success')) {
return back()->withErrors($res->json('message'));
}{
"success": true,
"message": "OTP berhasil dikirim.",
"data": {
"requestId": "0f3c9e7a-9f1e-4a2b-8c3d-1e5b7a9d2c44",
"phone": "6281234567890",
"messageId": "3EB0C767D82B0A3F1B2C",
"expiresAt": "2026-08-26T09:15:00.000Z",
"expiresInSeconds": 300,
"codeLength": 6,
"maxAttempts": 5,
"resendAfterSeconds": 60,
"reference": null
}
}Memeriksa kode yang dimasukkan pengguna. Kode yang benar langsung dihapus (sekali pakai).
| Field | Tipe | Keterangan | |
|---|---|---|---|
phone | wajib | string | Nomor yang sama dengan saat kirim. |
code | wajib | string | Kode dari pengguna. |
curl -X POST https://wa.zaintech.id/api/otp/verify \
-H "X-API-Key: $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"081234567890","code":"483920"}'{
"success": true,
"message": "OTP valid.",
"data": { "phone": "6281234567890", "valid": true,
"requestId": "0f3c9e7a-…", "reference": null }
}HTTP/1.1 400 Bad Request
{
"success": false,
"error": "INVALID_CODE",
"message": "Kode OTP salah. Sisa percobaan: 4.",
"data": { "phone": "6281234567890", "valid": false, "attemptsRemaining": 4 }
}Melihat apakah masih ada OTP aktif untuk sebuah nomor, sisa waktu berlaku, sisa percobaan, dan berapa detik lagi boleh kirim ulang. Berguna untuk menampilkan timer “Kirim ulang” di UI.
curl https://wa.zaintech.id/api/otp/status/081234567890 \
-H "X-API-Key: $WA_API_KEY"
{
"success": true,
"data": {
"phone": "6281234567890",
"active": true,
"requestId": "0f3c9e7a-…",
"expiresAt": "2026-08-26T09:15:00.000Z",
"expiresInSeconds": 213,
"attempts": 1,
"attemptsRemaining": 4,
"canResendInSeconds": 27
}
}Membatalkan OTP aktif untuk sebuah nomor, misalnya saat pengguna membatalkan pendaftaran.
curl -X DELETE https://wa.zaintech.id/api/otp/081234567890 \
-H "X-API-Key: $WA_API_KEY"Endpoint pesan
Mengirim pesan teks bebas — untuk notifikasi non-OTP. Maksimal 4096 karakter.
curl -X POST https://wa.zaintech.id/api/message/send \
-H "X-API-Key: $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"081234567890","message":"Pesanan #123 sudah dikirim."}'Mengirim beberapa pesan sekaligus. Dikirim berurutan dengan jeda antar pesan; respons memuat status per nomor.
curl -X POST https://wa.zaintech.id/api/message/bulk \
-H "X-API-Key: $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"messages":[
{"phone":"081234567890","message":"Halo A"},
{"phone":"081298765432","message":"Halo B"}
]}'Memeriksa apakah sebuah nomor terdaftar di WhatsApp — pakai ini sebelum menawarkan OTP via WhatsApp.
curl https://wa.zaintech.id/api/message/check/081234567890 \
-H "X-API-Key: $WA_API_KEY"
{ "success": true,
"data": { "phone":"6281234567890", "registered": true, "jid":"6281234567890@s.whatsapp.net" } }Endpoint sesi
Status koneksi WhatsApp: idle, connecting, waiting_qr,
waiting_pairing, connected, disconnected, atau logged_out.
Termasuk nomor yang terhubung, lama uptime, penyebab putus terakhir, dan hitungan pesan terkirim/gagal.
curl https://wa.zaintech.id/api/session/status -H "X-API-Key: $WA_API_KEY"Mengambil QR pairing saat sesi belum tersambung. Parameter ?format=:
png (default, data-URL), svg, text (string mentah),
terminal (ASCII). Tersedia juga /api/session/qr.png yang mengembalikan berkas PNG langsung.
# tampilkan QR di terminal server
curl -s https://wa.zaintech.id/api/session/qr?format=terminal \
-H "X-API-Key: $WA_API_KEY" | python3 -c "import sys,json;print(json.load(sys.stdin)['data']['image'])"Menerbitkan kode pairing 8 karakter — alternatif scan QR. Field phone opsional bila
WA_NUMBER sudah diisi di .env.
curl -X POST https://wa.zaintech.id/api/session/pairing-code \
-H "X-API-Key: $WA_API_KEY" -H "Content-Type: application/json" \
-d '{"phone":"6281234567890"}'
{ "success": true, "data": { "phone":"6281234567890", "code":"ABCD-1234",
"expiresAt":"2026-08-26T09:12:00.000Z" } }Menyambung ulang socket tanpa menghapus kredensial. Dipakai bila koneksi tersangkut.
Memutus tautan perangkat dan menghapus kredensial sesi. Setelah ini nomor harus dipasangkan ulang lewat QR atau pairing code.
Health check untuk monitoring/uptime. Membalas 200 bila WhatsApp tersambung,
503 bila tidak.
curl -i https://wa.zaintech.id/healthKode error
| HTTP | error | Arti & penanganan |
|---|---|---|
| 400 | INVALID_PHONE | Nomor kosong atau tidak bisa dinormalkan. |
| 400 | INVALID_CODE | Kode salah — cek attemptsRemaining. |
| 400 | EXPIRED | OTP kedaluwarsa, minta yang baru. |
| 400 | NOT_FOUND | Tidak ada OTP aktif untuk nomor itu. |
| 400 | TOO_MANY_ATTEMPTS | Salah > 5 kali; OTP dihanguskan. |
| 400 | INVALID_TEMPLATE | Template tidak memuat {{code}}. |
| 401 | UNAUTHORIZED | API key salah / tidak dikirim. |
| 404 | QR_NOT_READY | QR belum dibuat, coba lagi beberapa detik. |
| 409 | ALREADY_CONNECTED | Sesi sudah aktif; logout dulu bila mau ganti nomor. |
| 422 | NOT_REGISTERED | Nomor tujuan tidak punya WhatsApp. |
| 429 | COOLDOWN | Kirim ulang terlalu cepat; lihat header Retry-After. |
| 429 | QUOTA_EXCEEDED | Kuota OTP per nomor per jam tercapai. |
| 429 | RATE_LIMITED | Terlalu banyak request dari satu IP. |
| 503 | NOT_CONNECTED | Gateway belum terhubung ke WhatsApp — lakukan pairing. |
Rate limit & kebijakan
| Batas | Nilai default | Variabel .env |
|---|---|---|
| Request per IP | 300 / 15 menit | RATE_LIMIT_MAX |
| Pengiriman per IP | 30 / menit | SEND_RATE_LIMIT_MAX |
| Jeda kirim ulang per nomor | 60 detik | OTP_RESEND_COOLDOWN_SECONDS |
| OTP per nomor per jam | 5 | OTP_MAX_PER_HOUR_PER_PHONE |
| Salah kode maksimum | 5 | OTP_MAX_ATTEMPTS |
| Masa berlaku OTP | 300 detik | OTP_TTL_SECONDS |
| Jeda antar pesan keluar | 1200 ms | SEND_INTERVAL_MS |
Operasional
Aplikasi berjalan di 127.0.0.1:3004 di bawah pm2 (nama proses wa-gateway),
di-proxy oleh nginx dengan TLS Let's Encrypt, dan otomatis hidup lagi saat VM di-restart.
pm2 status wa-gateway # cek proses
pm2 logs wa-gateway # lihat log langsung
pm2 restart wa-gateway # restart aplikasi
pm2 save # simpan daftar proses untuk boot berikutnya
cd /usr/share/nginx/html/wa
nano .env # ubah konfigurasi, lalu pm2 restart wa-gateway| Lokasi | Isi |
|---|---|
/usr/share/nginx/html/wa/src | Kode aplikasi |
/usr/share/nginx/html/wa/.env | Konfigurasi & API key |
/usr/share/nginx/html/wa/auth | Kredensial sesi WhatsApp — jangan dihapus |
/usr/share/nginx/html/wa/data | OTP aktif (hash) yang dipersist |
/usr/share/nginx/html/wa/logs | Log pm2 |