Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.csku.ai/llms.txt

Use this file to discover all available pages before exploring further.

Pengenalan

Contoh implementasi webhook lengkap dalam berbagai bahasa pemrograman dan framework.
Pilih contoh yang sesuai dengan technology stack Anda.

Daftar Isi

  1. Node.js/Express
  2. Python/Flask
  3. PHP
  4. Go
  5. Ruby

Node.js/Express

Implementasi Lengkap

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

// Konfigurasi
const SECRET_KEY = process.env.WEBHOOK_SECRET_KEY || 'your_secret_key';
const PORT = process.env.PORT || 3000;

// Middleware
app.use(bodyParser.json());

// Simpan event yang sudah diproses (di produksi, gunakan database)
const processedEvents = new Set();

// Bersihkan event yang diproses secara berkala (simpan 10,000 terakhir)
setInterval(() => {
  if (processedEvents.size > 10000) {
    const events = Array.from(processedEvents);
    processedEvents.clear();
    events.slice(5000).forEach(id => processedEvents.add(id));
  }
}, 3600000); // Setiap jam

// Endpoint webhook
app.post('/webhook', async (req, res) => {
  try {
    // Validasi autentikasi
    const authHeader = req.headers.authorization;
    if (authHeader !== SECRET_KEY) {
      console.error('Secret webhook tidak valid');
      return res.status(401).json({ error: 'Unauthorized' });
    }
    
    const { event, timestamp, conversation_id, message } = req.body;
    
    // Deduplikasi event
    const eventId = `${event}_${message.id}`;
    if (processedEvents.has(eventId)) {
      console.log('Event duplikat, melewati');
      return res.status(200).send('OK');
    }
    
    // Proses berdasarkan jenis event
    switch (event) {
      case 'channel.message_in':
        await handleIncomingMessage(message, conversation_id);
        break;
      case 'agent.message_out':
        await handleAgentMessage(message, conversation_id);
        break;
      case 'ai.message_generated':
        await handleAIMessage(message, conversation_id);
        break;
      case 'device.connected':
        await handleDeviceConnected(req.body.device);
        break;
      case 'device.disconnected':
        await handleDeviceDisconnected(req.body.device);
        break;
      default:
        console.log('Jenis event tidak dikenal:', event);
    }
    
    // Tandai sebagai sudah diproses
    processedEvents.add(eventId);
    
    // Respons segera
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error webhook:', error);
    res.status(500).send('Error');
  }
});

// Handler event
async function handleIncomingMessage(message, conversationId) {
  console.log('Pesan baru dari:', message.sender_name);
  console.log('Konten:', message.content.text);
  console.log('Channel:', message.channel.name);
  
  // Logika bisnis Anda di sini
  // Contoh: Simpan ke database
  // await db.messages.create({ ...message, conversationId });
}

async function handleAgentMessage(message, conversationId) {
  console.log('Agen', message.agent.name, 'mengirim pesan');
  
  // Logika bisnis Anda di sini
}

async function handleAIMessage(message, conversationId) {
  console.log('AI', message.ai.name, 'menghasilkan respons');
  
  // Logika bisnis Anda di sini
}

async function handleDeviceConnected(device) {
  console.log('Perangkat', device.name, 'sekarang terhubung');
  
  // Logika bisnis Anda di sini
}

async function handleDeviceDisconnected(device) {
  console.log('Perangkat', device.name, 'sekarang terputus');
  
  // Logika bisnis Anda di sini
}

// Health check
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy', processedEvents: processedEvents.size });
});

// Jalankan server
app.listen(PORT, () => {
  console.log(`Server webhook berjalan di port ${PORT}`);
});

Python/Flask

Implementasi Lengkap

from flask import Flask, request, jsonify
from collections import deque
import os
from datetime import datetime
import threading
import time

# Konfigurasi
app = Flask(__name__)
SECRET_KEY = os.getenv('WEBHOOK_SECRET_KEY', 'your_secret_key')
PORT = os.getenv('PORT', 3000)

# Simpan event yang sudah diproses (di produksi, gunakan database)
processed_events = deque(maxlen=10000)

def cleanup_processed_events():
    """Pembersihan berkala (tidak diperlukan dengan deque maxlen)"""
    pass

# Jalankan thread pembersihan
cleanup_thread = threading.Thread(target=cleanup_processed_events, daemon=True)
cleanup_thread.start()

@app.route('/webhook', methods=['POST'])
def webhook():
    try:
        # Validasi autentikasi
        auth_header = request.headers.get('Authorization')
        if auth_header != SECRET_KEY:
            print('Secret webhook tidak valid')
            return jsonify({'error': 'Unauthorized'}), 401
        
        data = request.get_json()
        event = data.get('event')
        timestamp = data.get('timestamp')
        conversation_id = data.get('conversation_id')
        message = data.get('message')
        
        # Deduplikasi event
        event_id = f"{event}_{message['id']}"
        if event_id in processed_events:
            print(f'Event duplikat: {event_id}')
            return 'OK', 200
        
        # Proses berdasarkan jenis event
        if event == 'channel.message_in':
            handle_incoming_message(message, conversation_id)
        elif event == 'agent.message_out':
            handle_agent_message(message, conversation_id)
        elif event == 'ai.message_generated':
            handle_ai_message(message, conversation_id)
        elif event == 'device.connected':
            handle_device_connected(data['device'])
        elif event == 'device.disconnected':
            handle_device_disconnected(data['device'])
        else:
            print(f'Jenis event tidak dikenal: {event}')
        
        # Tandai sebagai sudah diproses
        processed_events.append(event_id)
        
        return 'OK', 200
    except Exception as e:
        print(f'Error webhook: {str(e)}')
        return 'Error', 500

def handle_incoming_message(message, conversation_id):
    print(f"Pesan baru dari: {message['sender_name']}")
    print(f"Konten: {message['content']['text']}")
    print(f"Channel: {message['channel']['name']}")
    # Logika bisnis Anda di sini

def handle_agent_message(message, conversation_id):
    print(f"Agen {message['agent']['name']} mengirim pesan")
    # Logika bisnis Anda di sini

def handle_ai_message(message, conversation_id):
    print(f"AI {message['ai']['name']} menghasilkan respons")
    # Logika bisnis Anda di sini

def handle_device_connected(device):
    print(f"Perangkat {device['name']} sekarang terhubung")
    # Logika bisnis Anda di sini

def handle_device_disconnected(device):
    print(f"Perangkat {device['name']} sekarang terputus")
    # Logika bisnis Anda di sini

@app.route('/health', methods=['GET'])
def health():
    return jsonify({
        'status': 'healthy',
        'processed_events': len(processed_events)
    })

if __name__ == '__main__':
    app.run(port=PORT, debug=True)

PHP

Implementasi Lengkap

<?php
$SECRET_KEY = getenv('WEBHOOK_SECRET_KEY') ?: 'your_secret_key';
$processedEvents = [];

function webhookHandler() {
    global $SECRET_KEY, $processedEvents;
    
    // Validasi autentikasi
    $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if ($authHeader !== $SECRET_KEY) {
        error_log('Secret webhook tidak valid');
        http_response_code(401);
        echo json_encode(['error' => 'Unauthorized']);
        return;
    }
    
    // Dapatkan data POST
    $data = json_decode(file_get_contents('php://input'), true);
    
    if (!isset($data['event'], $data['message'])) {
        error_log('Struktur payload tidak valid');
        http_response_code(400);
        echo json_encode(['error' => 'Invalid payload']);
        return;
    }
    
    $event = $data['event'];
    $message = $data['message'];
    $conversationId = $data['conversation_id'] ?? '';
    
    // Deduplikasi event
    $eventId = $event . '_' . ($message['id'] ?? '');
    if (in_array($eventId, $processedEvents)) {
        error_log("Event duplikat: $eventId");
        http_response_code(200);
        echo 'OK';
        return;
    }
    
    // Proses berdasarkan jenis event
    switch ($event) {
        case 'channel.message_in':
            handleIncomingMessage($message, $conversationId);
            break;
        case 'agent.message_out':
            handleAgentMessage($message, $conversationId);
            break;
        case 'ai.message_generated':
            handleAIMessage($message, $conversationId);
            break;
        case 'device.connected':
            handleDeviceConnected($data['device']);
            break;
        case 'device.disconnected':
            handleDeviceDisconnected($data['device']);
            break;
        default:
            error_log("Jenis event tidak dikenal: $event");
    }
    
    // Tandai sebagai sudah diproses
    $processedEvents[] = $eventId;
    
    // Batasi array event yang diproses
    if (count($processedEvents) > 10000) {
        array_shift($processedEvents);
    }
    
    http_response_code(200);
    echo 'OK';
}

function handleIncomingMessage($message, $conversationId) {
    error_log("Pesan baru dari: {$message['sender_name']}");
    error_log("Konten: {$message['content']['text']}");
    error_log("Channel: {$message['channel']['name']}");
    // Logika bisnis Anda di sini
}

function handleAgentMessage($message, $conversationId) {
    error_log("Agen {$message['agent']['name']} mengirim pesan");
    // Logika bisnis Anda di sini
}

function handleAIMessage($message, $conversationId) {
    error_log("AI {$message['ai']['name']} menghasilkan respons");
    // Logika bisnis Anda di sini
}

function handleDeviceConnected($device) {
    error_log("Perangkat {$device['name']} sekarang terhubung");
    // Logika bisnis Anda di sini
}

function handleDeviceDisconnected($device) {
    error_log("Perangkat {$device['name']} sekarang terputus");
    // Logika bisnis Anda di sini
}

// Health check
function healthCheck() {
    http_response_code(200);
    echo json_encode([
        'status' => 'healthy',
        'processed_events' => count($processedEvents)
    ]);
}

// Penanganan route
$requestUri = $_SERVER['REQUEST_URI'] ?? '';

if (strpos($requestUri, '/webhook') !== false) {
    webhookHandler();
} elseif (strpos($requestUri, '/health') !== false) {
    healthCheck();
}

// Jalankan handler
webhookHandler();
?>

Go

Implementasi Lengkap

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "sync"
    "time"
)

// Konfigurasi
const (
    SECRET_KEY = os.Getenv("WEBHOOK_SECRET_KEY")
    PORT      = os.Getenv("PORT")
)

var (
    processedEvents = make(map[string]bool)
    mu            sync.RWMutex
)

// Struktur event
type WebhookPayload struct {
    Event          string      `json:"event"`
    Timestamp      int64       `json:"timestamp"`
    ConversationID string      `json:"conversation_id"`
    Message        Message     `json:"message"`
    Device         Device      `json:"device,omitempty"`
}

type Message struct {
    ID         string       `json:"id"`
    SenderName string       `json:"sender_name"`
    Channel    Channel      `json:"channel"`
    Bisnis     Bisnis      `json:"bisnis"`
    User       User         `json:"user"`
    Content    Content      `json:"content"`
    Agent      *Agent       `json:"agent,omitempty"`
    AI         *AI          `json:"ai,omitempty"`
}

type Channel struct {
    ID     string `json:"id"`
    Name   string `json:"name"`
    Engine string `json:"engine"`
}

type Content struct {
    Type         string       `json:"type"`
    Text         string       `json:"text,omitempty"`
    Attachments  []Attachment  `json:"attachments,omitempty"`
}

// ... definisi struct lainnya

func webhookHandler(w http.ResponseWriter, r *http.Request) {
    // Validasi method
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    // Validasi autentikasi
    authHeader := r.Header.Get("Authorization")
    if authHeader != SECRET_KEY {
        log.Println("Secret webhook tidak valid")
        http.Error(w, "Unauthorized", http.StatusUnauthorized)
        return
    }
    
    // Parse payload
    var payload WebhookPayload
    err := json.NewDecoder(r.Body).Decode(&payload)
    if err != nil {
        log.Printf("Error decoding payload: %v", err)
        http.Error(w, "Invalid payload", http.StatusBadRequest)
        return
    }
    
    // Deduplikasi event
    eventID := fmt.Sprintf("%s_%s", payload.Event, payload.Message.ID)
    mu.RLock()
    _, exists := processedEvents[eventID]
    mu.RUnlock()
    
    if exists {
        log.Printf("Event duplikat: %s", eventID)
        w.WriteHeader(http.StatusOK)
        fmt.Fprintf(w, "OK")
        return
    }
    
    // Proses berdasarkan jenis event
    switch payload.Event {
    case "channel.message_in":
        handleIncomingMessage(payload.Message, payload.ConversationID)
    case "agent.message_out":
        handleAgentMessage(payload.Message, payload.ConversationID)
    case "ai.message_generated":
        handleAIMessage(payload.Message, payload.ConversationID)
    case "device.connected":
        handleDeviceConnected(payload.Device)
    case "device.disconnected":
        handleDeviceDisconnected(payload.Device)
    default:
        log.Printf("Jenis event tidak dikenal: %s", payload.Event)
    }
    
    // Tandai sebagai sudah diproses
    mu.Lock()
    processedEvents[eventID] = true
    mu.Unlock()
    
    // Respons segera
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "OK")
}

func handleIncomingMessage(message Message, conversationID string) {
    log.Printf("Pesan baru dari: %s", message.SenderName)
    log.Printf("Konten: %s", message.Content.Text)
    log.Printf("Channel: %s", message.Channel.Name)
    // Logika bisnis Anda di sini
}

func handleAgentMessage(message Message, conversationID string) {
    log.Printf("Agen %s mengirim pesan", message.Agent.Name)
    // Logika bisnis Anda di sini
}

func handleAIMessage(message Message, conversationID string) {
    log.Printf("AI %s menghasilkan respons", message.AI.Name)
    // Logika bisnis Anda di sini
}

func handleDeviceConnected(device Device) {
    log.Printf("Perangkat %s sekarang terhubung", device.Name)
    // Logika bisnis Anda di sini
}

func handleDeviceDisconnected(device Device) {
    log.Printf("Perangkat %s sekarang terputus", device.Name)
    // Logika bisnis Anda di sini
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "status":           "healthy",
        "processed_events": len(processedEvents),
    })
}

func main() {
    // Bersihkan event yang diproses secara berkala
    go func() {
        for {
            time.Sleep(time.Hour)
            mu.Lock()
            if len(processedEvents) > 10000 {
                // Hapus 5000 event tertua
                count := 0
                for key := range processedEvents {
                    if count >= 5000 {
                        delete(processedEvents, key)
                    }
                    count++
                }
            }
            mu.Unlock()
        }
    }()
    
    // Setup route
    http.HandleFunc("/webhook", webhookHandler)
    http.HandleFunc("/health", healthHandler)
    
    // Jalankan server
    port := PORT
    if port == "" {
        port = "3000"
    }
    log.Printf("Server webhook berjalan di port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Menguji Webhook Anda

Uji dengan curl

# Uji webhook channel.message_in
curl -X POST https://your-domain.com/webhook \
  -H "Authorization: your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "channel.message_in",
    "timestamp": 1738056000,
    "conversation_id": "conv_test123",
    "conversation_label": "Test User",
    "need_human": 0,
    "message": {
      "id": "msg_test456",
      "sender_name": "Test User",
      "channel": {
        "id": "channel_test",
        "name": "WhatsApp Business",
        "engine": "wa"
      },
      "bisnis": {
        "id": "biz_test",
        "name": "Test Business"
      },
      "user": {
        "id": "merchant_test",
        "name": "Test Merchant"
      },
      "content": {
        "type": "text",
        "text": "Halo, saya butuh bantuan"
      }
    }
  }'

Uji dengan Postman

  1. Buat request baru
    • Method: POST
    • URL: https://your-domain.com/webhook
  2. Tambahkan header
    • Authorization: your_secret_key
    • Content-Type: application/json
  3. Tambahkan body (raw JSON)
    {
      "event": "channel.message_in",
      "timestamp": 1738056000,
      "conversation_id": "conv_test123",
      "message": {
        "id": "msg_test456",
        "sender_name": "Test User",
        "channel": {
          "name": "WhatsApp Business",
          "engine": "wa"
        },
        "content": {
          "type": "text",
          "text": "Pesan test"
        }
      }
    }
    
  4. Kirim request dan verifikasi Anda menerima 200 OK
Selalu uji dengan payload real sebelum deploy ke produksi.

Checklist Produksi

Sebelum go live, pastikan Anda telah menyelesaikan langkah-langkah ini.
  • URL Webhook dapat diakses publik
  • HTTPS diaktifkan (sertifikat SSL valid)
  • Autentikasi diimplementasikan dan diuji
  • Idempotensi diimplementasikan
  • Penanganan error sudah ada
  • Logging dikonfigurasi
  • Monitoring disiapkan
  • Rate limiting dikonfigurasi
  • Indexing database dioptimalkan
  • Load testing telah dilakukan
  • Tim sudah dilatih langkah-langkah troubleshooting

Langkah Selanjutnya

Konfigurasi

Siapkan webhook di dashboard

Praktik Terbaik

Pastikan penanganan yang andal

Referensi Event

Dokumentasi event lengkap

Troubleshooting

Debug masalah webhook