Skenarionya hampir selalu sama. User baru mengisi nomor HP di form registrasi, menekan kirim, lalu menatap layar menunggu SMS berisi kode yang tidak kunjung masuk. Sebagian menekan "kirim ulang" berkali-kali. Sebagian lagi menyerah dan menutup aplikasi, tepat di langkah terakhir sebelum jadi user aktif. Padahal di HP yang sama, notifikasi WhatsApp masuk tanpa hambatan.
Artikel ini membahas cara memverifikasi nomor HP user aplikasi Anda memakai WhatsApp OTP: kenapa channel ini masuk akal untuk aplikasi yang melayani pasar Indonesia, seperti apa alur request → verify di level API, dan proteksi apa saja yang harus ada sebelum fitur ini layak produksi. Contoh kode memakai API OTP.ID V3, tapi konsep alurnya berlaku umum di penyedia mana pun.
Kenapa WhatsApp untuk OTP di Indonesia
Angkanya dulu. Indonesia adalah negara pengguna WhatsApp terbesar ketiga di dunia dengan 112 juta pengguna, di bawah India dan Brasil, dari total 3 miliar pengguna global per kuartal pertama 2025 (data World Population Review, dikutip CNN Indonesia, Mei 2025). Kalau produk Anda melayani pasar Indonesia, kemungkinan besar nomor yang mau diverifikasi memang aktif di WhatsApp.
Arah standar keamanan juga relevan. NIST SP 800-63B mengklasifikasikan verifikasi out-of-band lewat jaringan telepon publik (PSTN), yang mencakup SMS dan panggilan suara, sebagai RESTRICTED (bagian 5.1.3.3). Bukan berarti OTP SMS dilarang; klasifikasi itu menuntut organisasi menilai risikonya dan menyediakan alternatif bagi penggunanya. OTP yang dikirim lewat jalur data seperti WhatsApp berada di luar cakupan klasifikasi tersebut.
Dari sisi user yang menerima kode, pesan WhatsApp menampilkan nama pengirim atau brand, sehingga lebih mudah dikenali dibanding SMS dari nomor acak. Dan dari pengamatan operasional di OTP.ID sendiri, WhatsApp adalah channel yang paling banyak dipakai Client karena keterkirimannya paling baik di antara channel yang tersedia. Angka delivery rate-nya belum kami publikasikan; sesuai prinsip kami, klaim keandalan baru ditulis setelah terukur dari data produksi beserta periodenya.

Anatomi alur verifikasi nomor
Alur lengkapnya:
1. Backend aplikasi memanggil API kirim OTP
2. Kode terkirim ke WhatsApp user
3. User mengetik kode di aplikasi
4. Backend memanggil API verifikasi
5. Status final: verified, atau gagal (kode salah / kedaluwarsa / habis percobaan)
Yang membuat fitur ini lebih rumit dari kelihatannya adalah pekerjaan turunan di langkah 1 dan 4: menghasilkan kode yang tidak mudah ditebak, memberlakukan masa berlaku, membatasi jumlah percobaan, memastikan kode hangus setelah dipakai, membatasi kiriman per nomor, sampai memutuskan bagaimana kode disimpan di database. Daftar inilah yang paling sering bocor di implementasi buatan sendiri, dan hampir semuanya bisa dipindahkan ke sisi penyedia API.
Kirim kode: POST /v3/request
Satu request dari backend Anda:
curl -X POST https://api.otp.id/v3/request \
-H "Authorization: Bearer $OTPID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"destination": "628xxxxxxxxxx",
"brand": "TokoAnda",
"otp_length": 6,
"ttl": 300,
"external_id": "reg-usr-1042"
}'
destination diisi nomor HP dalam format internasional tanpa tanda plus, digit saja, minimal 10 digit. brand adalah nama yang tampil di pesan; kalau dikosongkan, dipakai nama brand akun Anda. otp_length default 6 dan dibatasi di rentang 4–8; ttl default 300 detik dan dibatasi 60–900. external_id opsional tapi sebaiknya selalu diisi — perannya dibahas di bagian proteksi.
Response sukses:
{
"success": true,
"data": {
"otp_id": "OTP20260815ABCD000123",
"status": "sent",
"channel": "whatsapp",
"number": "628xxxxxxxxxx",
"price": 350,
"last_balance": 99650,
"expires_at": "2026-08-15 14:05:00"
},
"error": null
}
Simpan data.otp_id; itu kunci yang dipakai saat verifikasi. price dan last_balance menunjukkan biaya request ini dan sisa kredit prepaid setelahnya (angka di contoh hanya ilustrasi; harga per channel tampil di dashboard). expires_at memakai zona waktu WIB.
Satu perilaku yang sering terlewat: gagal kirim ke vendor tetap dikembalikan sebagai HTTP 200, dengan data.status bernilai "failed" dan kredit tidak didebit. Jadi periksa data.status di kode Anda, jangan hanya mengandalkan HTTP status code.
Verifikasi kode: POST /v3/verify
Saat user mengetik kodenya, backend memanggil:
curl -X POST https://api.otp.id/v3/verify \
-H "Authorization: Bearer $OTPID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"otp_id": "OTP20260815ABCD000123", "otp": "123456"}'
Hasilnya sinkron, dan perilakunya perlu dipahami sebelum menulis error handling:
| Kondisi | HTTP | Isi response |
|---|---|---|
| Kode cocok | 200 | verified: true; transaksi berstatus verified |
| Kode salah | 200 | verified: false, reason: "mismatch" |
| Lewat masa berlaku | 422 | error OTP_EXPIRED |
| Salah 5 kali | 422 | error TOO_MANY_ATTEMPTS |
| Sudah pernah diverifikasi | 422 | error ALREADY_USED |
Kode yang salah dikembalikan sebagai HTTP 200 dengan verified: false. Di backend Anda, jalur ini jangan masuk exception handler; itu respons normal yang tinggal diteruskan ke user sebagai "kode salah, coba lagi".
Tiga proteksi di tabel itu berjalan di server tanpa konfigurasi apa pun: masa berlaku, maksimal 5 percobaan salah untuk channel standar, dan sekali-pakai. Sebagai pembanding, NIST SP 800-63B (bagian 5.1.3.2) mensyaratkan autentikasi out-of-band dianggap tidak valid setelah 10 menit. Default 300 detik jauh di dalam batas itu. Kalau Anda menaikkan ttl, pastikan nilainya tetap sesuai kebijakan risiko aplikasi dan standar yang dipakai organisasi Anda.
Proteksi lain yang sudah berjalan di server
Idempotency lewat external_id menyelamatkan Anda dari kasus retry jaringan. Request ulang dengan external_id dan parameter yang sama dalam 24 jam akan menerima replay response pertama, tanpa kirim ulang dan tanpa debit ulang. Kalau external_id sama dipakai dengan parameter berbeda, server menjawab 409 DUPLICATE_EXTERNAL_ID.
Rate limit bekerja di dua dimensi. Per nomor tujuan: maksimal 5 OTP per 10 menit dan 10 OTP per jam, dijawab 429 DESTINATION_RATE_LIMITED saat terlampaui — pagar terhadap pola penyalahgunaan yang memicu pengiriman OTP massal ke satu nomor. Per API key: 20 request per detik untuk semua endpoint V3.
Kode OTP sendiri disimpan sebagai hash SHA-256 dengan salt per record. Kode tidak pernah muncul di response API maupun log, jadi kebocoran log tidak otomatis jadi kebocoran kode aktif.
Kalau arsitektur Anda butuh notifikasi alih-alih polling, tersedia webhook otp.verified yang dikirim saat verifikasi berhasil, ditandatangani HMAC-SHA256 dengan timestamp untuk dicek di sisi penerima.

Satu hal tetap jadi tugas aplikasi: beri cooldown pada tombol "kirim ulang" di UI. Rate limit server adalah pagar terakhir; UX yang baik membuat user tidak pernah menabraknya.
Kalau nomor tidak terdaftar di WhatsApp?
Pengirimannya gagal: data.status bernilai failed dan kredit tidak didebit. OTP.ID tidak melakukan fallback otomatis antar channel; keputusan channel berikutnya ada di tangan Anda sebagai pemilik alur dan biaya.
Praktisnya sederhana. Tangkap status failed, tawarkan pilihan ke user ("kirim lewat SMS saja?"), lalu buat request baru dengan body yang sama dan channel diganti "sms" atau "misscall". Channel Missed Call memverifikasi lewat panggilan tak terjawab: user memasukkan digit terakhir nomor pemanggil sebagai kode.
Checklist sebelum naik produksi
- API key disimpan di environment variable dan hanya dipakai dari backend. API yang dipanggil langsung dari aplikasi mobile membawa API key ikut terdistribusi ke setiap perangkat user.
- Normalisasi nomor sebelum request: digit saja, awalan
62, minimal 10 digit. - Tangani semua jalur gagal:
status: "failed"saat kirim,reason: "mismatch"saat verify,402 INSUFFICIENT_BALANCEsaat kredit habis,429saat rate limit tersentuh. - Jangan tulis kode OTP ke log atau analytics.
- Pantau
last_balancedari setiap response, dan aktifkan balance alert (email/Telegram) di dashboard supaya kredit tidak habis diam-diam. - Tidak ada environment sandbox; uji dengan nomor tim sendiri, termasuk satu kasus nomor yang tidak punya WhatsApp untuk memastikan jalur gagal Anda benar.
Mulai dari mana
Seluruh alur di atas hanya dua request: satu untuk mengirim kode, satu untuk memverifikasinya; masa berlaku, batas percobaan, dan sisanya ditangani server. Referensi lengkap endpoint, error code, dan webhook ada di dokumentasi API OTP.ID di docs.otp.id. Kalau tidak mau menulis HTTP client sendiri, tersedia SDK resmi untuk Go, PHP, Node.js, dan Python di registry masing-masing (Go module, Packagist, npm, PyPI).
Registrasi publik OTP.ID sudah dibuka di app.otp.id. Merchant baru mendapat kredit gratis Rp2.000 untuk mulai mencoba alur WhatsApp OTP langsung dari dashboard.
