D
API Reference

Webhook Reference

Webhook keluar ke backend Dazo di endpoint callback_message. Dua payload — report false untuk pesan masuk, report true untuk status ACK. Backend balas result false berarti socket event tidak di-emit.

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:

ts
// 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

  1. Jika backend membalas { "result": false }, engine mencatat warning dan tidak meneruskan event ke Socket.IO:
ts
// 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
}
  1. Emit socket terjadi setelah webhook sukses, di dalam sendWebhook() — bukan di pemanggilnya. Ini penting saat menelusuri alur.

  2. 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:

jsonc
{
  "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

typeField terisi
texttext = isi pesan
extendedtexttext = isi pesan (link/reply)
imagetext = URL media, caption = caption
videotext = URL media, caption = caption
documenttext = URL media, caption = caption
audiotext = URL media
locationtext = URL thumbnail, latitude/longitude/locationName/locationAddress terisi

Catatan perilaku

AturanDetail
Skip status@broadcastPesan dari status broadcast dilewati
Skip @newsletterPesan dari newsletter dilewati
Skip protocolMessageTipe protocolMessage dilewati
Skip senderKeyDistributionMessageTipe ini dilewati
@lid normalisasiJID @lid diganti remoteJidAlt bila tersedia
Media downloadImage/video/document/audio/sticker diunduh ke public/files/<instance>_<uuid>.<ext>
Lokasi thumbnailDisimpan sebagai <instance>_loc_<uuid>.jpeg
Foto profil fallbackGagal ambil → ui-avatars.com dengan nama chat
chat_historiesSetiap 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:

json
{
  "result": true,
  "report": true,
  "number": "628123456789@s.whatsapp.net",
  "temp_id": "temp-abc",
  "id": "3EB0...",
  "status": "delivered",
  "instanceID": "0c1a968c-..."
}

Status mapping

Baileys statusString webhook
1pending
2sent
3delivered
4viewed
5played

Dedupe

Ada dedupe in-memory berbasis messageId + status agar tidak dobel:

ts
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:

ts
const inbox = await ChatInboxModel.findOne({
  pesan_id: update.key.id,
  instance: instanceId,
});
// temp_id: inbox?.temp_id ?? null

Jika tidak ada di chat_inbox, temp_id = null.

Response dari backend

Backend Dazo diharapkan membalas dengan:

json
{ "result": true, "data": { ... } }
Response backendAksi 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 / timeoutLog error Gagal mengirim webhook: ...

data dari backend → Socket.IO

Response data dari backend dimasukkan ke payload Socket.IO:

ts
// 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

reportEvent 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

http
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

json
{
  "result": true,
  "data": {
    "chat_inbox_id": "inbox-123",
    "chat_history_id": "history-456"
  }
}

Socket emit (setelah webhook sukses)

json
// 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

AreaLokasi
Bentuk payloadsrc/utils/helper.ts — sendWebhook
Field reportDua jenis payload dibedakan oleh field ini
WEBHOOK_URL.env — base URL backend
Endpoint path/callback_message (hardcoded di helper.ts:31)

Langkah berikutnya