Dokumentasi Zena API
Kirim pesan WhatsApp, SMS, Email, Telegram, Line, dan Facebook lewat satu API.
| Versi | Base URL | Untuk |
|---|---|---|
| v1 | https://api.zena.co.id/v1 | Format Sandeza. Untuk sistem yang sudah memakai Sandeza: cukup ganti base URL. |
| v2 | https://api.zena.co.id/v2 | API platform Zena dengan kunci API b1k_…. Untuk integrasi baru. Lihat API v2. |
Semua alamat memakai HTTPS. Request dan jawaban berformat JSON (UTF-8).
Migrasi dari Sandeza
- Ganti Base URL Send menjadi
https://api.zena.co.id/v1. Path, isi request, dan bentuk jawaban tetap sama:POST https://api.zena.co.id/v1/json/send/cgw - Pakai username dan password baru dari Zena untuk membuat
signature. Cara membuatnya sama seperti sebelumnya. - Pastikan setiap
sender_idyang Anda pakai sudah didaftarkan oleh Zena. Sender ID yang belum terdaftar dijawab dengan RC 8. - Kirimkan ke Zena alamat endpoint Anda untuk menerima status pengiriman (DR) dan pesan masuk (MO). Format keduanya sama seperti sebelumnya.
Hal kecil yang berbeda dirangkum di Perbedaan dari Sandeza.
Autentikasi
Setiap request membawa tiga field berikut di body:
| Field | Isi |
|---|---|
username | Username dari Zena. |
time | Waktu saat request dibuat, Unix timestamp dalam detik (string). Selisihnya dengan jam server Zena paling banyak 5 menit; lebih dari itu dijawab RC 11. |
signature | SHA-256 (hex) dari username + password + time, digabung tanpa pemisah. |
Contoh membuat signature:
// PHP
$time = (string) time();
$signature = hash('sha256', $username . $password . $time);
// Node.js
const time = String(Math.floor(Date.now() / 1000));
const signature = require("crypto").createHash("sha256").update(username + password + time).digest("hex");
# Python
import hashlib, time as t
ts = str(int(t.time()))
signature = hashlib.sha256((username + password + ts).encode()).hexdigest()
Jika Zena mengaktifkan daftar IP untuk akun Anda, request dari IP lain dijawab RC 6.
Mengirim pesan
POST https://api.zena.co.id/v1/json/send/cgw
Content-Type: application/json
| Field | Wajib | Keterangan |
|---|---|---|
signature | ya | Lihat Autentikasi. |
time | ya | Unix timestamp (detik). |
username | ya | Username dari Zena. |
type | tidak | "2" = satu channel atau beberapa channel sekaligus (bawaan). "1" = utama + cadangan, lihat type 1. |
ref_id | ya | ID unik buatan Anda, maksimal 50 karakter. ID yang sudah pernah diterima dijawab RC 9. Request yang ditolak (RC selain 0) tidak menyimpan ref_id-nya, jadi boleh dikirim ulang. |
subject | tidak | Dipakai sebagai subjek email. |
sender_id | ya | Sender ID yang didaftarkan Zena untuk akun Anda. |
budget_code | tidak | Diterima, tidak dipakai. |
created_by | tidak | Diterima, tidak dipakai. |
channel | ya | Objek berisi satu atau lebih channel: sms, email, telegram, line, facebook, whatsapp. |
Contoh lengkap dengan curl:
curl -X POST https://api.zena.co.id/v1/json/send/cgw \
-H "Content-Type: application/json" \
-d '{"signature":"<sha256 hex>","time":"1730458063","type":"2","username":"toko_maju","ref_id":"inv-2024-000123","subject":"Notifikasi pesanan","sender_id":"TOKOMAJU","channel":{"sms":{"message":"Pesanan Anda sudah dikirim.","msisdn":"081234567890"}}}'
Nomor tujuan (msisdn) boleh ditulis 0812…, 62812…, atau +62812….
SMS
| Field | Wajib | Keterangan |
|---|---|---|
message | ya | Isi pesan. |
msisdn | ya | Nomor tujuan. |
backup_on | tidak | Hanya untuk type 1. Lihat cadangan. |
backup_exp | tidak | Hanya untuk type 1. |
use_backup_sender | tidak | Diterima, tidak dipakai. |
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"sms": {
"message": "Kode OTP Anda 482910. Jangan berikan ke siapa pun.",
"msisdn": "081234567890"
}
}
}| Field | Wajib | Keterangan |
|---|---|---|
email | ya | Alamat tujuan. |
message | ya* | Isi email (teks). Untuk template: nilai isian, "nama:Budi;nomor:INV-01". |
template_id | tidak | ID template email yang didaftarkan Zena untuk akun Anda. Isian {nama} di template diganti dengan nilai dari message. |
attachment | tidak | Alamat https file, dipisah ;. Maksimal 5 MB per file. |
Subjek diambil dari subject request, atau dari template jika memakai template_id.
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"email": {
"email": "budi@contoh.co.id",
"message": "nama:Budi;nomor:INV-2024-000123",
"template_id": "invoice"
}
}
}
Jika ada lebih dari satu lampiran, lampiran pertama dikirim bersama isi email. Lampiran berikutnya dikirim sebagai email tersendiri dengan subjek yang sama.
Telegram, Line, Facebook
| Field | Wajib | Keterangan |
|---|---|---|
chat_id | ya | ID penerima di platform itu. |
message | ya* | Isi pesan. Boleh kosong jika ada lampiran. |
attachment | tidak | Alamat https file, dipisah ;. Setiap file dikirim sebagai pesan tersendiri setelah teks. |
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"telegram": {
"message": "Pesanan INV-2024-000123 sudah dikirim.",
"chat_id": "396731234"
}
}
}
Di ketiga platform ini, bisnis hanya bisa mengirim ke orang yang sudah pernah mengirim pesan lebih dulu ke akun bisnis tersebut. chat_id didapat dari pesan masuk.
WhatsApp: template
Template harus sudah disetujui WhatsApp di akun Anda. Bahasa template dipilih otomatis.
| Field | Wajib | Keterangan |
|---|---|---|
msisdn | ya | Nomor tujuan. |
template_id | ya | Nama template. Template yang tidak ada dijawab RC 2. |
header | tidak | Isian header: teks, gambar, video, dokumen, atau lokasi. |
message | tidak | Isian body ({{1}}, {{2}}, …). Kosongkan untuk template tanpa isian. |
button | tidak | Isian tombol: URL dinamis, OTP, katalog. |
carousel | tidak | Kartu-kartu template carousel. |
use_mm_lite | tidak | Untuk template kategori UTILITY atau AUTHENTICATION dijawab RC 25. Untuk template MARKETING, pesan dikirim sebagai template biasa. |
Body dan header teks
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"whatsapp": {
"msisdn": "081234567890",
"template_id": "konfirmasi_pesanan",
"header": {
"type": "header",
"parameters": [
{
"type": "text",
"text": "Toko Maju"
}
]
},
"message": {
"type": "body",
"parameters": [
{
"type": "text",
"text": "Budi"
},
{
"type": "text",
"text": "INV-2024-000123"
}
]
}
}
}
}
Header gambar, video, dokumen
{
"header": {
"type": "header",
"parameters": [
{
"type": "image",
"image": {
"link": "https://contoh.co.id/promo.png"
}
}
]
}
}
{
"header": {
"type": "header",
"parameters": [
{
"type": "document",
"document": {
"link": "https://contoh.co.id/invoice.pdf",
"filename": "Invoice.pdf"
}
}
]
}
}
Untuk video, ganti image dengan video. File harus bisa diunduh lewat https.
Header lokasi
{
"header": {
"type": "header",
"parameters": [
{
"type": "location",
"location": {
"latitude": "-6.1754",
"longitude": "106.8272",
"name": "Toko Maju Pusat",
"address": "Jl. Merdeka No. 1, Jakarta"
}
}
]
}
}
Tombol URL dinamis dan OTP
{
"button": [
{
"type": "BUTTON",
"sub_type": "URL",
"index": "0",
"parameters": [
{
"type": "text",
"text": "pesanan/INV-2024-000123"
}
]
}
]
}
Untuk template OTP (kategori AUTHENTICATION), isi kode yang sama di message dan di tombol:
{
"template_id": "kode_otp",
"message": {
"type": "body",
"parameters": [
{
"type": "text",
"text": "482910"
}
]
},
"button": [
{
"type": "BUTTON",
"sub_type": "URL",
"index": "0",
"parameters": [
{
"type": "text",
"text": "482910"
}
]
}
]
}
Katalog
{
"button": [
{
"type": "BUTTON",
"sub_type": "catalog",
"index": "0",
"parameters": [
{
"type": "action",
"action": {
"thumbnail_product_retailer_id": "SKU-001"
}
}
]
}
]
}
Carousel
{
"template_id": "promo_carousel",
"header": "",
"button": "",
"message": {
"type": "BODY",
"parameters": [
{
"type": "TEXT",
"text": "20OFF"
}
]
},
"carousel": {
"type": "CAROUSEL",
"cards": [
{
"card_index": 0,
"components": [
{
"type": "HEADER",
"parameters": [
{
"type": "IMAGE",
"image": {
"link": "https://contoh.co.id/kartu1.png"
}
}
]
},
{
"type": "BODY",
"parameters": [
{
"type": "TEXT",
"text": "10%"
}
]
},
{
"type": "BUTTON",
"sub_type": "QUICK_REPLY",
"index": "0",
"parameters": [
{
"type": "PAYLOAD",
"payload": "beli-1"
}
]
},
{
"type": "BUTTON",
"sub_type": "URL",
"index": "1",
"parameters": [
{
"type": "payload",
"payload": "promo/kartu1"
}
]
}
]
}
]
}
}
Huruf besar atau kecil pada type dan sub_type sama saja.
WhatsApp: format delimiter
Bentuk ringkas untuk template. Bagian dipisah :=:, beberapa isian dipisah ||.
| Field | Contoh |
|---|---|
message | text:=:Budi||text:=:INV-2024-000123 |
header teks | text:=:Toko Maju |
header gambar / video / dokumen | image:=:https://contoh.co.id/promo.pngdocument:=:https://contoh.co.id/invoice.pdf:=:Invoice.pdf |
header lokasi | location:=:-6.1754:=:106.8272:=:Toko Maju Pusat:=:Jl. Merdeka No. 1 |
button URL / OTP | button:=:url:=:pesanan/INV-2024-000123 |
button quick reply | button:=:quick_reply:=:ya |
Beberapa tombol dipisah || dan diberi nomor urut 0, 1, 2, … sesuai urutannya.
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"whatsapp": {
"msisdn": "081234567890",
"template_id": "konfirmasi_pesanan",
"header": "text:=:Toko Maju",
"message": "text:=:Budi||text:=:INV-2024-000123"
}
}
}WhatsApp: tanpa template
Pesan tanpa template hanya bisa dikirim dalam 24 jam sejak pelanggan terakhir mengirim pesan. Di luar itu, request dijawab RC 1, dan Anda perlu memakai template.
| message.type | Isi |
|---|---|
text | body.text |
image, video, audio, document | body.media.url (https). body.text (opsional) menjadi keterangan gambar/video/dokumen. |
button | body.text, body.button (maksimal 3: key, title maks. 20 karakter), header (opsional: teks, atau gambar/video/dokumen lewat media_url), footer.text (opsional). |
list | body.text, body.list.title (label tombol pembuka), body.list.sections[].title, body.list.sections[].actions[] (key, title, description). Maksimal 10 baris. Header hanya teks. |
location | body.location.latitude, longitude, name, address |
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"whatsapp": {
"msisdn": "081234567890",
"message": {
"type": "text",
"body": {
"text": "Halo Budi, pesanan Anda sedang dikemas."
}
}
}
}
}
{
"whatsapp": {
"msisdn": "081234567890",
"message": {
"type": "button",
"header": {
"type": "text",
"text": "Konfirmasi"
},
"body": {
"text": "Apakah pesanan sudah diterima?",
"button": [
{
"type": "postback",
"key": "sudah",
"title": "Sudah"
},
{
"type": "postback",
"key": "belum",
"title": "Belum"
}
]
},
"footer": {
"text": "Toko Maju"
}
}
}
}
{
"whatsapp": {
"msisdn": "081234567890",
"message": {
"type": "list",
"body": {
"text": "Pilih paket langganan",
"list": {
"title": "Lihat paket",
"sections": [
{
"title": "Paket",
"actions": [
{
"type": "postback",
"key": "basic",
"title": "Basic",
"description": "Rp 99.000/bulan"
},
{
"type": "postback",
"key": "pro",
"title": "Pro"
}
]
}
]
}
}
}
}
}
Tombol atau baris list yang dipilih pelanggan kembali sebagai pesan masuk dengan message.type = "postback" dan postback.key.
Channel whatsapp_generic dengan message berupa teks biasa juga diterima, dan dikirim sebagai pesan teks WhatsApp.
Beberapa channel sekaligus
Dengan type: "2", setiap channel di dalam channel dikirim bersamaan. Satu ref_id berlaku untuk semuanya. DR membedakannya lewat field channel.
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "2",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"sms": {
"message": "Pesanan INV-2024-000123 sudah dikirim.",
"msisdn": "081234567890"
},
"email": {
"email": "budi@contoh.co.id",
"message": "Pesanan INV-2024-000123 sudah dikirim."
}
}
}
- Semua channel diperiksa dulu sebelum ada yang dikirim. Jika ada satu yang tidak valid, seluruh request ditolak.
- Jika setidaknya satu channel terkirim, jawabannya RC 0. Channel yang ditolak dilaporkan lewat DR dengan status 4.
- Satu jenis channel hanya boleh muncul sekali dalam satu request.
Utama + cadangan (type 1)
Dengan type: "1", channel pertama di channel menjadi channel utama. Channel berikutnya baru dikirim jika channel sebelumnya memenuhi aturan backup_on miliknya.
| Kode backup_on | Cadangan dikirim jika pesan… |
|---|---|
| 1 | tidak terkirim (ditolak sebelum keluar) |
| 2 | tidak sampai (undelivered) |
| 3 | masih tertunda setelah sekian menit |
| 4 | gagal |
| 5, 6, 7 | Email: spam, bounced, blacklist (dibaca sebagai gagal) |
| 9 | WhatsApp: sudah sampai tetapi belum dibaca setelah sekian menit |
backup_on berisi kode yang dipisah ;. backup_exp berisi menit tunggu dengan urutan yang sama. Untuk selain kode 3 dan 9, isi 0.
{
"signature": "<sha256 hex>",
"time": "1730458063",
"type": "1",
"username": "toko_maju",
"ref_id": "inv-2024-000123",
"subject": "Notifikasi pesanan",
"sender_id": "TOKOMAJU",
"channel": {
"sms": {
"message": "Kode OTP Anda 482910.",
"msisdn": "081234567890",
"backup_on": "1;2;3",
"backup_exp": "0;0;10"
},
"telegram": {
"message": "Kode OTP Anda 482910.",
"chat_id": "396731234"
}
}
}
Contoh di atas: SMS dikirim lebih dulu. Telegram dikirim jika SMS ditolak, tidak sampai, atau belum sampai setelah 10 menit. Jika SMS sampai, Telegram tidak dikirim dan DR-nya berstatus 14.
Jika backup_on kosong, cadangan dikirim saat pesan ditolak atau gagal (setara 1;2;4).
Jawaban & kode RC
Setiap jawaban memakai HTTP 200. Hasilnya dibaca dari rc:
{
"rc": "0",
"ref_id": "inv-2024-000123",
"code_sms": "wMjAyMTA0MTQwODA2MjMxNTg3MTQ4",
"reason_status": ""
}
{
"rc": "8",
"ref_id": "inv-2024-000124",
"code_sms": "",
"reason_status": "Unknown Sender Id"
}
| rc | Arti |
|---|---|
| 0 | Diterima. code_sms adalah ID request di Zena. |
| 1 | Parameter atau JSON tidak valid, jenis pesan tidak didukung, atau pesan bebas di luar 24 jam. |
| 2 | Channel, template, atau konfigurasi tidak ditemukan; batas request terlampaui; atau gangguan sementara (boleh dicoba lagi). |
| 3 | Nomor, alamat, atau chat_id tujuan tidak valid. |
| 4 | Signature salah atau username tidak dikenal. |
| 5 | Akun tidak aktif. |
| 6 | IP tidak ada di daftar yang diizinkan. |
| 7 | Saldo tidak cukup. |
| 8 | Sender ID belum terdaftar untuk channel itu. |
| 9 | ref_id sudah pernah dipakai. |
| 11 | time terlalu jauh dari jam server (lebih dari 5 menit). |
| 24 | Pesan terlalu panjang (maksimal 4.096 karakter). |
| 25 | use_mm_lite pada template UTILITY atau AUTHENTICATION. |
Status pengiriman (DR)
Zena mengirim POST JSON ke alamat DR Anda setiap kali status pesan berubah. Balas dengan HTTP 2xx dalam 10 detik. Jika tidak, Zena mencoba lagi setelah 1 menit, 5 menit, 30 menit, 2 jam, dan 6 jam.
{
"status": 1,
"channel": "7",
"ref_id": "inv-2024-000123",
"time": "2026-11-14 19:12:52",
"division_id": "b6de8c1d-8e1f-4b82-9148-0a7ab9577b73",
"price": "PAID"
}
| Field | Isi |
|---|---|
status | Lihat tabel status di bawah. |
channel | 1 SMS · 2 Line · 3 Email · 4 Facebook · 5 Telegram · 7 WhatsApp |
ref_id | ref_id dari request Anda. |
time | Waktu perubahan status, WIB (YYYY-MM-DD HH:mm:ss). |
division_id | ID akun Anda di Zena. |
price | Hanya WhatsApp: PAID untuk template, FREE untuk pesan bebas atau yang tidak terkirim. |
counter_message | Hanya SMS: jumlah bagian SMS. |
| status | Arti |
|---|---|
| 1 | Delivered: sampai di penerima |
| 4 | Tidak terkirim: ditolak sebelum keluar (dalam request beberapa channel atau utama + cadangan) |
| 5 | Failed: gagal |
| 7 | Opened: email dibuka (jika dilaporkan oleh channel) |
| 9 | Read: dibaca |
| 14 | Cadangan tidak dipakai (type 1) |
Satu pesan bisa menerima beberapa DR, misalnya 1 lalu 9. Status yang datang terlambat dan mundur, misalnya 1 setelah 9, tidak dikirim.
Pesan masuk (MO)
Pesan dari pelanggan ke channel yang terdaftar untuk akun Anda dikirim sebagai POST JSON ke alamat MO Anda, dengan aturan ulang yang sama seperti DR.
{
"from": {
"avatar": "",
"description": "",
"name": "Budi",
"username": "6281234567890"
},
"message": {
"id": "184467",
"interactive": {
"body": {
"text": ""
},
"footer": {
"text": "",
"title": ""
},
"header": {
"media_url": "",
"text": "",
"type": ""
},
"items": null
},
"location": {
"address": "",
"latitude": 0,
"live_period": 0,
"longitude": 0,
"name": ""
},
"media": {
"animated": false,
"id": "",
"mime_type": "",
"name": "",
"size": 0,
"url": ""
},
"postback": {
"key": "",
"title": "",
"type": "",
"value": ""
},
"reply_id": "",
"text": "Oke kak, terima kasih",
"times": 1794630153,
"timestamp": "2026-11-14T04:22:33Z",
"type": "text"
},
"reply_token": ""
}
| Field | Isi |
|---|---|
from.username | Nomor WhatsApp atau chat_id pengirim. Pakai sebagai msisdn / chat_id untuk membalas. |
from.name | Nama pengirim, jika ada. |
message.type | text, image, video, audio, document, voice, location, atau postback (tombol / baris list yang dipilih). |
message.text | Isi teks. |
message.media.url | Alamat file untuk pesan gambar, video, audio, atau dokumen. |
message.postback | Untuk postback: key = key tombol yang dipilih, title = labelnya. |
message.times / timestamp | Waktu pesan: Unix detik / ISO 8601 UTC. |
Perbedaan dari Sandeza
- Kredensial: username dan password dari Zena, bukan yang lama.
- Status "sent": tidak dikirim sebagai DR. DR dikirim saat pesan sampai, dibaca, gagal, tidak terkirim, atau saat cadangan tidak dipakai.
- Field
price: berdasarkan jenis pesan (template = PAID), bukan tagihan per pesan. - Template email: didaftarkan lewat Zena.
- Field yang diterima tapi tidak dipakai:
use_backup_sender,budget_code,created_by.use_mm_litepada template MARKETING dikirim sebagai template biasa. - Tambahan: keterangan (caption) untuk media WhatsApp lewat
body.text, beberapa tombol dalam format delimiter, dan tombol quick reply delimiter. - Jawaban
reason_statusbisa berisi penjelasan dalam bahasa Inggris yang lebih rinci daripada daftar RC.
Batasan
- 120 request per menit per akun. Kelebihannya dijawab RC 2; coba lagi setelah satu menit.
- Ukuran body request maksimal 1 MB. File dikirim sebagai alamat https, bukan isi file.
- Pesan teks maksimal 4.096 karakter.
- Tombol WhatsApp maksimal 3, label maksimal 20 karakter. List maksimal 10 baris.
API v2
Untuk integrasi baru, API v2 menyediakan API platform Zena secara langsung:
https://api.zena.co.id/v2
- Autentikasi dengan kunci API dari panel Anda di
app.zena.co.id, menu API:Authorization: Bearer b1k_…. - Endpoint, isi request, dan jawabannya sama dengan dokumentasi di menu API panel tersebut. Ganti alamat dasarnya dengan
https://api.zena.co.id/v2. - Kunci sandbox (
b1k_test_…) menjalankan semua pemeriksaan tanpa benar-benar mengirim pesan.
| Endpoint | Fungsi |
|---|---|
GET /v2/account | Akun yang dibuka kunci ini |
GET /v2/channels | Channel yang terhubung (ID untuk channel_id) |
GET /v2/templates?channel_id=… | Template WhatsApp yang disetujui |
POST /v2/messages | Kirim teks, tombol, list, media, lokasi, atau template |
GET /v2/messages/{id} | Status pesan |
GET / PUT / DELETE /v2/webhook | Alamat webhook untuk pesan masuk dan status |
POST /v2/sandbox/inbound | Sandbox: membuat pesan masuk tiruan |
curl -X POST https://api.zena.co.id/v2/messages \
-H "Authorization: Bearer b1k_…" \
-H "Content-Type: application/json" \
-d '{"channel_id": 12, "to": "6281234567890", "text": "Halo dari API v2"}'
Jika akun Anda juga memakai API v1, jangan mengubah webhook lewat v2. Webhook itu dipakai Zena untuk mengirim DR dan MO v1.