kitasapa

Dokumentasi

API kitasapa

Hubungkan aplikasi, kasir, atau toko online Anda ke nomor WhatsApp usaha yang sudah tersambung di kitasapa. Kirim kode OTP, struk, dan notifikasi dari sistem Anda sendiri, lalu terima kabar status dan pesan masuk lewat webhook.

Ringkasan

Kode OTPTemplate autentikasi dengan tombol salin kode atau one-tap.
Template & teksStruk, pengingat, kabar pesanan dari template yang disetujui.
PesananCatat pesanan dan ubah statusnya, struk terkirim otomatis.
Webhook keluarStatus terkirim/dibaca, pesan masuk, dan tombol yang ditekan.
Alamat
POST https://kitasapa.com/api/integrasi.php
Format
JSON (UTF-8), maks 64 KB per permintaan (media_unggah maks 24 MB)
Autentikasi
Kunci API + tanda tangan HMAC-SHA256 per permintaan
Pengirim
Nomor WhatsApp usaha Anda sendiri, bukan nomor kitasapa

Memulai

  1. Sambungkan nomor usaha ke kitasapa lewat kitasapa.com/daftar. Akun WhatsApp Business dan nomornya tetap atas nama usaha Anda.
  2. Minta fitur Integrasi dinyalakan. Fitur ini dinyalakan tim kitasapa per usaha. Hubungi kami.
  3. Buat kunci API di dasbor, menu Integrasi (khusus pemilik usaha). Pilih izin yang dibutuhkan. Rahasia kunci hanya tampil sekali, simpan di server Anda.
  4. Siapkan template. Template harus sudah disetujui Meta. Untuk OTP, buat template kategori Authentication di WhatsApp Manager, lalu tekan Tarik dari Meta di menu Template kitasapa.
  5. Uji sambungan dengan aksi info. Jawaban 200 berarti kunci dan tanda tangan Anda benar.

Izin kunci

IzinUntuk
pesan:kirimKirim template (termasuk OTP) dan teks
pesanan:tulisCatat pesanan dan ubah statusnya
pengerjaan:tulisPindah tahap status pengerjaan (servis, laundry, dll.)
template:tulisAjukan template pesan baru ke Meta
otomatis:tulisBaca dan ubah balasan otomatis dan menu bot
langganan:bayarLihat dan perpanjang langganan kitasapa (bayar QRIS)

Buat kunci terpisah untuk tiap sistem, dengan izin seperlunya. Satu usaha bisa punya sampai 5 kunci aktif, dan kunci bisa dicabut kapan saja dari dasbor.

Autentikasi

Setiap permintaan membawa lima header berikut.

HeaderIsi
X-Kitasapa-KunciID kunci publik, diawali ks_
X-Kitasapa-WaktuWaktu Unix dalam detik. Selisih dengan jam server lebih dari 300 detik ditolak.
X-Kitasapa-Nonce32 karakter hex acak, sekali pakai per kunci
Idempotency-KeyWajib untuk aksi yang mengirim atau mengubah data. 8–100 karakter A–Z a–z 0–9 _ - :. Kosongkan untuk info.
X-Kitasapa-TandaTanda tangan HMAC-SHA256, lihat di bawah

Menghitung tanda tangan

tanda = hex( HMAC-SHA256( rahasia, waktu + "." + nonce + "." + idempotency_key + "." + badan ) )
  • rahasia = teks rahasia utuh, termasuk awalan ksr_.
  • badan = byte badan persis seperti yang dikirim. Tandatangani string yang sama dengan yang Anda kirim, jangan menyusun ulang JSON sesudahnya.
  • Tanpa Idempotency-Key (aksi info), titiknya tetap ada: waktu.nonce..badan.
  • Mengulang permintaan karena jaringan putus: tanda tangani ulang dengan waktu dan nonce baru, tapi Idempotency-Key dan badan tetap sama.

Contoh kode

Jalankan di server Anda. Rahasia kunci tidak boleh ada di aplikasi HP atau peramban.

PHP

function kitasapa(string $kunci, string $rahasia, array $isi, string $idem = ''): array
{
    $badan = json_encode($isi, JSON_UNESCAPED_UNICODE);
    $waktu = (string)time();
    $nonce = bin2hex(random_bytes(16));
    $tanda = hash_hmac('sha256', "$waktu.$nonce.$idem.$badan", $rahasia);
    $h = ['Content-Type: application/json', "X-Kitasapa-Kunci: $kunci", "X-Kitasapa-Waktu: $waktu",
          "X-Kitasapa-Nonce: $nonce", "X-Kitasapa-Tanda: $tanda"];
    if ($idem !== '') { $h[] = "Idempotency-Key: $idem"; }
    $ch = curl_init('https://kitasapa.com/api/integrasi.php');
    curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $badan, CURLOPT_HTTPHEADER => $h,
                            CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20]);
    $j = json_decode((string)curl_exec($ch), true);
    $kode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    return [$kode, $j];
}

Node.js (18+)

const crypto = require('crypto');

async function kitasapa(kunci, rahasia, isi, idem = '') {
  const badan = JSON.stringify(isi);
  const waktu = String(Math.floor(Date.now() / 1000));
  const nonce = crypto.randomBytes(16).toString('hex');
  const tanda = crypto.createHmac('sha256', rahasia).update(`${waktu}.${nonce}.${idem}.${badan}`).digest('hex');
  const h = { 'Content-Type': 'application/json', 'X-Kitasapa-Kunci': kunci, 'X-Kitasapa-Waktu': waktu,
              'X-Kitasapa-Nonce': nonce, 'X-Kitasapa-Tanda': tanda };
  if (idem) h['Idempotency-Key'] = idem;
  const r = await fetch('https://kitasapa.com/api/integrasi.php', { method: 'POST', headers: h, body: badan });
  return { kode: r.status, jawab: await r.json() };
}

cURL

KUNCI=ks_0123456789abcdef0123
RAHASIA=ksr_...        # tampil sekali saat kunci dibuat
BADAN='{"aksi":"info"}'
WAKTU=$(date +%s); NONCE=$(openssl rand -hex 16); IDEM=""
TANDA=$(printf '%s' "$WAKTU.$NONCE.$IDEM.$BADAN" | openssl dgst -sha256 -hmac "$RAHASIA" -r | cut -d' ' -f1)

curl -sS https://kitasapa.com/api/integrasi.php \
  -H 'Content-Type: application/json' -H "X-Kitasapa-Kunci: $KUNCI" -H "X-Kitasapa-Waktu: $WAKTU" \
  -H "X-Kitasapa-Nonce: $NONCE" -H "X-Kitasapa-Tanda: $TANDA" --data-binary "$BADAN"

Kirim kode OTP

Pakai aksi kirim_template dengan template kategori Authentication yang sudah disetujui Meta. Cukup kirim kode, kitasapa menyusun badan pesan dan tombol salin kode sesuai format resmi Meta.

Permintaan

{
  "aksi": "kirim_template",
  "ke": "6281234567890",
  "nama": "kode_masuk",
  "bahasa": "id",
  "kode": "482913"
}

Jawaban 200

{
  "ok": true,
  "pesan_id": 5521,
  "status": "terkirim",
  "ulang": false
}
  • ke = nomor WhatsApp pelanggan dengan kode negara, tanpa + atau spasi.
  • kode = 1–15 karakter tanpa spasi (batas Meta). Jangan kirim parameter atau tombol untuk template autentikasi.
  • Didukung: tombol salin kode dan one-tap. Zero-tap belum didukung (ditolak 422 template_belum_didukung).
  • Masa berlaku kode diatur di sistem Anda. Keterangan "kode berlaku N menit" bisa dipasang saat membuat template di WhatsApp Manager.
  • Pakai Idempotency-Key unik per kode, misalnya otp:<id-permintaan>. Kalau jaringan putus dan Anda mengulang dengan key yang sama, kode tidak terkirim dua kali.
  • Kode OTP disamarkan di kotak masuk kitasapa, tim Anda tidak bisa membacanya.
  • Biaya pesan autentikasi ditagih Meta langsung ke akun WhatsApp Business Anda, sesuai tarif resmi Meta.

Aksi lainnya

AksiIzinKeterangan
info–Uji sambungan, daftar nomor usaha, sisa batas kirim harian, jam server
daftar_template–Template usaha beserta statusnya di Meta, jumlah isian, dan tombolnya
kirim_templatepesan:kirimTemplate yang disetujui, termasuk OTP
kirim_tekspesan:kirimTeks bebas, hanya dalam 24 jam sejak pesan terakhir pelanggan
buat_pesananpesanan:tulisCatat pesanan, struk terkirim otomatis
ubah_pesananpesanan:tulisMajukan status pesanan satu langkah
pindah_tahappengerjaan:tulisPindah tahap pengerjaan dan kabari pelanggan
media_unggahtemplate:tulisUnggah gambar/video/dokumen contoh untuk judul template
ajukan_templatetemplate:tulisAjukan template baru ke Meta (aturan sama dengan dasbor)
otomatis_bacaotomatis:tulisBaca balasan otomatis dan menu bot sekaligus
otomatis_simpanotomatis:tulisSimpan balasan di luar jam buka dan balasan kata kunci
bot_simpanotomatis:tulisSimpan menu bot dan pertanyaan siap-ketuk
langganan_bacalangganan:bayarStatus langganan kitasapa, tagihan, dan paket
langganan_bayarlangganan:bayarBuat QRIS untuk membayar tagihan langganan
langganan_ceklangganan:bayarCek apakah tagihan sudah terbayar
langganan_sikluslangganan:bayarPilih bayar bulanan atau tahunan

Bila usaha punya lebih dari satu nomor, sertakan nomor (ID nomor dari aksi info).

Baru 2 Oktober 2026: media_unggah, ajukan_template, otomatis_baca, otomatis_simpan, dan bot_simpan, untuk mengelola template dan balasan otomatis dari sistem Anda sendiri; serta langganan_baca, langganan_bayar, langganan_cek, dan langganan_siklus untuk menampilkan dan memperpanjang langganan kitasapa dari aplikasi Anda.

info

{"aksi":"info"}

→ {"ok":true,"kunci_id":"ks_…","cakupan":["pesan:kirim"],
   "nomor":[{"id":4,"tampil":"62812…","nama":"Toko Budi","terdaftar":true}],
   "template_media":[{"media_id":12,"template":"promo_bulan_ini","bahasa":"id","jenis":"image","nama_berkas":"brosur.jpg","kartu":null}],
   "kuota":[{"nomor_id":4,"batas":250,"terpakai":37,"sisa":213}],
   "simulasi":false,"waktu_server":1790000000}

template_media = gambar/video/dokumen contoh dari template yang sudah dibuat (di dasbor atau lewat ajukan_template); media_id-nya bisa dipakai di judul_media saat kirim. Media kartu template carousel bernilai kartu = nomor kartu (mulai 0) dan bisa dipakai di kartu_media; media judul bernilai kartu: null.

  • kuota = batas pesan harian Meta, satu baris per nomor. batas = jumlah orang unik yang boleh menerima template dalam 24 jam bergulir, sesuai tingkat nomor yang dilaporkan Meta (belum dilaporkan = 250).
  • terpakai = orang unik yang sudah menerima template dalam 24 jam terakhir, dari semua jalur (API, siaran, pengingat). sisa = batas − terpakai (tidak pernah negatif). Mengirim lagi ke orang yang sudah terhitung tidak menambah terpakai.
  • Batas Meta berlaku per portofolio bisnis, jadi angkanya sama untuk semua nomor usaha Anda. Pakai sisa untuk membagi kiriman massal (sisanya dikirim besok), bukan sebagai jaminan: hitungan Meta yang menentukan.

daftar_template

{"aksi":"daftar_template"}

→ {"ok":true,"simulasi":false,"template":[
   {"nama":"pengingat_kontrol","bahasa":"id","kategori":"UTILITY","status":"APPROVED","alasan":null,
    "isian":4,"butuh_kode":false,"judul":null,
    "badan":"Halo {{1}}, mengingatkan jadwal kontrol pada {{2}} pukul {{3}} WIB dengan {{4}}. …",
    "tombol":[{"indeks":0,"jenis":"balas_cepat","teks":"Datang"},{"indeks":1,"jenis":"balas_cepat","teks":"Jadwal ulang"}],
    "kupon":null,"penawaran":null,"kartu":null,
    "bisa_dikirim":true,"diperbarui":"2026-10-02 13:00:00"}]}
  • Satu baris per nama + bahasa, yaitu versi yang dipakai kirim_template. Template diajukan dari dasbor atau lewat ajukan_template; aksi ini hanya membaca.
  • status apa adanya dari Meta: PENDING (ditinjau), APPROVED, REJECTED (lihat alasan), PAUSED, DISABLED.
  • isian = jumlah parameter yang wajib dikirim. butuh_kode = template OTP: kirim kode, bukan parameter. tombol[].jenis: balas_cepat, url, url_dinamis (wajib teks), telepon, salin_kupon, atau jenis lain yang belum didukung.
  • kupon = template bertombol salin kode kupon: {"indeks":1,"maks":20} (kirim kupon, maks maks karakter). penawaran = penawaran terbatas: {"teks":"Diskon kilat","kedaluwarsa":true} (kedaluwarsa true = wajib kedaluwarsa_ms). kartu = template carousel: [{"indeks":0,"jenis":"image","badan":"…","tombol":[{"indeks":0,"jenis":"balas_cepat","teks":"Pesan"}]}]. Ketiganya null bila tidak dipakai template itu.
  • bisa_dikirim = disetujui dan bentuknya didukung API ini. Bila tidak didukung, alasannya ada di tidak_didukung. Cocok untuk memeriksa naskah di sistem Anda sebelum mulai mengirim.

kirim_template (umum)

{"aksi":"kirim_template","ke":"6281234567890","nama":"pengingat_janji","bahasa":"id",
 "parameter":["Budi","Senin 29 Sep 10.00"],
 "tombol":[{"indeks":0,"payload":"hadir:881"},{"indeks":1,"teks":"janji/881"}]}
  • parameter mengisi {{1}}, {{2}}, … berurutan. Jumlahnya harus pas.
  • tombol (opsional), menurut urutan tombol di template mulai 0. Balas cepat: payload (1–256 karakter), kembali ke Anda lewat webhook tombol_ditekan. URL dinamis: teks = akhiran URL.
  • Belum didukung: judul bervariabel atau judul lokasi, isian bernama {{nama}}, tombol flow dan katalog, kartu carousel berisian atau berlink dinamis. Kode kupon, penawaran terbatas, dan carousel: lihat di bawah.
  • Penerima yang meminta berhenti promo. Template kategori Marketing ke orang yang di kitasapa tercatat meminta berhenti promo (mis. menekan tombol atau membalas BERHENTI) ditolak 409 penerima_berhenti, dan tidak ada yang dikirim. Ini lapis pengaman kedua; persetujuan utama tetap dikelola sistem Anda. Template Utility dan Authentication (pengingat janji, kode, kabar pesanan) tetap dikirim. Penerima tanpa catatan di kitasapa tidak ditahan.

kirim_template berjudul gambar, video, atau dokumen

{"aksi":"kirim_template","ke":"6281234567890","nama":"promo_bulan_ini","bahasa":"id",
 "parameter":["Budi"]}                                      // pakai gambar contoh template

{"aksi":"kirim_template","ke":"6281234567890","nama":"kirim_invoice","bahasa":"id",
 "parameter":["Budi","#INV-12"],
 "judul_media":{"url":"https://toko.contoh/invoice/12.pdf","nama_berkas":"Invoice 12.pdf"}}

{"aksi":"kirim_template","ke":"6281234567890","nama":"promo_bulan_ini","bahasa":"id",
 "parameter":["Budi"],"judul_media":{"media_id":12}}
  • Tanpa judul_media: dikirim dengan media contoh yang diunggah saat template dibuat di dasbor. Template yang dibuat di luar kitasapa belum punya contoh di sini, jadi wajib url atau media_id.
  • url wajib https:// ke host publik (port 443, tanpa nama pengguna/sandi; alamat lokal dan privat ditolak). Meta yang mengambil berkasnya. nama_berkas hanya untuk dokumen.
  • jenis (image, video, document) opsional; bila diisi harus sama dengan judul template.
  • Batas WhatsApp: gambar JPG/PNG maks 5 MB, video MP4 maks 16 MB, dokumen PDF.
  • Media tak tersedia (belum ada contoh, sudah dihapus, jenis tak cocok) → 422 media_judul_tidak_tersedia. Template tanpa judul media menolak judul_media (400 masukan_tidak_sah).

kirim_template kode kupon dan penawaran terbatas

{"aksi":"kirim_template","ke":"6281234567890","nama":"kupon_pelanggan_setia","bahasa":"id",
 "parameter":["Budi"],"kupon":"HEMAT10"}

{"aksi":"kirim_template","ke":"6281234567890","nama":"diskon_kilat","bahasa":"id",
 "parameter":["Budi"],"kupon":"KILAT25","kedaluwarsa_ms":1791100800000}
  • kupon wajib untuk template bertombol salin kode kupon: kode yang disalin pelanggan, tanpa spasi, maks 20 karakter (maks 15 di template penawaran terbatas). Tombol kupon tidak diisi lewat tombol.
  • kedaluwarsa_ms wajib untuk penawaran terbatas yang memakai hitung mundur: waktu berakhir dalam milidetik unix, minimal 1 menit dari sekarang. Hitung mundur tidak tampil di WhatsApp desktop/web.
  • Lihat daftar_template (kupon, penawaran) untuk tahu isian mana yang diminta. Isian kurang, berlebih (template tidak memakainya), atau tidak sah → 400 masukan_tidak_sah.
  • Template seperti ini hanya kategori Marketing, jadi aturan penerima yang meminta berhenti promo tetap berlaku.

kirim_template carousel (kartu bergeser)

{"aksi":"kirim_template","ke":"6281234567890","nama":"pilihan_minggu_ini","bahasa":"id",
 "parameter":["Budi"]}                                      // gambar contoh tiap kartu

{"aksi":"kirim_template","ke":"6281234567890","nama":"pilihan_minggu_ini","bahasa":"id",
 "parameter":["Budi"],
 "kartu_media":[null,{"media_id":31},{"url":"https://toko.contoh/foto/paket-c.jpg"}]}
  • parameter mengisi gelembung teks di atas kartu. Teks dan tombol tiap kartu tetap seperti saat template dibuat.
  • kartu_media (opsional) = media pengganti per kartu, urut mulai kartu 1. Tiap butir sama dengan judul_media: null atau {"pakai":"contoh"} (media contoh kartu itu), {"media_id":…}, atau {"url":"https://…"}. Boleh lebih pendek dari jumlah kartu; sisanya memakai contoh.
  • Semua kartu memakai jenis media yang sama (gambar JPG/PNG maks 5 MB atau video MP4 maks 16 MB), sesuai template.
  • Template carousel yang dibuat di luar kitasapa tidak punya contoh di sini: isi kartu_media untuk semua kartu. Media tak tersedia → 422 media_kartu_tidak_tersedia. Template bukan carousel menolak kartu_media.

kirim_teks

{"aksi":"kirim_teks","ke":"6281234567890","teks":"Pesanan #12 siap diambil"}

Maks 4.096 karakter, hanya dalam jendela 24 jam sejak pesan terakhir pelanggan. Di luar itu jawabannya 409 di_luar_jendela, gunakan kirim_template.

buat_pesanan / ubah_pesanan

{"aksi":"buat_pesanan","kontak":"6281234567890","isi":"2x Nasi goreng","total":50000,
 "ambil":"2026-09-29T15:00","pengingat":true}
→ {"ok":true,"no":12}

{"aksi":"ubah_pesanan","no":12,"status":"Siap"}      // Diproses → Siap → Selesai

pindah_tahap

{"aksi":"pindah_tahap","no":7,"tahap":"selesai","kabar":true}
// tahap: diterima, dikerjakan, menunggu, selesai, diambil

Aksi pesanan dan pengerjaan hanya mengirim WhatsApp ke pelanggan bila kunci juga punya izin pesan:kirim. Tanpa izin itu datanya tetap tercatat, tapi tidak ada pesan yang keluar.

Pesan yang dikirim lewat API tampil di kotak masuk dengan pengirim API · nama kunci.

media_unggah

{"aksi":"media_unggah","jenis":"image","nama_berkas":"brosur.jpg","isi_base64":"/9j/4AAQSkZJRg…"}

→ {"ok":true,"media":{"id":12,"jenis":"image","mime":"image/jpeg","nama":"brosur.jpg","ukuran":123456}}
  • Contoh untuk judul template bergambar, video, atau dokumen. Aturannya sama dengan unggahan di dasbor.
  • jenis: image (JPG/PNG, maks 5 MB), video (MP4, maks 16 MB), document (PDF, maks 16 MB). Badan permintaan maks 24 MB.
  • Tipe dibaca dari isi berkas, bukan dari nama atau ekstensinya. Ekstensi yang tak cocok ditambah, mis. brosur.pdf berisi JPEG menjadi brosur.pdf.jpg. isi_base64 boleh berawalan data:…;base64,.
  • Galat: 400 masukan_tidak_sah (jenis salah, base64 rusak, isi bukan tipe yang didukung), 413 terlalu_besar.
  • Wajib Idempotency-Key; diulang dengan key dan badan sama = media yang sama. Berkas yang tidak dipakai template dihapus setelah 30 hari.

ajukan_template

{"aksi":"ajukan_template","nama":"info_libur","kategori":"UTILITY",
 "isi":"Halo {{1}}, klinik libur {{2}} ya.","contoh":["Kak Rina","Senin"],"label":["Sapaan dan nama","Hari"],
 "judul":"Info klinik","kaki":"Balas STOP untuk berhenti",
 "tombol":[{"jenis":"balas","teks":"Baik"},
           {"jenis":"link","teks":"Buka","url":"https://contoh.com/jadwal"},
           {"jenis":"telepon","teks":"Telepon","telepon":"081234567890"}]}
→ {"ok":true,"status":"PENDING"}

{"aksi":"ajukan_template","nama":"kode_masuk","kategori":"AUTHENTICATION",
 "otp":{"tombol":"Salin kode","menit":5,"keamanan":true}}
→ {"ok":true,"status":"PENDING"}

{"aksi":"ajukan_template","nama":"promo_scaling","kategori":"MARKETING",
 "isi":"Halo {{1}}, scaling gigi diskon 20% bulan ini.","contoh":["Kak Rina"],
 "judul_media":{"jenis":"image","media_id":12}}
→ {"ok":true,"status":"PENDING"}

{"aksi":"ajukan_template","nama":"kupon_pelanggan_setia","kategori":"MARKETING",
 "isi":"Halo {{1}}, pakai kode kupon di bawah untuk potongan 10%.","contoh":["Kak Rina"],
 "kaki":"Tidak mau terima promo? Tekan Berhenti promo",
 "tombol":[{"jenis":"balas","teks":"Berhenti promo"},{"jenis":"salin","kode":"HEMAT10"}]}

{"aksi":"ajukan_template","nama":"diskon_kilat","kategori":"MARKETING",
 "isi":"Halo {{1}}, diskon 25% hanya sampai jam 9 malam.","contoh":["Kak Rina"],
 "judul_media":{"jenis":"image","media_id":14},
 "penawaran":{"teks":"Diskon kilat","kedaluwarsa":true},
 "tombol":[{"jenis":"salin","kode":"KILAT25"},{"jenis":"link","teks":"Pesan","url":"https://toko.contoh/promo"}]}

{"aksi":"ajukan_template","nama":"pilihan_minggu_ini","kategori":"MARKETING",
 "isi":"Halo {{1}}, ini pilihan minggu ini. Geser untuk lihat semuanya.","contoh":["Kak Rina"],
 "carousel":{"jenis":"image","kartu":[
   {"media_id":21,"isi":"Paket hemat untuk sehari-hari.",
    "tombol":[{"jenis":"balas","teks":"Pesan yang ini"},{"jenis":"link","teks":"Lihat","url":"https://toko.contoh/a"}]},
   {"media_id":22,"isi":"Porsi keluarga, 4-5 orang.",
    "tombol":[{"jenis":"balas","teks":"Pesan yang ini"},{"jenis":"link","teks":"Lihat","url":"https://toko.contoh/b"}]}]}}
→ {"ok":true,"status":"PENDING"}
  • Aturannya sama persis dengan membuat template di dasbor kitasapa, termasuk kalimat galatnya. Bahasa: Indonesia (id).
  • nama: huruf kecil, angka, garis bawah. kategori: UTILITY, MARKETING, atau AUTHENTICATION.
  • isi maks 1.024 karakter. Isian {{1}}, {{2}}, … berurutan tanpa loncat, tidak boleh di awal atau akhir teks. contoh wajib satu per isian (Meta memakainya untuk menilai). label opsional (nama isian, maks 30 karakter, jumlahnya sama dengan isian).
  • judul dan kaki opsional: maks 60 karakter, satu baris, tanpa isian.
  • tombol maks 3: balas, link (https://), telepon. Maks 2 link dan 1 telepon; balas cepat otomatis ditaruh di depan.
  • AUTHENTICATION: hanya otp (teks tombol salin kode maks 25 karakter, menit masa berlaku 1–90 atau kosong, keamanan = tambahkan saran keamanan). Isi pesan kode masuk baku dari Meta.
  • Judul gambar/video/dokumen: unggah dulu lewat media_unggah, lalu judul_media berisi jenis yang sama dan media_id-nya. Tidak boleh bersama judul teks. Satu unggahan untuk satu template; media milik usaha lain atau yang sudah dipakai template lain ditolak 400. Media ini menjadi bawaan saat template dikirim.
  • Kode kupon (hanya MARKETING): tombol {"jenis":"salin","kode":"HEMAT10"}, tanpa teks (label tombolnya baku dari WhatsApp). kode = contoh untuk dinilai Meta, maks 20 karakter tanpa spasi; kode sebenarnya diisi saat kirim (kupon). Maks 1 tombol kupon.
  • Penawaran terbatas (hanya MARKETING): penawaran = {"teks":…,"kedaluwarsa":true|false}, teks maks 16 karakter. Judul hanya gambar atau video (atau tanpa judul), tanpa kaki, isi maks 600 karakter, tombol hanya salin (contoh kode maks 15) dan link. kedaluwarsa: true = hitung mundur, waktu berakhirnya diisi saat kirim (kedaluwarsa_ms).
  • Carousel (hanya MARKETING): isi = gelembung teks di atas kartu; carousel.jenis image atau video (sama untuk semua kartu); carousel.kartu 2–10 kartu, tiap kartu media_id dari media_unggah (satu unggahan per kartu), isi opsional maks 160 karakter tanpa isian (isi semua kartu atau kosongkan semua), dan 1–2 tombol (balas, link, telepon) dengan jenis dan urutan yang sama di semua kartu. Tanpa judul, judul_media, kaki, tombol, atau penawaran di luar kartu.
  • Belum bisa lewat API (422 template_belum_didukung): tombol formulir. Buat dari dasbor.
  • Persetujuan Meta datang belakangan; pantau dengan daftar_template. Isian salah → 400 masukan_tidak_sah; ditolak Meta saat diajukan → 502 meta_menolak. Mode uji: "status":"SIMULASI", tidak dikirim ke Meta.

otomatis_baca

{"aksi":"otomatis_baca"}
→ {"ok":true,
   "otomatis":{"luarJam":{"aktif":true,"buka":"08:00","tutup":"17:00","pesan":"Klinik sedang tutup…"},
               "kata":[{"kata":["jadwal","jam praktik"],"balas":"Jadwal praktik…","dipakai":12}]},
   "bot":{"aktif":true,"sapa":"Halo, ada yang bisa kami bantu?",
          "menu":[{"judul":"Jadwal dokter","balas":"Senin–Sabtu…"}],"ice":["Jam buka?","","",""]},
   "ice_galat":null,"menu":{"otomatis":true,"bot":true}}
  • dipakai = berapa kali aturan itu membalas bulan ini. ice (pertanyaan siap-ketuk) selalu 4 slot, kosong = "".
  • ice_galat = galat terakhir saat mengirim pertanyaan siap-ketuk ke WhatsApp (null = baik).
  • Menu yang dimatikan di Menu usaha: bagiannya null (lihat menu). Keduanya mati → 403 menu_mati.

otomatis_simpan / bot_simpan

{"aksi":"otomatis_simpan",
 "luarJam":{"aktif":true,"buka":"08:00","tutup":"17:00","pesan":"Klinik sedang tutup…"},
 "kata":[{"kata":["jadwal","jam praktik"],"balas":"Jadwal praktik…"}]}
→ {"ok":true}

{"aksi":"bot_simpan","bot":{"aktif":true,"sapa":"Halo, ada yang bisa kami bantu?",
 "menu":[{"judul":"Jadwal dokter","balas":"Senin–Sabtu…"}],"ice":["Jam buka?"]}}
→ {"ok":true}
  • Aturan sama dengan dasbor. luarJam dan kata boleh salah satu; kata menggantikan seluruh daftar.
  • Batas WhatsApp: menu bot maks 10 pilihan, judul pilihan maks 24 karakter, pertanyaan siap-ketuk maks 4 × 80 karakter.
  • Isian salah → 400 masukan_tidak_sah dengan kalimat penjelas. Menu mati → 403 menu_mati.

Langganan kitasapa: langganan_baca, langganan_bayar, langganan_cek, langganan_siklus

Tampilkan status langganan kitasapa usaha Anda dan perpanjang dengan QRIS, langsung dari aplikasi Anda. Aturannya sama dengan menu Langganan di dasbor. Keempat aksi ini tetap bisa dipakai saat langganan terkunci: membayar adalah cara membukanya. Aksi tulis lain saat terkunci → 402 langganan_terkunci.

{"aksi":"langganan_baca"}
→ {"ok":true,
   "langganan":{"paket":"toko","nama_paket":"Pro","siklus":"bulanan","harga_siklus":199000,
                "status":"aktif","jatuh_tempo":"2026-10-07","hari_lagi":5,"dikunci_pada":"2026-10-10",
                "masa_coba":false,"sudah_bayar":true, …},
   "tagihan":[{"no":"INV-202610-K0012","paket":"Pro","siklus":"bulanan","periode_mulai":"2026-10-08",
               "periode_akhir":"2026-11-07","jumlah":199000,"status":"menunggu","nominal_bayar":null,
               "dibayar":null,"via":null}],
   "menunggu":{"no":"INV-202610-K0012", …},
   "paket":[{"kode":"warung","nama":"Lite","harga":99000,"harga_tahun":990000,"nomor":1,"pengguna":1,"usaha":1}, …],
   "pemilik":true}

{"aksi":"langganan_bayar","no":"INV-202610-K0012"}        ← wajib Idempotency-Key
→ {"ok":true,"no":"INV-202610-K0012","qris":"00020101021226…","nominal":199123,
   "kedaluwarsa":"2026-10-02T15:00:00+07:00","simulasi":false}

{"aksi":"langganan_cek","no":"INV-202610-K0012"}
→ {"ok":true,"status":"menunggu","dibayar":null}          ← "lunas" + waktu setelah dibayar

{"aksi":"langganan_siklus","siklus":"tahunan"}            ← wajib Idempotency-Key
→ {"ok":true,"berubah":true,"tagihan":"INV-202610-K0012","langganan":{…}}
  • Status langganan: baru, aktif, tenggang, terkunci, berhenti. menunggu = tagihan yang belum dibayar (atau null). Belum ada langganan → "langganan":null.
  • Tampilkan qris sebagai kode QR dan minta pembayaran persis sebesar nominal (beberapa rupiah di atas harga, supaya pembayaran Anda dikenali). QR berlaku sampai kedaluwarsa (60 menit); sesudahnya panggil langganan_bayar lagi dengan Idempotency-Key baru.
  • Selama QR tampil, panggil langganan_cek berkala (mis. tiap 5 detik). Begitu "status":"lunas", langganan diperpanjang dan kunci terbuka otomatis.
  • langganan_siklus berlaku mulai periode berikut; tagihan yang belum dibayar ikut berganti harga. Selama QR sungguhan masih bisa dibayar, siklus belum bisa diganti (409 konflik, pesan menyebut jamnya).
  • Tagihan bukan milik usaha Anda → 404 tidak_ditemukan. Tagihan sudah lunas/batal → 409 konflik. Mode uji: "simulasi":true, QR tidak bisa dibayar.

Idempotensi

  • Idempotency-Key berlaku 24 jam per kunci API.
  • Key dan badan sama → jawaban yang sama persis, dengan header Idempotent-Replayed: true. Pesan tidak dikirim ulang.
  • Key sama, badan berbeda → 409 idempotency_konflik. Memperbaiki isian berarti key baru.
  • Key sama saat permintaan pertama masih berjalan → 409 sedang_diproses, coba lagi sebentar.
  • Pesan yang gagal tidak dikirim ulang dengan key yang sama. Pakai key baru bila memang ingin mencoba lagi.
  • 409 hasil_tak_pasti → periksa hasilnya di dasbor dulu sebelum mengirim dengan key baru.

Batas pemakaian

  • 60 permintaan per menit per kunci. Lebih dari itu: 429 batas_laju dengan header Retry-After (detik).
  • Per alamat IP: 5 permintaan per detik (lonjakan sampai 20). Lebih dari itu: 429 tanpa badan JSON; tunggu sebentar, ulang dengan waktu dan nonce baru.
  • Maks 5 kunci aktif per usaha.
  • Badan permintaan maks 64 KB (400 badan_terlalu_besar), kecuali media_unggah maks 24 MB (413 terlalu_besar).

Batas ini menjaga nilai kualitas nomor Anda di Meta. Sistem yang macet dan mengirim berulang-ulang bisa membuat nomor diberi peringatan.

Kode galat

Jawaban selalu JSON. Sukses: {"ok": true, …}. Galat: {"ok": false, "galat": "<kode>", "pesan": "<kalimat>"}. Logika sistem Anda sebaiknya membaca galat, sedangkan pesan boleh ditampilkan ke pengguna.

HTTPgalatArti
400masukan_tidak_sah, json_tidak_sah, aksi_tidak_dikenal, idempotency_key_wajib, nomor_wajib, ke_tidak_sah, badan_terlalu_besarPeriksa isian
401kunci_tidak_sah, kunci_dicabut, waktu_tidak_sah, nonce_tidak_sah, tanda_tidak_sah, idempotency_key_tidak_sahAutentikasi gagal
402langganan_terkunciLangganan kitasapa belum dibayar (aksi langganan_* tetap bisa)
403fitur_mati, klien_tidak_aktif, cakupan_ditolak, menu_matiTidak diizinkan
404nomor_tidak_ditemukan, tidak_ditemukanData tidak ada
409ulangan, di_luar_jendela, penerima_berhenti, idempotency_konflik, sedang_diproses, hasil_tak_pasti, konflik, tidak_bisa_kirimKeadaan tidak memungkinkan
413terlalu_besarBerkas media_unggah melebihi batas jenisnya
422template_belum_disetujui, template_belum_didukung, media_judul_tidak_tersedia, media_kartu_tidak_tersediaMasalah template
429batas_lajuTerlalu sering
500galat_serverCoba lagi dengan key yang sama
502meta_menolakMeta menolak pesan (pesan_id tetap ada) atau template yang diajukan

Webhook keluar

kitasapa bisa mengabari sistem Anda saat status pesan berubah atau ada pesan masuk. Atur URL dan jenis peristiwanya di menu Integrasi. Rahasia webhook (ksw_…) tampil sekali saat dibuat.

POST https://sistem-anda.com/kitasapa
X-Kitasapa-Waktu: 1790000000
X-Kitasapa-Peristiwa: status_pesan
X-Kitasapa-Id: 3f9c…
X-Kitasapa-Tanda: hex(HMAC-SHA256(rahasia_webhook, waktu + "." + badan))

{"id":"3f9c…","peristiwa":"status_pesan","waktu":"2026-09-29T10:15:02+07:00",
 "data":{"pesan_id":5521,"nomor_id":4,"ke":"6281234567890","status":"dibaca","lewat_api":true}}
PeristiwaIsi data
status_pesanpesan_id, nomor_id, ke, status (terkirim / sampai / dibaca / gagal), lewat_api, dan galat bila gagal
pesan_masukpesan_id, nomor_id, dari, jenis, waktu, plus isi, nama_kontak bila "sertakan isi" dinyalakan
tombol_ditekanSeperti pesan_masuk, plus tombol_id dan menjawab_pesan_id
pesan_dari_hpPesan keluar ke pelanggan yang tidak dikirim lewat API ini, supaya Inbox Anda lengkap: pesan_id, nomor_id, ke, jenis, waktu, sumber (+ isi bila "sertakan isi"). sumber: hp (diketik tim di aplikasi WhatsApp Business HP, nomor tersambung dengan Coexistence), otomatis (balasan otomatis), bot (menu bot), dasbor (dikirim tim dari dasbor kitasapa), sistem (fitur kitasapa lain: siaran, pengingat, dst.). Kiriman lewat kirim_teks/kirim_template tidak pernah memicunya. Abaikan nilai sumber yang belum dikenal.
reaksiReaksi emoji pada pesan, dari pelanggan maupun tim: nomor, nomor_id, kontak, wamid_tuju, pesan_id, arah, dihapus, waktu, plus sumber untuk arah keluar dan emoji bila "sertakan isi" dinyalakan. Lihat penjelasan di bawah.
ujiDari tombol "Uji kirim" di dasbor

Peristiwa reaksi

{"id":"…","peristiwa":"reaksi","waktu":"2026-10-03T10:15:02+07:00",
 "data":{"nomor":4,"nomor_id":4,"kontak":"6281234567890","wamid_tuju":"wamid.HBgN…","pesan_id":5521,
         "arah":"masuk","dihapus":false,"waktu":"2026-10-03T10:15:00+07:00","emoji":"👍"}}
  • Reaksi emoji bukan pesan baru: reaksi tidak dikirim sebagai pesan_masuk. Centang peristiwa reaksi di menu Integrasi untuk menerimanya.
  • wamid_tuju = id WhatsApp pesan yang direaksikan; pesan_id = id pesan kitasapa-nya (null bila tidak tersimpan).
  • arah: masuk = reaksi pelanggan; keluar = reaksi tim, dengan sumber dasbor atau hp (aplikasi WhatsApp Business di HP).
  • emoji kosong ("") = reaksinya dihapus. emoji hanya ikut bila "sertakan isi" nyala; dihapus selalu ada.
  • Satu orang satu reaksi per pesan. Simpan per wamid_tuju + arah dan timpa dengan yang terbaru (bandingkan waktu); isi kiriman adalah keadaan terkini, jadi reaksi yang diganti cepat bisa tiba dua kali dengan emoji yang sama.

Memeriksa tanda tangan (PHP)

$badan = file_get_contents('php://input');
$waktu = $_SERVER['HTTP_X_KITASAPA_WAKTU'] ?? '';
$sah = ctype_digit($waktu) && abs(time() - (int)$waktu) <= 300
    && hash_equals(hash_hmac('sha256', $waktu . '.' . $badan, RAHASIA_WEBHOOK), $_SERVER['HTTP_X_KITASAPA_TANDA'] ?? '');
if (!$sah) { http_response_code(401); exit; }
  • Jawab 2xx secepatnya (batas 5 detik), proses belakangan.
  • Selain 2xx dicoba ulang setelah 1 menit, 5 menit, 15 menit, 1 jam, 3 jam, dan 6 jam.
  • Urutan kedatangan tidak dijamin. Buang kiriman ganda berdasarkan id.
  • URL wajib https:// di port 443 dengan alamat publik. Pengalihan (3xx) tidak diikuti.
  • Lima kali gagal berturut-turut → kiriman ke URL Anda ditunda 10 menit. Satu kiriman sukses memulihkannya.
  • "Sertakan isi" bawaannya mati: isi pesan dan nama pelanggan hanya dikirim bila pemilik usaha menyalakannya.

Mode simulasi

Selama nomor usaha belum siap mengirim sungguhan, semua permintaan tetap diproses penuh (autentikasi, idempotensi, pencatatan), tapi pesan tidak dikirim ke Meta. Jawabannya berisi "status": "simulasi", dan aksi info menjawab "simulasi": true. Pakai mode ini untuk menyelesaikan integrasi lebih dulu; kodenya tidak perlu diubah saat pengiriman sungguhan dibuka.

Keamanan

  • Simpan rahasia kunci hanya di server. Jangan tanam di aplikasi HP, peramban, atau repositori kode.
  • Rahasia bocor? Cabut kuncinya dari menu Integrasi, lalu buat kunci baru.
  • Log panggilan di dasbor (7 hari) hanya mencatat waktu, kunci, aksi, dan kode hasil. Isi pesan, nomor, dan kode OTP tidak pernah dicatat.

Butuh bantuan integrasi? Tim kami bisa membantu sampai panggilan pertama Anda berhasil.

Hubungi kami