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
- Alamat
POST https://kitasapa.com/api/integrasi.php- Format
- JSON (UTF-8), maks 64 KB per permintaan (
media_unggahmaks 24 MB) - Autentikasi
- Kunci API + tanda tangan HMAC-SHA256 per permintaan
- Pengirim
- Nomor WhatsApp usaha Anda sendiri, bukan nomor kitasapa
Memulai
- Sambungkan nomor usaha ke kitasapa lewat kitasapa.com/daftar. Akun WhatsApp Business dan nomornya tetap atas nama usaha Anda.
- Minta fitur Integrasi dinyalakan. Fitur ini dinyalakan tim kitasapa per usaha. Hubungi kami.
- Buat kunci API di dasbor, menu Integrasi (khusus pemilik usaha). Pilih izin yang dibutuhkan. Rahasia kunci hanya tampil sekali, simpan di server Anda.
- 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.
- Uji sambungan dengan aksi
info. Jawaban200berarti kunci dan tanda tangan Anda benar.
Izin kunci
| Izin | Untuk |
|---|---|
pesan:kirim | Kirim template (termasuk OTP) dan teks |
pesanan:tulis | Catat pesanan dan ubah statusnya |
pengerjaan:tulis | Pindah tahap status pengerjaan (servis, laundry, dll.) |
template:tulis | Ajukan template pesan baru ke Meta |
otomatis:tulis | Baca dan ubah balasan otomatis dan menu bot |
langganan:bayar | Lihat 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.
| Header | Isi |
|---|---|
X-Kitasapa-Kunci | ID kunci publik, diawali ks_ |
X-Kitasapa-Waktu | Waktu Unix dalam detik. Selisih dengan jam server lebih dari 300 detik ditolak. |
X-Kitasapa-Nonce | 32 karakter hex acak, sekali pakai per kunci |
Idempotency-Key | Wajib untuk aksi yang mengirim atau mengubah data. 8–100 karakter A–Z a–z 0–9 _ - :. Kosongkan untuk info. |
X-Kitasapa-Tanda | Tanda tangan HMAC-SHA256, lihat di bawah |
Menghitung tanda tangan
tanda = hex( HMAC-SHA256( rahasia, waktu + "." + nonce + "." + idempotency_key + "." + badan ) )
rahasia= teks rahasia utuh, termasuk awalanksr_.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 kirimparameteratautomboluntuk 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
| Aksi | Izin | Keterangan |
|---|---|---|
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_template | pesan:kirim | Template yang disetujui, termasuk OTP |
kirim_teks | pesan:kirim | Teks bebas, hanya dalam 24 jam sejak pesan terakhir pelanggan |
buat_pesanan | pesanan:tulis | Catat pesanan, struk terkirim otomatis |
ubah_pesanan | pesanan:tulis | Majukan status pesanan satu langkah |
pindah_tahap | pengerjaan:tulis | Pindah tahap pengerjaan dan kabari pelanggan |
media_unggah | template:tulis | Unggah gambar/video/dokumen contoh untuk judul template |
ajukan_template | template:tulis | Ajukan template baru ke Meta (aturan sama dengan dasbor) |
otomatis_baca | otomatis:tulis | Baca balasan otomatis dan menu bot sekaligus |
otomatis_simpan | otomatis:tulis | Simpan balasan di luar jam buka dan balasan kata kunci |
bot_simpan | otomatis:tulis | Simpan menu bot dan pertanyaan siap-ketuk |
langganan_baca | langganan:bayar | Status langganan kitasapa, tagihan, dan paket |
langganan_bayar | langganan:bayar | Buat QRIS untuk membayar tagihan langganan |
langganan_cek | langganan:bayar | Cek apakah tagihan sudah terbayar |
langganan_siklus | langganan:bayar | Pilih 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 menambahterpakai.- Batas Meta berlaku per portofolio bisnis, jadi angkanya sama untuk semua nomor usaha Anda. Pakai
sisauntuk 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 lewatajukan_template; aksi ini hanya membaca. statusapa adanya dari Meta:PENDING(ditinjau),APPROVED,REJECTED(lihatalasan),PAUSED,DISABLED.isian= jumlahparameteryang wajib dikirim.butuh_kode= template OTP: kirimkode, bukanparameter.tombol[].jenis:balas_cepat,url,url_dinamis(wajibteks),telepon,salin_kupon, atau jenis lain yang belum didukung.kupon= template bertombol salin kode kupon:{"indeks":1,"maks":20}(kirimkupon, maksmakskarakter).penawaran= penawaran terbatas:{"teks":"Diskon kilat","kedaluwarsa":true}(kedaluwarsatrue = wajibkedaluwarsa_ms).kartu= template carousel:[{"indeks":0,"jenis":"image","badan":"…","tombol":[{"indeks":0,"jenis":"balas_cepat","teks":"Pesan"}]}]. Ketiganyanullbila tidak dipakai template itu.bisa_dikirim= disetujui dan bentuknya didukung API ini. Bila tidak didukung, alasannya ada ditidak_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"}]}
parametermengisi{{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 webhooktombol_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 wajiburlataumedia_id. urlwajibhttps://ke host publik (port 443, tanpa nama pengguna/sandi; alamat lokal dan privat ditolak). Meta yang mengambil berkasnya.nama_berkashanya 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 menolakjudul_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}
kuponwajib 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 lewattombol.kedaluwarsa_mswajib 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"}]}
parametermengisi 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 denganjudul_media:nullatau{"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_mediauntuk semua kartu. Media tak tersedia →422 media_kartu_tidak_tersedia. Template bukan carousel menolakkartu_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.pdfberisi JPEG menjadibrosur.pdf.jpg.isi_base64boleh berawalandata:…;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, atauAUTHENTICATION.isimaks 1.024 karakter. Isian{{1}},{{2}}, … berurutan tanpa loncat, tidak boleh di awal atau akhir teks.contohwajib satu per isian (Meta memakainya untuk menilai).labelopsional (nama isian, maks 30 karakter, jumlahnya sama dengan isian).juduldankakiopsional: maks 60 karakter, satu baris, tanpa isian.tombolmaks 3:balas,link(https://),telepon. Maks 2 link dan 1 telepon; balas cepat otomatis ditaruh di depan.AUTHENTICATION: hanyaotp(teks tombol salin kode maks 25 karakter,menitmasa 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, lalujudul_mediaberisijenisyang sama danmedia_id-nya. Tidak boleh bersamajudulteks. Satu unggahan untuk satu template; media milik usaha lain atau yang sudah dipakai template lain ditolak400. Media ini menjadi bawaan saat template dikirim. - Kode kupon (hanya
MARKETING): tombol{"jenis":"salin","kode":"HEMAT10"}, tanpateks(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), tanpakaki, isi maks 600 karakter, tombol hanyasalin(contoh kode maks 15) danlink.kedaluwarsa: true= hitung mundur, waktu berakhirnya diisi saat kirim (kedaluwarsa_ms). - Carousel (hanya
MARKETING):isi= gelembung teks di atas kartu;carousel.jenisimageatauvideo(sama untuk semua kartu);carousel.kartu2–10 kartu, tiap kartumedia_iddarimedia_unggah(satu unggahan per kartu),isiopsional maks 160 karakter tanpa isian (isi semua kartu atau kosongkan semua), dan 1–2tombol(balas,link,telepon) dengan jenis dan urutan yang sama di semua kartu. Tanpajudul,judul_media,kaki,tombol, ataupenawarandi 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(lihatmenu). 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.
luarJamdankataboleh salah satu;katamenggantikan 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_sahdengan 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 (ataunull). Belum ada langganan →"langganan":null. - Tampilkan
qrissebagai kode QR dan minta pembayaran persis sebesarnominal(beberapa rupiah di atas harga, supaya pembayaran Anda dikenali). QR berlaku sampaikedaluwarsa(60 menit); sesudahnya panggillangganan_bayarlagi dengan Idempotency-Key baru. - Selama QR tampil, panggil
langganan_cekberkala (mis. tiap 5 detik). Begitu"status":"lunas", langganan diperpanjang dan kunci terbuka otomatis. langganan_siklusberlaku 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_lajudengan headerRetry-After(detik). - Per alamat IP: 5 permintaan per detik (lonjakan sampai 20). Lebih dari itu:
429tanpa 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), kecualimedia_unggahmaks 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.
| HTTP | galat | Arti |
|---|---|---|
| 400 | masukan_tidak_sah, json_tidak_sah, aksi_tidak_dikenal, idempotency_key_wajib, nomor_wajib, ke_tidak_sah, badan_terlalu_besar | Periksa isian |
| 401 | kunci_tidak_sah, kunci_dicabut, waktu_tidak_sah, nonce_tidak_sah, tanda_tidak_sah, idempotency_key_tidak_sah | Autentikasi gagal |
| 402 | langganan_terkunci | Langganan kitasapa belum dibayar (aksi langganan_* tetap bisa) |
| 403 | fitur_mati, klien_tidak_aktif, cakupan_ditolak, menu_mati | Tidak diizinkan |
| 404 | nomor_tidak_ditemukan, tidak_ditemukan | Data tidak ada |
| 409 | ulangan, di_luar_jendela, penerima_berhenti, idempotency_konflik, sedang_diproses, hasil_tak_pasti, konflik, tidak_bisa_kirim | Keadaan tidak memungkinkan |
| 413 | terlalu_besar | Berkas media_unggah melebihi batas jenisnya |
| 422 | template_belum_disetujui, template_belum_didukung, media_judul_tidak_tersedia, media_kartu_tidak_tersedia | Masalah template |
| 429 | batas_laju | Terlalu sering |
| 500 | galat_server | Coba lagi dengan key yang sama |
| 502 | meta_menolak | Meta 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}}
| Peristiwa | Isi data |
|---|---|
status_pesan | pesan_id, nomor_id, ke, status (terkirim / sampai / dibaca / gagal), lewat_api, dan galat bila gagal |
pesan_masuk | pesan_id, nomor_id, dari, jenis, waktu, plus isi, nama_kontak bila "sertakan isi" dinyalakan |
tombol_ditekan | Seperti pesan_masuk, plus tombol_id dan menjawab_pesan_id |
pesan_dari_hp | Pesan 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. |
reaksi | Reaksi 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. |
uji | Dari 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 peristiwareaksidi menu Integrasi untuk menerimanya. wamid_tuju= id WhatsApp pesan yang direaksikan;pesan_id= id pesan kitasapa-nya (nullbila tidak tersimpan).arah:masuk= reaksi pelanggan;keluar= reaksi tim, dengansumberdasboratauhp(aplikasi WhatsApp Business di HP).emojikosong ("") = reaksinya dihapus.emojihanya ikut bila "sertakan isi" nyala;dihapusselalu ada.- Satu orang satu reaksi per pesan. Simpan per
wamid_tuju+arahdan timpa dengan yang terbaru (bandingkanwaktu); 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