Channel API — Dokumentasi Integrasi Bot WhatsApp

API ini untuk menghubungkan bot WhatsApp eksternal ke data toko (produk, order, delivery) dan mencatat status koneksi WhatsApp Channel. Kazean tidak menjalankan engine WhatsApp — bot pihak ketiga yang mengelola koneksi WA, lalu melaporkan statusnya ke sini.

1. Autentikasi

Setiap request (kecuali webhook) memakai scoped API key yang dibuat di halaman /developer:

Authorization: Bearer kz_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Key ini bukan key admin — hanya boleh mengakses scope yang dipilih saat dibuat, dan hanya melihat data milik akun yang membuatnya. Simpan di sisi bot (server), jangan pernah ditempel di kode frontend/browser.

Scope yang tersedia

ScopeKegunaan
products.readBaca katalog produk aktif
orders.readBaca order milik akun ini
orders.updateSet status pengiriman sisi bot (bukan status pembayaran)
delivery.readBaca detail hasil pembelian (delivery) sebuah order
channel.readBaca daftar & status channel
channel.eventKirim event channel (connected/disconnected/dst)
stock.readCek stok produk & riwayat perubahannya
stock.writeRestock produk (hanya produk yang diizinkan admin untuk key ini)
panel.readBaca status & sisa masa aktif panel milik akun ini
panel.triggerPerpanjang panel & memesan panel baru (provisioning) milik akun ini

Key tidak pernah diberi izin admin. Kalau butuh scope tambahan, buat key baru dengan scope yang sesuai — jangan minta semua scope kalau bot cuma butuh sebagian.

2. Channel

Channel didaftarkan lewat halaman /developer (bukan lewat API) — hasilnya channel_id dan webhookSecret (tampil sekali, simpan di sisi bot).

GET /api/channel

Scope: channel.read. Daftar semua channel milik akun ini.

GET /api/channel/:channel_id

Scope: channel.read. Detail satu channel.

GET /api/channel/:channel_id/status

Scope: channel.read. Cek cepat status channel saja.

{ "success": true, "data": { "channel_id": "...", "status": "ACTIVE" } }

Status channel

StatusArti
ACTIVETersambung, boleh dipakai
INACTIVETerputus sementara (mis. setelah channel.disconnected)
DISABLEDDinonaktifkan manual oleh pemilik — request event ditolak
REVOKEDDicabut permanen oleh admin — request event ditolak

3. Mengirim Event Channel

Ada dua jalur yang setara secara fungsi — pakai salah satu sesuai kebutuhan bot:

A. POST /api/channel/:channel_id/event (pakai API key)

Scope: channel.event.

{
  "event_id": "evt_unik_123",
  "event_type": "channel.connected",
  "payload": { "any": "data" }
}

B. POST /api/webhook/channel (pakai signature, bukan API key)

Body sama seperti di atas ditambah channel_id, tapi autentikasi pakai header signature HMAC-SHA256 dari webhookSecret channel:

X-Kazean-Signature: <hex HMAC-SHA256 dari raw body pakai webhookSecret>

Body:
{
  "channel_id": "ch_xxx",
  "event_id": "evt_unik_123",
  "event_type": "channel.connected",
  "payload": { "any": "data" }
}

Contoh lengkap: bangun body, buat signature, lalu kirim webhook-nya (Node.js 18+, pakai fetch bawaan — tinggal salin & sesuaikan baseUrl/webhookSecret):

const crypto = require("crypto");

const baseUrl = "https://kazean.biz.id";
const channelId = "ch_xxx";           // channel_id dari /developer
const webhookSecret = "..."; // webhookSecret dari /developer, simpan aman di server bot

async function sendChannelEvent(eventType, payload) {
  const body = JSON.stringify({
    channel_id: channelId,
    event_id: crypto.randomUUID(), // wajib unik per event, dipakai buat idempotency
    event_type: eventType,
    payload,
  });
  const signature = crypto.createHmac("sha256", webhookSecret).update(body).digest("hex");

  const res = await fetch(`${baseUrl}/api/webhook/channel`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Kazean-Signature": signature },
    body,
  });
  if (!res.ok) throw new Error(`Webhook gagal: ${res.status} ${await res.text()}`);
  return res.json();
}

module.exports = { sendChannelEvent };

// Contoh pakai:
// sendChannelEvent("channel.connected", { number: "6281234567890" });
import crypto from "crypto";

const baseUrl = "https://kazean.biz.id";
const channelId = "ch_xxx";           // channel_id dari /developer
const webhookSecret = "..."; // webhookSecret dari /developer, simpan aman di server bot

export async function sendChannelEvent(eventType, payload) {
  const body = JSON.stringify({
    channel_id: channelId,
    event_id: crypto.randomUUID(), // wajib unik per event, dipakai buat idempotency
    event_type: eventType,
    payload,
  });
  const signature = crypto.createHmac("sha256", webhookSecret).update(body).digest("hex");

  const res = await fetch(`${baseUrl}/api/webhook/channel`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Kazean-Signature": signature },
    body,
  });
  if (!res.ok) throw new Error(`Webhook gagal: ${res.status} ${await res.text()}`);
  return res.json();
}

// Contoh pakai:
// await sendChannelEvent("channel.connected", { number: "6281234567890" });

Pilih format sesuai package.json bot kamu — kalau ada "type": "module" (atau file .mjs) pakai tab ESM, kalau tidak (default Node.js / .cjs) pakai tab CommonJS. Jangan dicampur (import + module.exports di file yang sama akan error).

Tipe event yang dikenali

event_typeEfek di Kazean
channel.connectedChannel → ACTIVE
channel.disconnectedChannel → INACTIVE
channel.messageDicatat saja (tidak mengubah status)
channel.updatedDicatat saja
channel.errorDicatat saja

Idempotency: setiap event wajib punya event_id unik. Event dengan event_id yang sama pada channel yang sama hanya diproses sekali — kirim ulang saat retry itu aman, tidak akan dobel.

4. Data Toko

GET /api/integrations/products

Scope: products.read. Katalog produk aktif (tanpa data stok rahasia/link download).

GET /api/integrations/orders

Scope: orders.read. Semua order milik akun pemilik key ini.

GET /api/integrations/delivery/:orderId

Scope: delivery.read. Detail hasil pembelian (akun/lisensi/link/dll) sebuah order — order harus milik akun ini.

PUT /api/integrations/orders/:id/status

Scope: orders.update. Terbatas — hanya menandai status pengiriman pesan di sisi bot, bukan status pembayaran/order:

{ "botDeliveryStatus": "sent", "note": "opsional" }

Nilai botDeliveryStatus: sent, failed, atau acknowledged. Status pembayaran/order inti (pending/paid/completed) selalu berasal dari webhook payment gateway di backend, tidak bisa diubah lewat API ini — ini untuk mencegah manipulasi stok/order.

5. Stock Engine

Endpoint ini untuk bot yang perlu tahu/mengelola stok produk tertentu, mis. bot reseller yang otomatis upload stok baru begitu supplier mengirim kode.

GET /api/integrations/stock/:productId

Scope: stock.read.

{ "success": true, "data": {
  "productId": "...", "name": "...", "active": true,
  "unlimitedStock": false, "hasStockPool": true,
  "stock": 12, "inStock": true
} }

GET /api/integrations/stock/:productId/history

Scope: stock.read. Query page, limit (default 20, maks 100). Riwayat sale, restock, manual_adjustment, restore untuk produk ini.

POST /api/integrations/stock/:productId/restock

Scope: stock.write. Terbatas — hanya berhasil kalau admin sudah mengisi meta.apiRestockCustomerId produk ini dengan id akun pemilik key (diset admin lewat form edit produk di /manage), supaya sembarang pembeli tidak bisa menyuntik stok ke katalog toko.

{ "items": "kode1\nkode2\nkode3" }
// atau
{ "items": ["kode1", "kode2", "kode3"] }

6. Panel / Pterodactyl Provisioning

Endpoint ini cuma menyentuh panel milik akun pemilik key sendiri — tidak bisa dipakai untuk memesankan atau melihat panel customer lain.

GET /api/integrations/panel

Scope: panel.read. Semua panel (order kategori panel) milik akun ini.

GET /api/integrations/panel/:orderId

Scope: panel.read. Detail satu panel — panelStatus (active/suspended/missing), expiresAt, renewPrice, renewDays.

POST /api/integrations/panel/:orderId/renew

Scope: panel.trigger. Perpanjang panel, dibayar dari saldo akun pemilik key — sama seperti tombol Perpanjang di halaman Riwayat. Gagal dengan pesan jelas kalau saldo tidak cukup, panel ditangguhkan admin, atau server sudah tidak ada di panel.

POST /api/integrations/panel/order

Scope: panel.trigger. Memesan produk kategori panel baru (memicu provisioning otomatis ke Pterodactyl), dibayar dari saldo akun pemilik key:

{ "productId": "...", "panelUsername": "opsional", "panelPassword": "opsional" }

Provisioning berjalan async lewat antrian — cek statusnya lewat GET /api/integrations/panel/:orderId atau GET /api/integrations/orders.

7. Rate Limit

Endpoint ber-API-key: 60 request/menit per key. Webhook: 120 request/menit per channel_id. Response 429 kalau terlampaui.

8. Error Umum

StatusArti
401API key tidak ada/salah, atau signature webhook tidak valid
403Key/channel tidak aktif, scope tidak cukup, atau channel bukan milik key ini
404Channel/order/produk tidak ditemukan
409Konflik (mis. stok baru saja habis, username panel sudah dipakai)
429Rate limit terlampaui
502Gagal menghubungi Pterodactyl saat provisioning/renew panel