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
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
-
Buat request baru
- Method: POST
- URL:
https://your-domain.com/webhook
-
Tambahkan header
Authorization:your_secret_keyContent-Type:application/json
-
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" } } } -
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