Skip to content
S SiPesan Docs
ID EN

API Sandeza (H2H & CGW)

Halaman ini untuk klien yang memindahkan integrasi Sandeza (PT Sprint Asia) ke SIPESAN. Endpoint, bentuk payload, dan kode balasan dipertahankan, jadi yang perlu Anda ubah hanya base URL dan kredensial. Yang TIDAK sama dicantumkan lengkap di bagian terakhir.

Endpoint

Kegunaan URL
Kirim template (H2H) https://omnimanguntama.id/h2h/json/cgw
Tanya status (H2H) https://omnimanguntama.id/h2h/inq_status.php
Kirim pesan chat (CGW) https://omnimanguntama.id/cgw/{channel}/send/{clientID}

Contoh request

Kirim template (H2H)

Password tidak pernah ikut dikirim. Yang dikirim signature, dan kami menerima DUA bentuk. Bentuk vendor: SHA-256 dari username + password + time disambung tanpa pemisah, bersama username dan time yang sama persis dipakai menghitungnya; pakai ini kalau Anda pindah dari Sandeza, karena kode Anda sudah menghasilkannya. Bentuk ringkas: SHA-256 dari username + password saja, tanpa time. Field time tetap wajib ada di payload pada kedua bentuk, hanya isinya yang tidak lagi kami periksa.

curl -X POST https://omnimanguntama.id/h2h/json/cgw \
  -H 'Content-Type: application/json' \
  -d '{
  "username"   : "USERNAME",
  "time"       : "1756282800",
  "signature"  : "sha256(USERNAME + PASSWORD + time)",
  "type"       : "2",
  "ref_id"     : "INV-2026-0001",
  "subject"    : "Notifikasi tagihan",
  "sender_id"  : "6281100001111",
  "budget_code": "billing",
  "channel": {
    "whatsapp": {
      "msisdn"     : "628123456789",
      "template_id": "nama_template_anda",
      "header"     : "",
      "message"    : "text:=:Budi",
      "attachment" : "",
      "backup_on"  : "",
      "backup_exp" : ""
    }
  }
}'

Kirim pesan chat (CGW)

CGW memakai header Auth-username dan Auth-password, bukan signature, dan clientID ada di dalam URL-nya. Kirim hanya lewat HTTPS: passwordnya berjalan apa adanya di header, persis seperti pada vendor.

curl -X POST https://omnimanguntama.id/cgw/{channel}/send/{clientID} \
  -H 'Auth-username: USERNAME' \
  -H 'Auth-password: PASSWORD' \
  -H 'Content-Type: application/json' \
  -d '{
  "id"        : "MSG-0001",
  "reply_id"  : "",
  "times"     : 0,
  "timestampt": "2026-08-27T15:00:00.000000+07:00",
  "reply_token": "",
  "recipient" : "628123456789",
  "type"      : "text",
  "body"      : { "text": "Halo, tagihan Anda sudah terbit." }
}'

Dari nomor WhatsApp yang mana pesan keluar

Ini yang perlu Anda pastikan lebih dulu: tanpa ikatan channel yang benar, tidak ada satu pun kiriman yang berhasil. Nomor pengirimnya ditentukan berbeda di kedua protokol.

H2H memakai sender_id

Field sender_id di payload Anda menentukan nomor pengirimnya. Tiap sender_id yang Anda pakai didaftarkan ke sebuah channel WhatsApp pada kredensial Anda di panel. sender_id yang tidak terdaftar dijawab rc=8, bukan dikirim dari nomor lain. Cara paling aman adalah mengisinya dengan nomor WhatsApp itu sendiri: nilai berbentuk nomor kami cocokkan dengan nomor channelnya, jadi ikatan yang salah ketik ketahuan sebelum satu pesan pun terkirim. Beda format tidak masalah, 0812… dan 62812… dianggap sama, dan nomor yang Anda tulis dengan pemisah tetap cocok. Di panel nilainya disimpan sebagai angka saja berawalan 62, dan bentuk itulah yang paling aman Anda salin ke payload.

Satu nomor untuk semua sender_id

Kalau semua kiriman Anda keluar dari satu nomor, daftarkan satu ikatan dan kosongkan kolom sender_id-nya. Ikatan itu melayani sender_id apa pun yang Anda kirim. Kalau ada satu sender_id yang harus keluar dari nomor berbeda, daftarkan ikatan tersendiri untuknya: ikatan yang menyebut sender_id persis selalu menang atas yang dikosongkan.

CGW memakai kredensialnya sendiri

Payload CGW tidak membawa apa pun soal channel, jadi kredensial Anda yang memilihnya. Satu kredensial CGW menunjuk tepat satu channel WhatsApp. Kalau Anda butuh mengirim dari dua nomor, minta kredensial kedua, jangan menambah ikatan kedua pada kredensial yang sama: kami menolak kiriman dari kredensial yang menunjuk lebih dari satu channel alih-alih memilih salah satunya, karena pilihan itu tidak bisa Anda tebak dan balasan pelanggan akan masuk ke nomor yang tidak Anda pantau.

subject tidak memilih channel

subject disimpan apa adanya bersama catatan pesan Anda dan tidak pernah dipakai memilih nomor pengirim. Menaruh nama atau nomor channel di sana tidak berpengaruh apa pun; yang menentukan tetap sender_id untuk H2H dan kredensial untuk CGW.

Channel terikat berhenti dijawab otomasi kami

Percakapan yang lahir di channel yang terikat ke kredensial Anda dimiliki bot Anda, jadi AI dan chatbot SIPESAN berhenti menjawab di sana. Itu disengaja: tanpa itu pelanggan menerima dua balasan untuk satu pertanyaan.

Angka status yang bertabrakan

H2H dan CGW memakai bilangan yang sama untuk arti yang berbeda. Menyalin penanganan status dari satu protokol ke protokol lain tidak memunculkan error apa pun, hanya status yang salah di dashboard Anda. Periksa tabel ini sebelum menyalin satu angka pun.

Angka Artinya di H2H Artinya di CGW
1 Sampai di perangkat Terkirim ke operator
2 Tidak sampai Sampai di perangkat
3 Menunggu Dibaca

Skala status lengkap

Keadaan H2H CGW
Terkirim ke operator tidak dilaporkan 1
Sampai di perangkat 1 2
Dibaca 9 3
Menunggu 3 0
Tidak terkirim 4 -1
Tidak sampai 2 -2
Gagal 5 -3

Apa yang kami periksa sebelum pesan berangkat

Berurutan. Yang gagal di sini dijawab seketika sebagai rc bukan-0, jadi Anda tahu di detik yang sama, bukan lewat webhook beberapa menit kemudian.

Diperiksa Gagal jadi rc
Signature, username, dan time cocok, dan signature-nya untuk isi pesan ini. 4
ref_id belum pernah dipakai. Ia dicatat lebih dulu, jadi ref_id yang sama tidak bisa dikirim dua kali meski percobaan pertama gagal. 9
Hanya channel whatsapp, dan type bukan primary+backup. Rute cadangan belum kami layani. 2
sender_id menunjuk channel yang terdaftar pada kredensial Anda. Kalau sender_id itu berbentuk nomor, ia harus nomor channel tersebut. 8
Bentuk payload channel bisa dibaca, dan msisdn nomor Indonesia yang sah. 1
template_id ada dan berstatus APPROVED pada WABA channel tersebut. 2
Jumlah parameter di message sama persis dengan jumlah placeholder template. Kurang atau lebih ditolak di sini, bukan dibiarkan gagal di Meta. 1
Saldo cukup untuk kategori template tersebut. 6

rc=0 berarti diterima, bukan terkirim. Pesan Anda diantrekan setelah semua pemeriksaan lolos, dan panggilan ke Meta terjadi sesudah response rc=0 sudah Anda pegang. Hasil pengiriman sebenarnya hanya datang lewat webhook status.

Kode balasan (rc)

rc dikirim sebagai STRING, bukan angka. Kolom "Kapan" adalah yang membuat daftar ini berguna: ia menyebut keadaan yang menghasilkannya, bukan sekadar menerjemahkan namanya.

rc Arti Kapan
0 Berhasil Kiriman diterima dan diteruskan. code_sms berisi codetrx untuk inq_status.php.
1 Parameter tidak sah Body bukan JSON, ref_id kosong, type di luar 1 dan 2, channel kosong, format parameter channel salah, atau attachment diisi.
2 Kesalahan internal Template tidak ditemukan, channel tidak dilayani, type=1, harga belum diatur untuk kategori template Anda, atau lebih dari satu channel dalam satu kiriman.
3 Penerima tidak sah msisdn kosong atau bukan nomor Indonesia yang sah. Format dengan 0, 62, atau + sama-sama diterima.
4 Signature tidak sah Signature salah, username tidak dikenal, time di luar toleransi, atau signature dipakai ulang untuk isi pesan yang berbeda.
6 IP tidak diizinkan Kredensial dibatasi ke daftar IP tertentu dan permintaan datang dari alamat lain.
7 Kuota tidak cukup Saldo tidak mencukupi atau masa aktif berakhir.
8 Sender Id tidak dikenal sender_id belum diikat ke nomor WhatsApp mana pun, atau template_id dikirim kosong.
9 Nomor referensi berulang ref_id sudah pernah dipakai. Berlaku juga ketika kiriman pertama gagal: satu ref_id hanya bisa dipakai sekali.

Webhook

Kami mengirim dua jenis webhook: perubahan status pesan yang Anda kirim, dan pesan masuk dari pelanggan. Aturan di bawah berlaku untuk keduanya.

rc=0 berarti diterima, bukan terkirim

Setelah semua pemeriksaan lolos, pesan Anda diantrekan; panggilan ke Meta terjadi setelah response rc=0 sudah Anda terima. Jadi rc=0 berarti "diterima dan akan dikirim". Hasil pengiriman sebenarnya hanya datang lewat webhook status, dan kegagalan dari Meta tidak pernah muncul sebagai rc. Klien yang menghitung rc=0 sebagai terkirim akan salah membaca laporannya sendiri.

Mendaftarkan URL

Dua URL terpisah pada kredensial Anda di panel: satu untuk status pengiriman, satu untuk pesan masuk. Keduanya opsional, dan yang dikosongkan berarti kami tidak mengirim apa pun ke sana. Mengisi URL pesan masuk juga menetapkan titik mulai: hanya pesan yang tiba SETELAH itu yang dikirimkan, supaya integrasi baru tidak kebanjiran riwayat.

Memverifikasi bahwa kiriman berasal dari kami

Header webhook-signature berisi v1,HMAC-SHA256 atas webhook-id.webhook-timestamp.body memakai webhook secret Anda. Yang ditandatangani adalah BODY MENTAH, jadi hitung HMAC sebelum body itu di-parse: JSON yang di-decode lalu di-encode ulang menghasilkan byte berbeda dan signature-nya tidak akan pernah cocok.

Balasan yang kami anggap berhasil

Balas dengan {"code":"0"}. Kami membaca isi balasannya, bukan hanya kode HTTP-nya, jadi 200 OK yang berisi hal lain tetap kami hitung gagal dan kami coba ulang. Ini disengaja: endpoint yang sedang rusak sering tetap menjawab 200.

Endpoint Anda harus tahan kiriman ganda

Kiriman yang gagal akan dicoba ulang, jadi payload yang sama bisa tiba lebih dari sekali. Pakai webhook-id untuk mengenali kiriman yang sudah pernah Anda proses. Untuk status, urutan tiba juga tidak dijamin: status yang lebih lama bisa menyusul setelah yang lebih baru, dan menimpanya akan membuat pesan yang sudah dibaca kembali terlihat sekadar terkirim.

Bentuk payload

Status pengiriman punya dua bentuk, dipilih dari protokol kredensial Anda. Keduanya bukan sekadar beda nama field: angka status-nya memakai skala berbeda, dan itulah tabrakan yang dijelaskan di atas.

Status pengiriman, bentuk H2H

Dikirim saat status sebuah pesan berubah, untuk kredensial berprotokol H2H. Field berada di tingkat teratas, tanpa pembungkus.

URL diambil dari status_webhook_url · skala status H2H

Field Keterangan
status Angka pada skala H2H. 1 berarti sampai di perangkat, 9 berarti dibaca.
channel Kode channel dalam penamaan H2H.
ref_id ref_id yang Anda kirim saat mengirim pesannya. Inilah yang Anda pakai mencocokkan status ini dengan pesan Anda.
time Waktu perubahan status terjadi, bukan waktu kiriman webhook ini.
division_id Diteruskan apa adanya dari kredensial Anda. Kosong kalau tidak diisi.

Status pengiriman, bentuk CGW

Dikirim saat status sebuah pesan berubah, untuk kredensial berprotokol CGW. Seluruh isinya dibungkus di dalam objek status.

URL diambil dari status_webhook_url · dibungkus di dalam status · skala status CGW

Field Keterangan
message_id Id yang ANDA kirim saat mengirim pesannya, bukan id kami.
provider_message_id Id dari WhatsApp. Berguna saat menelusuri bersama kami.
recipient_id Nomor tujuan pesannya.
status Angka pada skala CGW, dan bukan skala yang sama dengan bentuk H2H. 2 berarti sampai di perangkat, 3 berarti dibaca.
tid Id transaksi dari sisi kami.
times Waktu perubahan status sebagai detik Unix.
timestamp Waktu yang sama dalam bentuk ISO 8601.

Pesan masuk dari pelanggan

Dikirim saat pelanggan mengirim pesan ke channel yang ditangani bot Anda. Pesan pada channel yang tidak ditugaskan ke Anda tidak dikirimkan.

URL diambil dari content_webhook_url

Field Keterangan
from Objek berisi name dan username. username adalah nomor pelanggan.
message Isi pesannya: id, text, type, times, timestamp, dan reply_id bila pesan itu membalas pesan lain. type bernilai postback bila pelanggan menekan tombol, dan value di dalamnya adalah key tombol yang Anda kirim, bukan tulisan di tombolnya.
reply_token Selalu kosong. Ada supaya bentuknya tetap sama dengan yang Anda terima sebelumnya, dan tidak dipakai untuk apa pun.

Yang perlu Anda lakukan saat pindah

Base URL

Ganti base URL ke domain SIPESAN Anda. Path, nama field, dan struktur response tetap sama. Satu hal yang berbeda: isi reason_status kami lebih panjang pada penolakan: ia menyebut apa yang salah, bukan sekadar bahwa ada yang salah. Kalau kode Anda mencocokkan reason_status dengan string persis, cocokkan awalannya saja.

Kredensial

Username dan password baru dibuat di panel SIPESAN, grup menu Developers. Cara memakainya tidak berubah: CGW tetap mengirim header Auth-username dan Auth-password, H2H tetap mengirim signature SHA256(username + password + time) di dalam body.

Nama pengirim (sender_id)

sender_id Anda dipetakan ke nomor WhatsApp di panel, lewat Ikatan Channel pada kredensial. Sampai ikatan itu dibuat, kiriman dijawab rc=8 Unknown Sender Id.

Template

template_id merujuk nama template WhatsApp yang sudah disetujui Meta pada akun Anda di SIPESAN. Template lama Anda di Sandeza tidak ikut pindah dan perlu didaftarkan ulang.

Riwayat codetrx TIDAK ikut pindah

codetrx yang Anda simpan dari kiriman lama hanya dikenal oleh sistem Sandeza. Setelah pindah, inq_status.php hanya menemukan kiriman yang dibuat lewat SIPESAN. Simpan riwayat lama Anda sebelum cutover kalau masih dibutuhkan.

Webhook

URL webhook Anda didaftarkan pada kredensial di panel. Bentuk payload tidak berubah. Setiap kiriman kami tanda tangani: header webhook-signature berisi v1,HMAC-SHA256 atas webhook-id.webhook-timestamp.body memakai webhook secret Anda. Balas dengan {"code":"0"} supaya kami menganggapnya diterima.

Batasan IP (opsional)

Anda bisa membatasi kredensial ke alamat IP server Anda sendiri di panel. Dibiarkan kosong berarti diterima dari mana saja, sama seperti sebelumnya.

Yang sengaja kami buat berbeda

Daftar ini lengkap. Selain yang tercantum di sini, jawaban kami mengikuti Sandeza.

Username tidak dikenal dijawab rc=4

Sandeza menjawab rc=5 Invalid Corporate untuk username yang tidak ada, dan rc=4 Invalid Sign untuk signature yang salah. Kami menjawab rc=4 untuk keduanya, supaya jawaban kami tidak bisa dipakai menebak username mana yang terdaftar. Klien yang kredensialnya benar tidak pernah menyentuh cabang ini.

attachment pada channel WhatsApp ditolak

Pesan template WhatsApp hanya bisa membawa satu media, dan tempatnya sudah dipakai field header. Mengisi attachment dijawab rc=1 Invalid Parameter Channel, bukan diterima lalu diabaikan, supaya Anda tahu lampiran itu tidak akan sampai.

Bentuk signature ringkas, dan harganya

Selain bentuk vendor, kami menerima SHA-256 dari username + password saja. Nilainya TETAP untuk satu kredensial, jadi ia berperilaku seperti token: siapa pun yang menangkap satu request Anda bisa memakai signature itu untuk mengirim pesan lain, sampai passwordnya diganti. Bentuk vendor yang menyertakan time tidak punya sifat itu, dan karena itu tetap yang kami anjurkan. Kalau Anda memakai bentuk ringkas, kirim hanya lewat HTTPS, isi allowlist IP pada kredensial Anda, dan pakai tombol Buat Ulang Password begitu ada dugaan bocor. Pengiriman ulang request yang sama persis tetap tertahan oleh ref_id yang unik.

Umur time tidak diperiksa

Sandeza menolak signature yang timenya sudah lewat. Kami tidak, karena time boleh tidak ikut dalam signature sama sekali, dan menolak nilai yang tidak mengikat apa pun hanya akan menyingkirkan klien yang jam servernya meleset. Field time tetap wajib ada dan tetap harus berbentuk epoch detik atau YYYYMMDDHHIISS.

Signature berlaku untuk satu isi pesan

Satu signature hanya sah untuk satu isi pesan. Mengulang request yang sama persis tetap dilayani seperti biasa, tetapi memakai ulang signature untuk payload yang berbeda dijawab rc=4. Ini menutup risiko satu signature yang bocor dipakai mengirim pesan lain atas nama Anda.

Kami menolak lebih awal, dan menyebut sebabnya

Beberapa payload yang Sandeza terima lalu gagalkan belakangan, kami tolak di depan: jumlah parameter message harus persis sebanyak placeholder pada template, field header harus cocok dengan format header template (dan tidak boleh ada kalau templatenya tidak punya header), dan sender_id yang berbentuk nomor harus sama dengan nomor channel yang diikat padanya. Kalau tidak, rc=8. Alasannya sama untuk ketiganya: kegagalan yang datang belakangan sampai kepada Anda sebagai status, bukan sebagai jawaban request, dan jauh lebih mahal ditelusuri.

Channel yang dilayani

Saat ini WhatsApp saja, untuk kedua protokol. Channel lain dijawab dengan kode penolakan milik protokolnya sendiri: rc=2 Channel Not Found untuk H2H, dan meta.status false untuk CGW.

Primary Backup (type=1) belum dilayani

Kiriman dengan type=1 dijawab rc=2 Routes Not Found, bukan diterima lalu dikirim sebagai channel tunggal. Menerimanya akan membuat Anda mengira rute cadangan aktif padahal tidak ada. Pakai type=2.