Panduan produk
Webhook pesan masuk (listener)
Listener menghubungkan salah satu nomor WhatsApp Anda ke Unibox dan dapat mengirim webhook pesan masuk ke server Anda. Di dashboard, listener dikelola di halaman Incoming Triggers (pemicu pesan masuk). Setelah sebuah nomor menjadi listener, chat-nya tersinkron ke kotak masuk Unibox, dan jika Anda menambahkan URL webhook, Wbiztool mengirim setiap pesan baru ke server Anda. Gunakan webhook untuk mencatat percakapan di CRM, memberi tahu tim Anda, atau membuat balasan otomatis.
Sebelum memulai#
- Add-on Unibox. Biayanya $20/bulan atau $200/tahun per nomor WhatsApp, dan jumlah listener yang dapat Anda miliki sama dengan jumlah add-on. Beli di bagian Available Add-ons pada Billing & Plans di dalam dashboard. Tanpa add-on ini, halaman Incoming Triggers tetap terbuka, tetapi mengklik Add New Listener menampilkan Unibox Add-on Required dengan tombol Subscribe to Unibox Add-on.
- Nomor WhatsApp yang terhubung di WhatsApp settings. Hanya nomor terhubung yang belum menjadi listener yang dapat ditambahkan.
- Anda harus menjadi owner atau editor workspace.
- Untuk webhook: URL publik (gunakan
https) dengan panjang maksimal 100 karakter yang menerima permintaanPOSTdengan body JSON.
Menambahkan listener#
Buka Incoming Triggers
Di sidebar, buka Unibox lalu klik Incoming Triggers, atau buka Incoming Triggers.
Mulai listener baru
Klik kartu Add New Listener.
Pilih nomor
Pilih di Select WhatsApp Number. Jika daftar menampilkan No available WhatsApp numbers, berarti setiap nomor yang terhubung sudah menjadi listener, atau tidak ada nomor yang terhubung.
Tambahkan webhook (opsional)
Masukkan Webhook URL (Optional). Setelah Anda mengetik URL, Webhook Events muncul: biarkan Incoming Messages (pesan masuk), Outgoing Messages (pesan keluar), atau keduanya tercentang. Anda dapat menambahkan atau mengubah URL nanti.
Simpan
Klik Add Listener. Listener muncul sebagai kartu dengan status Active. Jika Anda memasukkan URL, secret webhook dibuat untuknya.
Mengelola listener#
Setiap kartu menampilkan nomor, statusnya, Webhook URL: (atau Not configured), Last Activity: (kapan terakhir kali nomor diperiksa untuk pesan baru, atau Never), dan Webhook Secret:, yang tersembunyi sampai Anda mengklik tombol mata.
Buka menu ⋮ pada kartu untuk:
| Tindakan | Yang terjadi |
|---|---|
| Edit | Mengubah URL webhook, event webhook, atau secret. Nomornya tidak dapat diubah. |
| Disable / Enable | Disable mengubah listener menjadi Inactive: nomor berhenti tersinkron ke kotak masuk dan tidak ada webhook yang dikirim. Enable membuatnya Active lagi. |
| Delete | Menghapus listener setelah Anda mengonfirmasi. Percakapan yang sudah ada di kotak masuk tetap ada. Menambahkan nomor yang sama lagi nanti akan memulihkan listener. Jika Anda memasukkan URL webhook saat menambahkannya kembali, listener tetap memakai secret webhook sebelumnya, jika ada, alih-alih mendapat yang baru. |
Status listener#
| Status | Arti |
|---|---|
| Active | Pesan tersinkron dan webhook dikirim selama nomor terhubung. |
| Pending | Nomor tidak terhubung saat listener dibuat, misalnya oleh Zapier. Klik Enable setelah nomor terhubung. |
| Inactive | Dinonaktifkan. Tidak ada yang tersinkron dan tidak ada webhook yang dikirim. |
Statistik#
| Kartu | Yang ditampilkan |
|---|---|
| Active Listeners | Semua listener di halaman, termasuk yang dinonaktifkan. |
| Available Numbers | Nomor terhubung yang belum menjadi listener. Nomor yang listener-nya sudah Anda hapus tetap dihitung sebagai terpakai di sini, sehingga angkanya bisa lebih kecil daripada yang sebenarnya dapat Anda tambahkan. |
| Total Limit | Berapa banyak listener yang diizinkan add-on Unibox Anda. |
| Messages Today | Belum dilacak; selalu menampilkan 0. |
Mengubah URL webhook atau event#
Buka listener
Klik ⋮ pada kartu, lalu Edit.
Perbarui pengaturan
Ubah Webhook URL (Optional) dan Webhook Events. Kosongkan URL untuk menghentikan webhook bagi nomor ini: secret-nya juga dihapus, dan secret baru dibuat jika Anda menambahkan URL lagi.
Simpan
Klik Update Listener.
Membuat ulang secret#
Di Edit Listener, klik tombol refresh di sebelah Webhook Secret lalu konfirmasi. Secret baru langsung disimpan, bahkan jika Anda kemudian menutup dialog tanpa mengklik Update Listener, dan permintaan ditandatangani dengan secret tersebut sejak saat itu. Segera perbarui server Anda dengan secret baru.
Cara webhook dikirim#
Wbiztool mengirim satu permintaan POST ke URL Anda untuk setiap pesan baru yang ditemukan saat nomor tersinkron, yang terjadi setiap beberapa menit selama nomor terhubung dan tidak sibuk mengirim pesan.
- Event:
message_receiveduntuk pesan yang dikirim orang ke nomor Anda, danmessage_sentuntuk pesan yang dikirim dari nomor tersebut (dari ponsel, kampanye, atau API). Hanya event yang dicentang di Webhook Events yang dikirim. - Balasan dari kotak masuk Unibox biasanya tidak memicu
message_sent, karena kotak masuk sudah memilikinya saat sinkronisasi berjalan. - Respons: balas dengan status HTTP
2xxapa pun dalam 8 detik. - Percobaan ulang: timeout, error koneksi,
429, atau respons5xxdicoba ulang hingga 3 kali, setelah 30 detik, 1 menit, dan 2 menit. Respons lain, seperti400atau404, tidak dicoba ulang. 410 Gone: Wbiztool menghapus URL webhook dari listener dan berhenti mengirim ke sana. Pesan tetap diimpor ke Unibox.- Urutan: permintaan dikirim secara independen dan dapat tiba tidak berurutan. Urutkan berdasarkan
message.timestampjika urutan penting.
Header#
| Header | Nilai |
|---|---|
Content-Type | application/json |
X-Wbiztool-Event | message_received atau message_sent |
X-Wbiztool-Timestamp | Kapan webhook dikirim, dalam ISO 8601 UTC. Sama dengan timestamp di body. |
X-Wbiztool-Webhook-Id | ID listener. Sama dengan webhook_id di body. |
X-Wbiztool-Signature | sha256= diikuti tanda tangan. Dikirim setiap kali listener memiliki secret, yang selalu terjadi jika URL diatur. |
Payload#
{
"event": "message_received",
"timestamp": "2026-09-16T10:31:12.482913+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0C1A2B3D4E5F60718",
"type": "chat",
"content": "Hi, is my order #4821 out for delivery?",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:29:58+00:00",
"whatsapp_timestamp": 1789554598,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"lid": null,
"phone_hidden": false,
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_sent",
"timestamp": "2026-09-16T10:33:40.117205+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0F9E8D7C6B5A40312",
"type": "chat",
"content": "Yes, it will reach you today by 6 PM.",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:31:04+00:00",
"whatsapp_timestamp": 1789554664,
"direction": "outgoing",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"lid": null,
"phone_hidden": false,
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:36:02.904311+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected]_3EB0A7B6C5D4E3F20109",
"type": "image",
"content": "📸 Image",
"from": "919876543210",
"from_name": "Aman",
"to": "919812345678",
"timestamp": "2026-09-16T10:34:51+00:00",
"whatsapp_timestamp": 1789554891,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0,
"media": {
"filename": "",
"mimetype": "image/jpeg",
"size": 245760
}
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "919876543210",
"lid": null,
"phone_hidden": false,
"name": "Aman",
"is_group": false,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
}
}{
"event": "message_received",
"timestamp": "2026-09-16T10:40:15.330187+00:00",
"webhook_id": 42,
"whatsapp_client_id": "678",
"whatsapp_phone": "919812345678",
"message": {
"id": "[email protected][email protected]",
"type": "chat",
"content": "Is the store open on Sunday?",
"from": "120363041234567890",
"from_name": "Acme Loyalty Club",
"to": "919812345678",
"timestamp": "2026-09-16T10:39:02+00:00",
"whatsapp_timestamp": 1789555142,
"direction": "incoming",
"status": "pending",
"is_forwarded": false,
"forwarding_score": 0
},
"contact": {
"whatsapp_id": "[email protected]",
"phone": "120363041234567890",
"lid": null,
"phone_hidden": false,
"name": "Acme Loyalty Club",
"is_group": true,
"is_business": false
},
"organisation": {
"id": "10314",
"name": "Acme Stores"
},
"group": {
"name": "Acme Loyalty Club"
}
}Nomor, ID, dan nama di atas hanyalah contoh.
Field tingkat atas#
| Field | Tipe | Deskripsi |
|---|---|---|
event | string | message_received atau message_sent. |
timestamp | string | Kapan webhook dikirim (ISO 8601, UTC). |
webhook_id | integer | ID listener. |
whatsapp_client_id | string | ID nomor WhatsApp Anda, seperti yang ditampilkan di WhatsApp settings. |
whatsapp_phone | string | Nomor WhatsApp Anda. |
message | object | Pesan. Lihat di bawah. |
contact | object | Orang atau grup lawan percakapan. Lihat di bawah. |
organisation | object | id (string) dan name workspace Anda. |
group | object | Hanya untuk chat grup: name, nama grup. |
Field message#
| Field | Tipe | Deskripsi |
|---|---|---|
id | string | ID WhatsApp untuk pesan. Gunakan untuk mengabaikan duplikat. |
type | string | chat untuk teks. Selain itu, tipe dari WhatsApp, seperti image, video, audio, ptt (pesan suara), document, sticker, atau location. |
content | string | Teks untuk pesan chat. Untuk media, berupa label: 📸 Image, 🎥 Video, 🎵 Audio, 🎤 Voice Message, 😊 Sticker, Document: diikuti teks dokumen (Document jika tidak ada teks), atau nama tipe dalam huruf kapital di awal kata untuk tipe lainnya, seperti Location. Keterangan (caption) tidak disertakan. |
from | string | Selalu nomor kontak (atau ID grup), di kedua arah. |
from_name | string | Nama kontak atau grup. Bisa kosong. |
to | string | Selalu nomor WhatsApp Anda, di kedua arah. |
timestamp | string | Kapan pesan dikirim di WhatsApp (ISO 8601, UTC). |
whatsapp_timestamp | integer | Waktu yang sama sebagai Unix timestamp dalam detik. |
direction | string | incoming atau outgoing. Gunakan field ini, bukan from dan to, untuk mengetahui arah pesan. |
status | string | Saat ini selalu pending. Jangan mengandalkannya untuk status terkirim atau dibaca. |
is_forwarded | boolean | Apakah pesan diteruskan. |
forwarding_score | integer | Berapa kali pesan diteruskan. |
media | object | Untuk pesan media jika detailnya tersedia: filename, mimetype, dan size dalam byte. File itu sendiri tidak disertakan. |
quoted_message_id | string | Hanya jika pesan membalas pesan lain. |
Field contact#
| Field | Tipe | Deskripsi |
|---|---|---|
whatsapp_id | string | WhatsApp ID, seperti [email protected] untuk orang atau …@g.us untuk grup. |
phone | string | Nomor tanpa +, atau ID grup untuk grup. Kosong jika WhatsApp menyembunyikan nomor kontak. |
lid | string or null | ID privasi WhatsApp untuk kontak, seperti 62337239240946@lid, jika WhatsApp menggunakannya. Jika tidak, null. |
phone_hidden | boolean | true jika WhatsApp tidak membagikan nomor kontak. Wbiztool mengisi nomor tersebut setiap kali WhatsApp menyediakannya. |
name | string | Nama yang dimiliki Wbiztool untuk kontak, atau nama grup. Bisa kosong. |
is_group | boolean | true untuk chat grup. |
is_business | boolean | true untuk akun WhatsApp Business, jika diketahui. |
Memverifikasi tanda tangan#
Setiap permintaan ditandatangani dengan secret listener Anda menggunakan HMAC-SHA256. Tanda tangan dihitung dari body permintaan mentah persis seperti yang diterima, dan dikirim sebagai sha256= ditambah digest hex huruf kecil di X-Wbiztool-Signature.
Selalu hitung tanda tangan dari byte mentah sebelum mem-parsing JSON. Mem-parsing lalu meng-encode ulang body akan mengubahnya (misalnya, karakter non-Inggris dan emoji tiba dalam bentuk escape \uXXXX), dan tanda tangan tidak akan cocok.
// Express: keep the raw body for this route
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.WBIZTOOL_WEBHOOK_SECRET;
app.post("/wbiztool/webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const received = req.get("X-Wbiztool-Signature") || "";
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.status(401).send("Invalid signature");
const data = JSON.parse(req.body.toString("utf8"));
if (data.event === "message_received") {
console.log(`New message from ${data.contact.phone}: ${data.message.content}`);
}
res.sendStatus(200); // reply quickly; do slow work in the background
});
app.listen(3000);# Flask
import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["WBIZTOOL_WEBHOOK_SECRET"].encode()
@app.post("/wbiztool/webhook")
def wbiztool_webhook():
raw_body = request.get_data() # raw bytes, before parsing
expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
received = request.headers.get("X-Wbiztool-Signature", "")
if not hmac.compare_digest(expected, received):
abort(401)
data = json.loads(raw_body)
if data["event"] == "message_received":
print(f"New message from {data['contact']['phone']}: {data['message']['content']}")
return "", 200<?php
$secret = getenv('WBIZTOOL_WEBHOOK_SECRET');
$rawBody = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
$received = $_SERVER['HTTP_X_WBIZTOOL_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($rawBody, true);
if ($data['event'] === 'message_received') {
error_log('New message from ' . $data['contact']['phone'] . ': ' . $data['message']['content']);
}
http_response_code(200);Tanda tangan tidak mencakup header timestamp, sehingga tidak melindungi dari permintaan yang diputar ulang (replay). Jika hal itu penting, simpan setiap message.id yang sudah Anda proses dan abaikan pengulangannya.
Pemecahan masalah#
| Pesan atau masalah | Yang harus dilakukan |
|---|---|
| Unibox Add-on Required saat Anda mengklik Add New Listener | Workspace Anda tidak memiliki add-on Unibox. Beli di bagian Available Add-ons pada Billing & Plans di dalam dashboard. |
You have reached your unibox numbers limit | Hapus listener yang tidak lagi Anda perlukan, atau tambah jumlah add-on. |
| No available WhatsApp numbers | Hubungkan nomor lain, atau nomor tersebut sudah menjadi listener. |
This WhatsApp number is already a listener | Ubah kartu yang sudah ada. |
Invalid WhatsApp client | Nomor terputus. Hubungkan ulang di WhatsApp settings lalu muat ulang halaman. |
Error yang menyebutkan value too long saat menyimpan | URL webhook lebih dari 100 karakter. Gunakan URL yang lebih pendek. |
| Tidak ada webhook yang tiba | Pastikan listener berstatus Active, nomor terhubung, jenis event dicentang, dan URL Anda berupa https publik dengan sertifikat yang valid. Pesan baru dikirim setelah sinkronisasi berikutnya, beberapa menit kemudian. |
| Beberapa webhook hilang | Server Anda mengembalikan status 4xx, atau terus gagal pada ketiga percobaan ulang. Periksa log server Anda pada waktu pesan yang hilang. |
| URL webhook hilang dari listener | Server Anda menjawab 410 Gone, sehingga Wbiztool berhenti mengirim ke sana. Tambahkan URL lagi di Edit Listener. |
| Tanda tangan tidak cocok | Gunakan body mentah, bukan JSON yang di-encode ulang, dan secret yang berlaku saat ini. Membuat ulang secret akan menggantikannya. Selama Zap Zapier menggunakan nomor tersebut, permintaan dikirim ke Zapier dengan secret Zapier. |
| Last Activity: menampilkan Never | Nomor belum diperiksa. Nomor harus terhubung dan tidak sibuk mengirim pesan. |
