Engine mengirim POST ke {WEBHOOK_URL}/callback_message untuk dua kejadian, dibedakan oleh field report.
Mode dummy
Jika WEBHOOK_URL mengandung string webhook-test, sendWebhook() langsung return tanpa mengirim apa pun:
// src/utils/helper.ts:27-29
if (WEBHOOK_URL.includes('webhook-test')) {
return; // mode dummy development
}Mode ini untuk development tanpa backend Dazo aktif. Jangan pakai di produksi — backend tidak akan menerima pesan masuk sama sekali.
Aturan penting
- Jika backend membalas
{ "result": false }, engine mencatat warning dan tidak meneruskan event ke Socket.IO:
// src/utils/helper.ts:35-38
if (res.data && res.data.result === false) {
console.warn(`[API] Backend menolak pesan: ${res.data.message}`);
return; // tidak emit socket
}-
Emit socket terjadi setelah webhook sukses, di dalam
sendWebhook()— bukan di pemanggilnya. Ini penting saat menelusuri alur. -
Bentuk payload webhook adalah kontrak lintas-service — jangan ubah tanpa koordinasi backend.
Payload 1: Pesan masuk (report: false)
Dikirim saat ada pesan masuk dari WhatsApp. Field lengkap:
{
"result": true,
"report": false,
"type": "text", // text | extendedtext | image | video | document | audio | location
"id": "3EB0...", // message id dari Baileys
"fromMe": false,
"number": "628123456789@s.whatsapp.net",
"name": "Nama Chat / Nama Grup",
"text": "isi pesan atau URL media",
"caption": "",
"quoteMsg": "{...}", // contextInfo dalam bentuk JSON string
"url": "http://localhost:5002/files/<instance>_<uuid>.jpeg",
"latitude": "undefined",
"longitude": "undefined",
"locationName": "",
"locationAddress": "",
"profilePic": "https://...",
"participantNumber": "628...@s.whatsapp.net",
"participantName": "Push Name",
"timestamp": 1700000000,
"status": "pending",
"instanceID": "0c1a968c-...",
"raw_message": "{...}"
}Field per tipe
type | Field terisi |
|---|---|
text | text = isi pesan |
extendedtext | text = isi pesan (link/reply) |
image | text = URL media, caption = caption |
video | text = URL media, caption = caption |
document | text = URL media, caption = caption |
audio | text = URL media |
location | text = URL thumbnail, latitude/longitude/locationName/locationAddress terisi |
Catatan perilaku
| Aturan | Detail |
|---|---|
Skip status@broadcast | Pesan dari status broadcast dilewati |
Skip @newsletter | Pesan dari newsletter dilewati |
Skip protocolMessage | Tipe protocolMessage dilewati |
Skip senderKeyDistributionMessage | Tipe ini dilewati |
@lid normalisasi | JID @lid diganti remoteJidAlt bila tersedia |
| Media download | Image/video/document/audio/sticker diunduh ke public/files/<instance>_<uuid>.<ext> |
| Lokasi thumbnail | Disimpan sebagai <instance>_loc_<uuid>.jpeg |
| Foto profil fallback | Gagal ambil → ui-avatars.com dengan nama chat |
chat_histories | Setiap pesan masuk memicu pengecekan/pembuatan chat_histories |
Payload 2: Update status pesan (report: true)
Dikirim saat status ACK pesan berubah. Dipantau via messages.update event Baileys:
{
"result": true,
"report": true,
"number": "628123456789@s.whatsapp.net",
"temp_id": "temp-abc",
"id": "3EB0...",
"status": "delivered",
"instanceID": "0c1a968c-..."
}Status mapping
| Baileys status | String webhook |
|---|---|
1 | pending |
2 | sent |
3 | delivered |
4 | viewed |
5 | played |
Dedupe
Ada dedupe in-memory berbasis messageId + status agar tidak dobel:
const dedupeKey = `${update.key.id}-${update.update.status}`;
if (processedUpdates.has(dedupeKey)) continue;
processedUpdates.add(dedupeKey);temp_id lookup
temp_id diambil dari koleksi chat_inbox berdasarkan pesan_id + instance:
const inbox = await ChatInboxModel.findOne({
pesan_id: update.key.id,
instance: instanceId,
});
// temp_id: inbox?.temp_id ?? nullJika tidak ada di chat_inbox, temp_id = null.
Response dari backend
Backend Dazo diharapkan membalas dengan:
{ "result": true, "data": { ... } }| Response backend | Aksi engine |
|---|---|
{ "result": true, "data": { ... } } | Lanjut emit Socket.IO event (getmessage / message_status_update) dengan data dari backend |
{ "result": false, "message": "..." } | Log warning [API] Backend menolak pesan: ..., tidak emit socket |
| Error HTTP / timeout | Log error Gagal mengirim webhook: ... |
data dari backend → Socket.IO
Response data dari backend dimasukkan ke payload Socket.IO:
// utils/helper.ts:42
const responseData = res.data.data;
// Untuk report: false (pesan masuk)
SocketService.getInstance().emitTo(instanceId, 'getmessage', {
id: numenc,
type: 'getmessage',
id_device: userNumber,
instance: instanceId,
text: messageId,
data: responseData, // ← data dari backend
});Socket event setelah webhook sukses
report | Event emit |
|---|---|
false (pesan masuk) | getmessage + getmessage_bubble |
true (status ACK) | message_status_update |
Field id pada getmessage dan message_status_update adalah hasil enkripsi AES-256-CBC dari instanceId + nomor. Lihat Security.
Contoh: pesan masuk text
Request engine → backend
POST {WEBHOOK_URL}/callback_message
Content-Type: application/json
{
"result": true,
"report": false,
"type": "text",
"id": "3EB0XYZ123",
"fromMe": false,
"number": "628123456789@s.whatsapp.net",
"name": "Budi",
"text": "Halo, saya mau tanya produk",
"caption": "",
"quoteMsg": "",
"url": "",
"latitude": "undefined",
"longitude": "undefined",
"locationName": "",
"locationAddress": "",
"profilePic": "https://pps.whatsapp.net/...",
"participantNumber": "628123456789@s.whatsapp.net",
"participantName": "Budi",
"timestamp": 1700000000,
"status": "pending",
"instanceID": "0c1a968c-7918-4c29-a524-0ad7c445ee7f",
"raw_message": "{\"key\":{...},\"message\":{...},\"messageTimestamp\":1700000000}"
}Response backend → engine
{
"result": true,
"data": {
"chat_inbox_id": "inbox-123",
"chat_history_id": "history-456"
}
}Socket emit (setelah webhook sukses)
// getmessage + getmessage_bubble (identik)
{
"id": "encrypted_id_xyz",
"type": "getmessage",
"id_device": "628123456789",
"instance": "0c1a968c-...",
"text": "3EB0XYZ123",
"data": {
"chat_inbox_id": "inbox-123",
"chat_history_id": "history-456"
}
}Yang TIDAK boleh diubah tanpa koordinasi
| Area | Lokasi |
|---|---|
| Bentuk payload | src/utils/helper.ts — sendWebhook |
Field report | Dua jenis payload dibedakan oleh field ini |
WEBHOOK_URL | .env — base URL backend |
| Endpoint path | /callback_message (hardcoded di helper.ts:31) |
Langkah berikutnya
- Detail event Socket.IO? Baca Realtime Events.
- Cara pesan masuk diproses? Baca Message Flow.
- Kembali ke API Reference.