WhatsApp OTP Gateway

wa.zaintech.id
memeriksa…

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
Bentuk respons. Sukses selalu { "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).

Jangan menaruh API key di kode front-end / aplikasi mobile. Panggil gateway ini dari backend Anda saja.

Format nomor

Nomor dinormalkan otomatis ke format internasional tanpa +. Semua bentuk berikut valid dan menghasilkan 6281234567890:

InputHasil
0812345678906281234567890
812345678906281234567890
+62 812-3456-78906281234567890
62812345678906281234567890

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.

Status sesi
belum dimuat
Opsi B — Pairing code (tanpa scan)

Di HP: WhatsApp → Perangkat tertaut → Tautkan perangkat → Tautkan dengan nomor telepon, lalu masukkan kode 8 digit ini.

Opsi A — Scan QR
QR muncul di sini saat sesi belum tersambung

Alur OTP

  1. Backend Anda memanggil POST /api/otp/send dengan nomor tujuan.
  2. Gateway membuat kode acak, menyimpan hash-nya, lalu mengirim pesan WhatsApp.
  3. Pengguna memasukkan kode di aplikasi Anda.
  4. Backend memanggil POST /api/otp/verify. Kode terpakai langsung dihapus (sekali pakai).
Kode tidak pernah dikembalikan lewat API dan tidak pernah disimpan dalam bentuk plaintext — hanya hash SHA-256 bergaram. Verifikasi memakai perbandingan waktu tetap.

Endpoint OTP

POST/api/otp/sendbutuh sesi aktif

Membuat kode OTP dan mengirimkannya ke nomor tujuan lewat WhatsApp.

FieldTipeKeterangan
phonewajibstringNomor tujuan.
appNameopsionalstringNama layanan di isi pesan. Default Zaintech.
lengthopsionalnumberPanjang kode, 4–10. Default 6.
ttlSecondsopsionalnumberMasa berlaku, 30–3600. Default 300.
templateopsionalstringTeks pesan. Wajib memuat {{code}}. Placeholder lain: {{app}}, {{ttl}}, {{ttlSeconds}}, {{phone}}.
referenceopsionalstringPenanda 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}'
POST/api/otp/verify

Memeriksa kode yang dimasukkan pengguna. Kode yang benar langsung dihapus (sekali pakai).

FieldTipeKeterangan
phonewajibstringNomor yang sama dengan saat kirim.
codewajibstringKode 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"}'
GET/api/otp/status/:phone

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
  }
}
DELETE/api/otp/:phone

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

POST/api/message/sendbutuh sesi aktif

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."}'
POST/api/message/bulkmaks 50

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"}
      ]}'
GET/api/message/check/:phone

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

GET/api/session/status

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"
GET/api/session/qr

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'])"
POST/api/session/pairing-code

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" } }
POST/api/session/restart

Menyambung ulang socket tanpa menghapus kredensial. Dipakai bila koneksi tersangkut.

POST/api/session/logoutdestruktif

Memutus tautan perangkat dan menghapus kredensial sesi. Setelah ini nomor harus dipasangkan ulang lewat QR atau pairing code.

GET/healthtanpa API key

Health check untuk monitoring/uptime. Membalas 200 bila WhatsApp tersambung, 503 bila tidak.

curl -i https://wa.zaintech.id/health

Kode error

HTTPerrorArti & penanganan
400INVALID_PHONENomor kosong atau tidak bisa dinormalkan.
400INVALID_CODEKode salah — cek attemptsRemaining.
400EXPIREDOTP kedaluwarsa, minta yang baru.
400NOT_FOUNDTidak ada OTP aktif untuk nomor itu.
400TOO_MANY_ATTEMPTSSalah > 5 kali; OTP dihanguskan.
400INVALID_TEMPLATETemplate tidak memuat {{code}}.
401UNAUTHORIZEDAPI key salah / tidak dikirim.
404QR_NOT_READYQR belum dibuat, coba lagi beberapa detik.
409ALREADY_CONNECTEDSesi sudah aktif; logout dulu bila mau ganti nomor.
422NOT_REGISTEREDNomor tujuan tidak punya WhatsApp.
429COOLDOWNKirim ulang terlalu cepat; lihat header Retry-After.
429QUOTA_EXCEEDEDKuota OTP per nomor per jam tercapai.
429RATE_LIMITEDTerlalu banyak request dari satu IP.
503NOT_CONNECTEDGateway belum terhubung ke WhatsApp — lakukan pairing.

Rate limit & kebijakan

BatasNilai defaultVariabel .env
Request per IP300 / 15 menitRATE_LIMIT_MAX
Pengiriman per IP30 / menitSEND_RATE_LIMIT_MAX
Jeda kirim ulang per nomor60 detikOTP_RESEND_COOLDOWN_SECONDS
OTP per nomor per jam5OTP_MAX_PER_HOUR_PER_PHONE
Salah kode maksimum5OTP_MAX_ATTEMPTS
Masa berlaku OTP300 detikOTP_TTL_SECONDS
Jeda antar pesan keluar1200 msSEND_INTERVAL_MS
Catatan WhatsApp. Ini memakai WhatsApp pribadi lewat protokol multi-device, bukan WhatsApp Business API resmi. Kirim wajar dan hindari blast massal agar nomor tidak diblokir. Jangan pakai nomor pribadi untuk gateway.

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
LokasiIsi
/usr/share/nginx/html/wa/srcKode aplikasi
/usr/share/nginx/html/wa/.envKonfigurasi & API key
/usr/share/nginx/html/wa/authKredensial sesi WhatsApp — jangan dihapus
/usr/share/nginx/html/wa/dataOTP aktif (hash) yang dipersist
/usr/share/nginx/html/wa/logsLog pm2