Lewati ke konten

Official Meta Business Partner

WhatsApp API buat kirim notifikasi pesanan

Langsung dari aplikasimu. Kalau pelanggan membalas, tim CS lanjut dari inbox WatZap.

Animasi: kode mengirim pesan WhatsApp, pelanggan membaca dan membalasnya, lalu balasan itu tiba di webhook.

Node.js · @watzapid/sdk
// npm install @watzapid/sdk
import { WatZap } from '@watzapid/sdk'

const wz = new WatZap()

const pesan = await wz.whatsapp
  .messages.sendText({
    to: '081234567890',
    text: 'Pesanan #INV-2091 dikirim',
  })

Pelanggan balas notifikasi, tim CS yang lanjutin

Notifikasi dari aplikasimu dan balasan tim CS tercatat di satu percakapan di inbox WatZap.

  1. Dikirim aplikasimu lewat API

    Pesannya langsung masuk ke percakapan pelanggan itu.

  2. Dibalas tim CS dari inbox WatZap

    Balasan keluar dari nomor yang sama, jadi pelanggan tetap di satu chat.

Mau sambungkan apa ke aplikasimu?

Konfirmasi pesanan

Begitu order masuk, pelanggan dapat pesan template berisi nomor pesanan dan tombol lacak.

sendTemplate
await wz.whatsapp.messages
  .sendTemplate({
    to: '081234567890',
    template: 'order_confirm',
    language: 'id',
    variables: { customer_name: 'Budi' },
  })

Kode OTP

Kode login atau verifikasi dikirim lewat template autentikasi WhatsApp.

sendTemplate · otpCode
await wz.whatsapp.messages
  .sendTemplate({
    to: '081234567890',
    template: 'login_otp',
    otpCode: '123456',
  })

Balasan ke servermu

Setiap pesan masuk diteruskan ke URL webhook-mu sebagai JSON.

webhook · Express
// URL-nya didaftarkan lewat
// wz.whatsapp.webhook(wabaId).set(url)
app.post('/watzap/masuk', (req, res) => {
  if (isWabaWebhook(req.body)) {
    console.log(req.body.data.message_text)
  }
  res.sendStatus(200)
})

Sinkron kontak

Daftar pelanggan di databasemu disinkronkan jadi list WatZap. Yang hilang dari daftar ditandai nonaktif, tidak dihapus.

lists.subscribers.sync
await wz.lists.subscribers.sync(
  listId,
  pelangganDariDatabase,
  { markMissingInactive: true },
)

Nomor WhatsApp biasa juga bisa dipakai, termasuk kirim ke grup. Lihat caranya

Timeout nggak bikin pesan terkirim dua kali

SDK resmi @watzapid/sdk untuk Node.js 18 ke atas, tanpa dependensi, dengan tipe TypeScript.

Node.js · try / catch
import { WatZap, WatZapError } from '@watzapid/sdk'

const wz = new WatZap()

try {
  await wz.whatsapp.messages.sendText({
    to: '+62 812-3456-7890',
    text: 'Pesanan #INV-2091 dikirim',
  })
} catch (err) {
  if (!(err instanceof WatZapError)) throw err
  if (err.mayHaveBeenProcessed) {
    // cek status dulu, jangan kirim ulang
  }
}
  1. sendText

    Tidak diulang otomatis kalau koneksi putus di tengah kirim, jadi pelanggan tidak dapat pesan dobel.

  2. '+62 812-3456-7890'

    Format 08…, 62…, dan +62… dinormalkan sendiri.

  3. WatZapError

    Semua error turunan satu kelas, lengkap dengan kode dan status HTTP.

  4. mayHaveBeenProcessed

    True kalau pesan mungkin sudah sampai. Cek statusnya dulu, jangan langsung kirim ulang.

Pakai ChatGPT, Claude, atau Cursor? Salin prompt berisi daftar method SDK yang benar-benar ada.

Lihat isi prompt
Prompt · @watzapid/sdk 0.2.0
You are helping me integrate WatZap into my app with the official SDK @watzapid/sdk (TypeScript/JavaScript, Node.js 18+). Use only the methods and options listed here. Do not invent others.

SETUP
- Install: npm install @watzapid/sdk
- ESM: import { WatZap } from '@watzapid/sdk'   CommonJS: const { WatZap } = require('@watzapid/sdk')
- const wz = new WatZap()  // reads the API key from the WATZAP_API_KEY environment variable
- Server-side only. Never put the API key in frontend code or in the repository.
- List methods return a PagedList: `await` it for one page ({ items, page, perPage, total, hasNextPage }), or `for await (const item of ...)` for every item.

WHATSAPP: WABA (official). Sends return Promise<{ id, raw }>; id is the Meta message ID (wamid).
- wz.whatsapp.messages.sendText({ to, text })
- wz.whatsapp.messages.sendTemplate({ to, template, language?, variables?, header?, buttons?, otpCode? })
  - variables: an object for NAMED templates, an array for POSITIONAL templates ({{1}}, {{2}})
  - header: { url, filename? }; buttons: { urlSuffix?: string | { [buttonIndex]: string }, couponCode?: string }
- wz.whatsapp.messages.sendImage|sendDocument|sendVideo({ to, url, caption? }); sendAudio({ to, url }); react({ to, messageId, emoji })
- wz.whatsapp.messages.sendButtons({ to, body, buttons: [{ id, title }] (1-3, title max 20 bytes), header?, footer? })
- wz.whatsapp.messages.sendList({ to, body, buttonText, sections: [{ title?, rows: [{ id, title, description? }] }] })
- wz.whatsapp.messages.sendLocation({ to, latitude, longitude, name?, address? }); sendContacts({ to, contacts }); sendRaw(payload)
- wz.whatsapp.messages.get(id) / status(id) / list({ status?, phone?, page?, perPage? }) / stats()
- wz.whatsapp.templates.list({ wabaId?, status?, search? }) / get(templateId | { name, wabaId, language? }) / status(ref) / create({ name, category, language, components }) / delete(ref) / sync()
- wz.whatsapp.webhook(wabaId).set(url) / get() / unset()
- Text and media only reach users who messaged the business in the last 24 hours. Otherwise use sendTemplate.
- Do not use wz.whatsapp.broadcasts to send campaigns: the server does not process broadcasts created through the API yet.

WHATSAPP: UNOFFICIAL. numberKey is the 16-character key of a linked number. Sends return { message, senderNumber, raw } with no message ID.
- const device = wz.unofficial.device(numberKey)
- device.messages.sendText({ to, text }) / sendImage({ to, url, caption? }) / sendFile({ to, url })
- device.groups.list() / device.groups.sendText({ groupId, text }) / sendImage({ groupId, url, caption? }) / sendFile({ groupId, url })
- device.validateNumber(phone); device.webhook.set(url) / get() / unset()
- wz.unofficial.conversations.list() / get(id) / messages(id) / markRead(id) / archive(id, archived?)

SHARED
- wz.senders.list() returns every WABA and Unofficial number with its numberKey / wabaId and status
- wz.contacts.list({ listId? }) / get({ contactId } | { phone } | { externalId }) / upsert({ listId, phone?, externalId?, name?, source?, status? }) / delete(contactId) / optIn({ phone }) / optOut({ phone })
  - upsert overwrites fields you leave out with empty values: always send every field you want to keep
  - opt-out is only recorded: check it yourself before sending
- wz.lists.list() / get(listId) / create({ name, customFields? }) / update(listId, {...}) / delete(listId)
- wz.lists.subscribers.list(listId, { channel? }) / get(listId, id) / upsert(listId, { phone?, email?, externalId?, name?, customFields? }) / delete(listId, id) / bulkUpsert(listId, items) / sync(listId, items, { markMissingInactive? })
- wz.media.uploadUrl({ url }) / list() / get(id) / download(id) / delete(id)
- wz.automations.bots() / rules() / n8nConfig() / n8nLogs() / n8nStats()

PHONE NUMBERS: 08xxx, 62xxx and +62xxx are all accepted and normalized. Use a leading + for non-Indonesian numbers.

ERRORS
- Every error extends WatZapError and has name, message, code, httpStatus, response, mayHaveBeenProcessed; RateLimitError also has retryAfterSeconds.
- Classes: ValidationError (bad input, checked by the SDK or the server), AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, InsufficientCreditsError, UnprocessableError, RateLimitError, ProviderError, InternalServerError, APIConnectionError, APITimeoutError, APIUserAbortError.
- The SDK already retries whenever it is safe. Do not wrap WhatsApp sends in your own retry loop.
- If err.mayHaveBeenProcessed is true, the message may already have been delivered: check wz.whatsapp.messages.status(id) or log it; do not resend automatically.

Now write the code for this: <DESCRIBE WHAT YOU WANT TO BUILD>

Yang perlu kamu siapkan

  1. Buat akun WatZap

    Satu akun untuk dashboard dan API.

  2. Sambungkan nomor WhatsApp Business

    Tim tetap bisa membalas dari aplikasi.

  3. Ambil API key dari dashboard

    Simpan di server, jangan di kode frontend.

  4. Pasang SDK

    Terminal
    npm install @watzapid/sdk

Masih ada yang mengganjal?

Detail teknis lengkapnya ada di dokumentasi API.

  • Harus pakai Node.js?

    Nggak. SDK-nya memang untuk TypeScript dan JavaScript, tapi bahasa lain bisa langsung panggil REST API-nya. Caranya ada di dokumentasi API. Situsnya WordPress? Pasang plugin resmi WatZap, tanpa koding.

  • Kenapa pesan teks saya nggak sampai?

    Pesan teks dan media cuma sampai ke pelanggan yang chat kamu dalam 24 jam terakhir. Di luar itu, kirim pesan template yang sudah disetujui Meta.

  • Bisa kirim broadcast lewat API?

    Belum. Broadcast dibuat dan dijadwalkan dari dashboard. Lewat API kamu bisa baca daftar broadcast dan statusnya.

  • Pesan dari API kelihatan di dashboard?

    Kelihatan. Pesan WABA yang dikirim lewat API muncul di inbox WatZap, dan tim CS bisa lanjut membalas dari percakapan itu.