Cara Integrasi API OTP WhatsApp di Node.js

Cara Integrasi API OTP WhatsApp di Node.js

API OTP WhatsApp di Node.js sebaiknya dipasang di backend, bukan di browser. Alasannya sederhana: API key OTP.ID adalah kredensial server. Begitu key itu masuk bundle frontend, extension browser, mobile app, atau log analytics, kontrolnya hilang.

Artikel ini membahas integrasi backend Node.js memakai SDK resmi @otp-id/sdk. Flow-nya pendek: install SDK, simpan API key di environment variable, panggil request OTP lewat Channel WhatsApp, simpan otp_id, lalu verify Kode OTP yang diketik End User.

Ilustrasi blok API Node.js yang terhubung ke verifikasi OTP WhatsApp

Gambaran flow integrasi

Di aplikasi Client, layar End User biasanya hanya punya dua langkah: masukkan nomor HP, lalu masukkan Kode OTP. Di backend, alurnya perlu sedikit lebih rapi.

  1. End User mengisi nomor HP di aplikasi Client.
  2. Backend Node.js menormalisasi nomor ke format digit internasional, misalnya 628xxxxxxxxxx.
  3. Backend memanggil API OTP.ID lewat SDK resmi.
  4. OTP.ID membuat Kode OTP dan mengirimnya lewat Channel WhatsApp.
  5. Backend menyimpan otp_id untuk proses verify berikutnya.
  6. End User mengetik Kode OTP.
  7. Backend memanggil verify dan baru melanjutkan flow aplikasi jika verified:true.

Request OTP yang sukses bukan berarti End User sudah terverifikasi. Request sukses berarti transaksi OTP sudah dibuat dan pengiriman dicoba. Status verifikasi baru diketahui setelah backend memanggil verify.

Prasyarat sebelum mulai

Contoh di artikel ini memakai Node.js 20 atau lebih baru, karena SDK resmi @otp-id/sdk memakai fetch bawaan Node.js dan AbortSignal.timeout.

Siapkan juga API key OTP.ID dari dashboard Client. Simpan key itu di environment variable, bukan di source code.

export OTPID_API_KEY="***"

Contoh di atas hanya placeholder. Jangan menaruh API key asli di repository, issue tracker, screenshot, atau file log.

Install SDK resmi OTP.ID

Install package dari npm:

npm install @otp-id/sdk

Package @otp-id/sdk adalah SDK resmi Node.js/TypeScript untuk API V3 OTP.ID. SDK ini punya method untuk request OTP, send OTP, verify OTP, cek status OTP, cek akun, dan membuat top-up. Untuk flow WhatsApp OTP standar, dua method yang paling sering dipakai adalah requestOtp() dan verifyOtp().

Endpoint backend untuk request OTP

Buat endpoint di backend Client, misalnya POST /auth/otp/request. Endpoint ini menerima nomor dari aplikasi, lalu backend memanggil OTP.ID.

Contoh memakai Express. Kalau stack backend Client memakai Fastify, Hono, NestJS, atau framework lain, bagian pentingnya tetap sama: API key tetap di server, dan response otp_id disimpan untuk langkah verify.

import express from "express";
import { OtpIdClient, APIError } from "@otp-id/sdk";

const app = express();
app.use(express.json());

const otp = new OtpIdClient(process.env.OTPID_API_KEY);

function normalizeIndonesianPhone(input) {
  const digits = String(input || "").replace(/\D/g, "");

  if (digits.startsWith("0")) {
    return `62${digits.slice(1)}`;
  }

  return digits;
}

app.post("/auth/otp/request", async (req, res) => {
  const destination = normalizeIndonesianPhone(req.body.phone);

  if (!destination || destination.length < 10) {
    return res.status(400).json({ message: "Nomor HP tidak valid" });
  }

  try {
    const result = await otp.requestOtp({
      channel: "whatsapp",
      destination,
      brand: "TokoAnda",
      otp_length: 6,
      ttl: 300,
      external_id: `login-${destination}-${Date.now()}`,
    });

    // Simpan result.otp_id di session, cache, atau tabel login attempt milik Client.
    // Jangan kirim API key atau detail internal ke frontend.
    return res.json({
      otp_id: result.otp_id,
      status: result.status,
      expires_at: result.expires_at,
    });
  } catch (err) {
    if (err instanceof APIError) {
      return res.status(err.httpStatus || 500).json({
        message: "Gagal mengirim OTP",
        code: err.code,
      });
    }

    return res.status(500).json({ message: "Terjadi kesalahan server" });
  }
});

Pada POST /v3/request, field channel:"whatsapp" memilih Channel WhatsApp. Field destination berisi nomor tujuan digit-only. Field brand dipakai sebagai nama brand di pesan berbasis teks. Field otp_length dan ttl boleh dikirim agar aturan Kode OTP jelas di sisi server OTP.ID.

external_id berguna untuk retry yang aman. Jika backend mengirim request ulang dengan external_id yang sama dan parameter yang sama dalam window yang didukung API, response pertama bisa di-replay tanpa debit ulang. Jangan isi external_id dengan data sensitif.

Simpan otp_id dengan benar

otp_id adalah pegangan transaksi OTP. Backend Client butuh nilai ini saat verify. Simpan di tempat yang sesuai dengan flow aplikasi.

Untuk login, otp_id bisa disimpan di tabel login attempt atau cache sementara yang terikat ke nomor HP dan session browser. Untuk registrasi, otp_id bisa terikat ke proses pembuatan akun yang belum selesai.

Jangan menganggap otp_id sebagai bukti login. Bukti login baru muncul setelah verify sukses.

Endpoint backend untuk verify OTP

Setelah End User memasukkan Kode OTP, frontend memanggil endpoint backend Client, misalnya POST /auth/otp/verify. Backend meneruskan otp_id dan otp ke OTP.ID.

app.post("/auth/otp/verify", async (req, res) => {
  const otpId = String(req.body.otp_id || "").trim();
  const code = String(req.body.otp || "").trim();

  if (!otpId || !code) {
    return res.status(400).json({ message: "otp_id dan otp wajib diisi" });
  }

  try {
    const result = await otp.verifyOtp(otpId, code);

    if (!result.verified) {
      return res.status(400).json({
        message: "Kode OTP salah",
        reason: result.reason,
      });
    }

    // Di titik ini nomor End User sudah terverifikasi.
    // Buat session login atau lanjutkan registrasi sesuai sistem Client.
    return res.json({ verified: true });
  } catch (err) {
    if (err instanceof APIError) {
      return res.status(err.httpStatus || 500).json({
        message: "Verifikasi OTP gagal",
        code: err.code,
      });
    }

    return res.status(500).json({ message: "Terjadi kesalahan server" });
  }
});

Kode OTP yang salah pada API V3 adalah hasil normal: response bisa tetap HTTP 200 dengan verified:false dan reason:"mismatch". Karena itu, aplikasi Client sebaiknya menampilkan pesan yang jelas tanpa menyebut detail teknis ke End User.

Untuk kondisi seperti OTP kedaluwarsa, terlalu banyak percobaan, atau kode sudah dipakai, API dapat mengembalikan error. Tangani error itu sebagai state aplikasi, bukan sekadar crash.

Ilustrasi perisai keamanan untuk API key OTP di backend Node.js

Hal yang perlu dijaga sebelum produksi

Ada beberapa kebiasaan kecil yang mencegah masalah besar.

Pertama, panggil OTP.ID hanya dari backend. Frontend cukup memanggil API milik Client. API key tidak boleh masuk browser atau aplikasi mobile.

Kedua, jangan log Kode OTP. OTP adalah credential sementara. Log request yang terlalu lengkap sering terasa membantu saat debugging, tapi berbahaya kalau menyimpan otp, API key, atau nomor lengkap tanpa kebutuhan jelas.

Ketiga, batasi tombol kirim ulang di UI. API V3 punya rate limit server-side, tetapi UX yang baik tetap memberi cooldown agar End User tidak menekan tombol berkali-kali.

Keempat, siapkan pilihan Channel lain di produk jika WhatsApp tidak bisa dipakai untuk nomor tertentu. OTP.ID tidak melakukan fallback otomatis antar Channel. Jika pengiriman gagal, request tidak didebit dan backend Client yang menentukan langkah berikutnya.

Contoh struktur route yang sederhana

Untuk tahap awal, struktur seperti ini sudah cukup:

src/
  otp-client.js
  routes/
    otp-request.js
    otp-verify.js

otp-client.js berisi inisialisasi SDK:

import { OtpIdClient } from "@otp-id/sdk";

export const otpClient = new OtpIdClient(process.env.OTPID_API_KEY);

Dengan pola ini, route request dan verify memakai client yang sama. Rotasi API key tetap dilakukan lewat environment variable dan restart service, bukan dengan mengubah kode.

Kapan memakai sendOtp()?

Contoh di artikel ini memakai requestOtp(), artinya Kode OTP dibuat oleh OTP.ID. Untuk sebagian besar flow login dan registrasi, ini pilihan yang lebih sederhana karena backend Client tidak perlu membuat dan menyimpan kode sendiri.

SDK juga menyediakan sendOtp() jika Client ingin membuat Kode OTP sendiri lalu meminta OTP.ID mengirimkannya. Mode itu butuh disiplin tambahan: backend Client harus memastikan kode dibuat secara aman, tidak bocor ke log, punya masa berlaku, dan tidak bisa dipakai ulang di luar flow yang sah.

Kalau belum punya alasan kuat untuk membuat kode sendiri, mulai dari requestOtp().

Penutup

Integrasi API OTP WhatsApp di Node.js tidak perlu rumit. Letakkan SDK di backend, simpan API key di environment variable, panggil requestOtp() untuk mengirim Kode OTP, simpan otp_id, lalu panggil verifyOtp() sebelum membuat session atau menyelesaikan registrasi.

Untuk mencoba, buat akun di https://app.otp.id, ambil API key dari dashboard, lalu jalankan flow di environment server. Dokumentasi API lengkap tersedia di https://docs.otp.id.

← SEMUA ARTIKEL