Referensi

Webhooks

Panduan menerima event outbound Dalekta: setup endpoint, header signature, HMAC verification, payload envelope, retry, dan idempotency.

01

Ringkasan

Webhook Dalekta mengirim event workspace ke endpoint HTTPS milik user saat lead atau chat berubah. Gunakan webhook ketika sistem eksternal perlu menerima push event tanpa polling Agent API.

Ketersediaan paketWebhook hanya tersedia untuk Signal Pro dan Signal Max. Paket Signal Lite tidak bisa membuat endpoint webhook atau menerima delivery webhook.
Outbound pushDalekta mengirim POST JSON ke URL endpoint yang dibuat dari menu API & Webhook.
Signed requestSetiap delivery ditandatangani dengan HMAC SHA-256 memakai signing secret endpoint.
At-least-onceDelivery bisa terkirim lebih dari sekali saat retry. Receiver wajib idempotent.
Retry otomatisTimeout atau response non-2xx akan dicoba ulang bertahap sampai percobaan habis.
02

Setup endpoint

  1. Buka dashboard Dalekta.
  2. Masuk ke menu API & Webhook.
  3. Klik Tambah webhook.
  4. Isi nama endpoint dan URL HTTPS receiver.
  5. Pilih event yang ingin dikirim.
  6. Copy signing secret whsec_... saat endpoint berhasil dibuat. Secret hanya tampil satu kali.
  7. Klik tombol test di daftar webhook untuk memastikan receiver membalas 2xx.
  8. Buka log delivery dari row webhook untuk melihat response, error, retry schedule, dan menjalankan retry manual untuk delivery FAILED.
HTTPS wajibURL webhook harus memakai HTTPS dan dapat diakses secara publik oleh Dalekta. URL lokal atau alamat jaringan privat tidak didukung.
03

Contoh pemakaian webhook

Webhook dipakai saat sistem luar perlu langsung menerima notifikasi dari Dalekta. Bedanya dengan Agent API: Agent API cocok untuk pertanyaan seperti tolong cek berapa leads hari ini, sedangkan webhook cocok untuk trigger otomatis seperti kalau ada lead baru, kirim ke CRM.

Kebutuhan userEvent webhookYang dilakukan receiver
kalau ada lead baru, kirim ke CRMlead.createdReceiver membuat atau memperbarui contact/deal di CRM memakai data.lead dan attribution.
kalau status lead berubah, update pipeline CRMlead.status_changedReceiver mencari lead berdasarkan id atau kode, lalu memindahkan stage pipeline sesuai status baru.
kalau closing, kirim notifikasi dan simpan revenuelead.closedReceiver mengirim notifikasi ke sistem kerja tim dan menyimpan closingValue jika ada.
kalau ada chat baru, trigger AI follow upchat.message.createdReceiver membuat job follow up. Jika perlu isi chat lengkap, agent membaca detail lewat Agent API messages endpoint dengan allowlist session.
kalau data lead berubah, sync ke spreadsheetlead.updatedReceiver melakukan upsert baris spreadsheet agar data lead di sistem luar tetap mengikuti Dalekta.
text
Contoh flow: lead baru masuk
1. Customer klik WhatsApp dari landing page.
2. Lead tercatat di Dalekta.
3. Dalekta membuat event `lead.created`.
4. Dalekta POST payload ke endpoint webhook yang subscribe `lead.created`.
5. Receiver verify `X-Dalekta-Signature`.
6. Receiver simpan `payload.id` untuk idempotency.
7. Receiver upsert contact/deal di CRM.
8. Receiver balas HTTP 200 agar delivery dianggap sukses.
Webhook bukan tempat bertanyaJika user bertanya jumlah lead hari ini, agent sebaiknya pakai Agent API. Webhook hanya mengirim event saat sesuatu terjadi, bukan menjawab query historis.
04

Headers

Dalekta mengirim payload sebagai JSON dan menambahkan header berikut pada setiap delivery.

HeaderIsi
Content-Typeapplication/json
X-Dalekta-Signaturet=<unix_timestamp>,v1=<hex_hmac_sha256>
X-Dalekta-EventNama event, misalnya lead.status_changed.
X-Dalekta-Delivery-IdID delivery unik dari log Dalekta.
User-AgentDalekta-Webhook/1.0
text
Content-Type: application/json
X-Dalekta-Signature: t=1785074400,v1=<hex_hmac_sha256>
X-Dalekta-Event: lead.status_changed
X-Dalekta-Delivery-Id: whd_xxx
User-Agent: Dalekta-Webhook/1.0
05

Payload envelope

Semua event memakai envelope yang sama. Field id adalah event id untuk idempotency lintas retry, sedangkan X-Dalekta-Delivery-Id adalah id delivery attempt/log.

json
{
  "id": "evt_xxx",
  "type": "lead.status_changed",
  "createdAt": "2026-07-26T14:00:00.000Z",
  "workspace": {
    "id": "workspace_id",
    "name": "Workspace name"
  },
  "data": {
    "lead": {
      "id": "lead_id",
      "status": "CLOSING"
    }
  }
}
FieldMakna
idEvent id stabil untuk dedupe. Simpan field ini di receiver.
typeNama event yang juga dikirim di X-Dalekta-Event.
createdAtWaktu event dibuat dalam ISO 8601 UTC.
workspaceWorkspace sumber event.
dataPayload spesifik event.
06

Verify signature

Signature dihitung dari string <timestamp>.<raw_json_body> memakai HMAC SHA-256 dan signing secret endpoint. Receiver harus memakai raw body asli, bukan hasil JSON stringify ulang.

  1. Ambil header X-Dalekta-Signature.
  2. Parse nilai t dan v1.
  3. Tolak request jika timestamp terlalu jauh dari waktu server, misalnya lebih dari 5 menit.
  4. Hitung HMAC SHA-256 dari <t>.<raw_body> dengan signing secret.
  5. Bandingkan hasil hex dengan v1 memakai timing-safe comparison.
js
const crypto = require("crypto");

function verifyDalektaSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    String(signatureHeader || "")
      .split(",")
      .map((part) => part.trim().split("="))
      .filter(([key, value]) => key && value)
  );

  const timestamp = Number(parts.t);
  const signature = parts.v1;
  if (!Number.isFinite(timestamp) || !signature) return false;

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestamp) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex");

  const actualBuffer = Buffer.from(signature, "hex");
  const expectedBuffer = Buffer.from(expected, "hex");
  return actualBuffer.length === expectedBuffer.length && crypto.timingSafeEqual(actualBuffer, expectedBuffer);
}
07

Contoh receiver

Contoh Express di bawah memakai raw body supaya signature bisa diverifikasi dengan benar sebelum payload diproses.

js
const express = require("express");
const app = express();

app.post("/dalekta/webhook", express.raw({ type: "application/json" }), async (req, res) => {
  const rawBody = req.body.toString("utf8");
  const secret = process.env.DALEKTA_WEBHOOK_SECRET;

  if (!verifyDalektaSignature(rawBody, req.header("X-Dalekta-Signature"), secret)) {
    return res.status(401).json({ error: "invalid_signature" });
  }

  const event = JSON.parse(rawBody);
  const deliveryId = req.header("X-Dalekta-Delivery-Id");

  // Simpan event.id atau deliveryId sebagai idempotency key sebelum menjalankan side effect.
  if (await alreadyProcessed(event.id)) {
    return res.status(200).json({ ok: true, duplicate: true });
  }

  await markProcessed(event.id, deliveryId);
  await handleDalektaEvent(event);

  return res.status(200).json({ ok: true });
});
Balas cepatReceiver sebaiknya membalas 2xx secepat mungkin. Pekerjaan berat dapat diproses secara asynchronous di sistem receiver.
08

Retry dan idempotency

KondisiStatus delivery
Response HTTP 2xxSUCCESS
Timeout 5 detikRETRYING atau FAILED jika percobaan habis
Response non-2xxRETRYING atau FAILED jika percobaan habis
Endpoint nonaktifSKIPPED

Retry berjalan bertahap setelah delivery gagal. Karena modelnya at-least-once, receiver harus aman menerima event yang sama lebih dari sekali.

Delivery log bisa dibuka dari daftar webhook di menu API & Webhook. Log menampilkan event id, status, jumlah attempt, HTTP status, error/response body ringkas, dan jadwal retry berikutnya.

Delivery yang sudah FAILED bisa dicoba ulang manual dari log selama endpoint masih aktif. Manual retry memakai signature baru dan tetap mempertahankan konteks delivery untuk audit.

Endpoint gagal beruntunDalekta dapat menonaktifkan endpoint setelah beberapa kegagalan beruntun. Setelah receiver diperbaiki, aktifkan endpoint kembali dari edit webhook lalu retry delivery gagal yang masih perlu diproses.
  • Simpan payload.id sebagai idempotency key utama.
  • Simpan juga X-Dalekta-Delivery-Id untuk audit delivery attempt.
  • Pastikan side effect tidak berjalan dua kali untuk payload.id yang sama.
  • Tetap balas 200 untuk duplicate yang sudah pernah diproses.
09

Event awal

EventTrigger
lead.createdLead baru dibuat dari tracking, klik WhatsApp, atau flow WhatsApp.
lead.updatedData lead berubah tanpa harus menunggu perubahan status.
lead.status_changedStatus lead berubah, misalnya dari Masuk WA ke MQL atau Prospek.
lead.closedLead pertama kali masuk closing atau punya nominal closing.
chat.message.createdPesan WhatsApp baru tersimpan. Payload chat tetap ringan.
json
{
  "id": "evt_lead_status_changed_xxx",
  "type": "lead.status_changed",
  "createdAt": "2026-07-26T14:00:00.000Z",
  "workspace": {
    "id": "workspace_id",
    "name": "Workspace name"
  },
  "data": {
    "lead": {
      "id": "lead_id",
      "status": "CLOSING",
      "previousStatus": "BOOKING",
      "currency": "IDR",
      "closingValue": 2500000,
      "closingDate": "2026-07-26T14:00:00.000Z"
    },
    "attribution": {
      "sourcePlatform": "meta",
      "utmCampaign": "campaign_name",
      "adId": "ad_id"
    }
  }
}
json
{
  "id": "evt_chat_message_created_xxx",
  "type": "chat.message.created",
  "createdAt": "2026-07-26T14:00:00.000Z",
  "workspace": {
    "id": "workspace_id",
    "name": "Workspace name"
  },
  "data": {
    "message": {
      "id": "wa_message_id",
      "leadId": "lead_id",
      "waSessionId": "wa_session_id",
      "direction": "inbound",
      "type": "text",
      "timestamp": "2026-07-26T14:00:00.000Z"
    },
    "customer": {
      "phoneNumber": "628xxxxxxxxxx",
      "name": "Customer name"
    },
    "content": {
      "availableViaAgentApi": true
    }
  }
}
Chat contentchat.message.created membawa payload ringan. Untuk membaca isi chat lengkap, gunakan Agent API messages endpoint dengan scope agent.chats.read dan WhatsApp session allowlist.
Dalekta | Webhooks | Dokumentasi Dalekta