WhatsApp API dengan Webhook
Panduan setup webhook WhatsApp API. Terima pesan real-time, handle events, secure webhook. Tutorial lengkap untuk developer!
Webhook = Real-time communication!
Webhook memungkinkan server kamu menerima notifikasi real-time setiap kali ada pesan masuk, status berubah, atau event lainnya di WhatsApp.
Apa itu Webhook?
📡 WEBHOOK EXPLAINED:
TANPA WEBHOOK (Polling):
- Server kamu tanya terus: "Ada pesan baru?"
- Boros resource
- Delay response
- Tidak efisien
DENGAN WEBHOOK:
- WhatsApp yang notify: "Hey, ada pesan!"
- Real-time
- Efisien
- Event-drivenWebhook Events
📋 EVENT TYPES:
MESSAGES:
- messages - Pesan masuk
- message_status - Status delivered/read
ACCOUNT:
- account_update - Perubahan akun
- phone_number_quality - Quality rating
BUSINESS:
- template_status - Template approval
- account_alerts - Peringatan akun
MEDIA:
- media - Media upload statusSetup Webhook
1. Buat Endpoint:
javascript
// server.js (Express)
const express = require('express');
const app = express();
app.use(express.json());
// Webhook verification (GET)
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode'];
const token = req.query['hub.verify_token'];
const challenge = req.query['hub.challenge'];
// Token yang kamu set di Meta Dashboard
const VERIFY_TOKEN = process.env.WEBHOOK_VERIFY_TOKEN;
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
console.log('Webhook verified!');
res.status(200).send(challenge);
} else {
res.status(403).send('Forbidden');
}
});
// Webhook handler (POST)
app.post('/webhook', (req, res) => {
const body = req.body;
console.log('Webhook received:', JSON.stringify(body, null, 2));
// Harus respond 200 dalam 20 detik!
res.status(200).send('OK');
// Process async
processWebhook(body);
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});2. Register di Meta Dashboard:
📝 LANGKAH REGISTER:
1. Buka Meta Developer Dashboard
2. Pilih App kamu
3. WhatsApp > Configuration
4. Webhook > Edit
5. Masukkan:
• Callback URL: https://yourdomain.com/webhook
• Verify Token: token_rahasia_kamu
6. Klik Verify and Save
7. Subscribe ke events yang diinginkanHandle Incoming Messages
Message Structure:
json
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "628123456789",
"phone_number_id": "PHONE_NUMBER_ID"
},
"contacts": [{
"profile": { "name": "John Doe" },
"wa_id": "628987654321"
}],
"messages": [{
"from": "628987654321",
"id": "wamid.xxx",
"timestamp": "1234567890",
"type": "text",
"text": { "body": "Halo!" }
}]
},
"field": "messages"
}]
}]
}Process Handler:
javascript
async function processWebhook(body) {
if (body.object !== 'whatsapp_business_account') return;
for (const entry of body.entry) {
for (const change of entry.changes) {
const value = change.value;
if (change.field === 'messages') {
// Handle incoming messages
if (value.messages) {
for (const message of value.messages) {
await handleIncomingMessage(value, message);
}
}
// Handle status updates
if (value.statuses) {
for (const status of value.statuses) {
await handleStatusUpdate(status);
}
}
}
}
}
}
async function handleIncomingMessage(value, message) {
const from = message.from;
const name = value.contacts[0]?.profile?.name || 'Customer';
const phoneNumberId = value.metadata.phone_number_id;
console.log(`Pesan dari ${name} (${from})`);
switch (message.type) {
case 'text':
await handleTextMessage(phoneNumberId, from, message.text.body);
break;
case 'image':
await handleImageMessage(phoneNumberId, from, message.image);
break;
case 'interactive':
await handleInteractiveMessage(phoneNumberId, from, message.interactive);
break;
default:
console.log(`Tipe pesan tidak dihandle: ${message.type}`);
}
}
async function handleStatusUpdate(status) {
const messageId = status.id;
const recipientId = status.recipient_id;
const statusType = status.status; // sent, delivered, read, failed
console.log(`Status ${statusType} untuk ${messageId}`);
// Update di database
await db.messages.updateOne(
{ messageId },
{ $set: { status: statusType, updatedAt: new Date() } }
);
}Tipe Pesan
Text Message:
javascript
// message.type === 'text'
{
"type": "text",
"text": {
"body": "Halo, mau tanya produk"
}
}
// Handler
async function handleTextMessage(phoneNumberId, from, text) {
console.log(`Text: ${text}`);
// Auto-reply
await sendMessage(phoneNumberId, from, {
type: 'text',
text: { body: 'Hai! Ada yang bisa dibantu?' }
});
}Image Message:
javascript
// message.type === 'image'
{
"type": "image",
"image": {
"id": "MEDIA_ID",
"mime_type": "image/jpeg",
"sha256": "xxx",
"caption": "Ini foto produk"
}
}
// Handler
async function handleImageMessage(phoneNumberId, from, image) {
// Download image
const mediaUrl = await getMediaUrl(image.id);
const imageBuffer = await downloadMedia(mediaUrl);
// Process (OCR, save, etc)
await processImage(imageBuffer);
}Interactive Response:
javascript
// message.type === 'interactive'
{
"type": "interactive",
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "btn_order",
"title": "Order Sekarang"
}
}
}
// atau list_reply
{
"type": "interactive",
"interactive": {
"type": "list_reply",
"list_reply": {
"id": "product_001",
"title": "Produk A",
"description": "Rp 100.000"
}
}
}Webhook Security
Verify Signature:
javascript
const crypto = require('crypto');
function verifyWebhookSignature(req) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) return false;
const expectedSignature = crypto
.createHmac('sha256', process.env.APP_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
const trusted = Buffer.from(`sha256=${expectedSignature}`, 'utf8');
const received = Buffer.from(signature, 'utf8');
return crypto.timingSafeEqual(trusted, received);
}
// Middleware
app.post('/webhook', (req, res) => {
if (!verifyWebhookSignature(req)) {
console.log('Invalid signature!');
return res.status(401).send('Invalid signature');
}
// Process...
res.status(200).send('OK');
});IP Whitelist:
javascript
const ALLOWED_IPS = [
// Meta IP ranges
'157.240.0.0/16',
'31.13.24.0/21',
// ... more IPs
];
function isAllowedIP(ip) {
// Implement IP check
return ALLOWED_IPS.some(range => ipInRange(ip, range));
}Error Handling
Retry Logic:
javascript
// Meta akan retry jika tidak dapat 200
// Retry schedule: 15s, 1m, 5m, 30m
// Pastikan respond cepat!
app.post('/webhook', async (req, res) => {
// RESPOND IMMEDIATELY
res.status(200).send('OK');
// Process di background
try {
await processWebhook(req.body);
} catch (error) {
console.error('Webhook processing error:', error);
// Log untuk debug, tapi jangan fail response
}
});Queue Processing:
javascript
const Queue = require('bull');
const webhookQueue = new Queue('webhook-processing');
app.post('/webhook', (req, res) => {
// Respond immediately
res.status(200).send('OK');
// Add to queue
webhookQueue.add(req.body);
});
// Process dari queue
webhookQueue.process(async (job) => {
await processWebhook(job.data);
});Testing Webhook
Ngrok untuk Local:
bash
# Install ngrok
npm install -g ngrok
# Jalankan server lokal
node server.js
# Expose ke internet
ngrok http 3000
# Dapat URL seperti: https://abc123.ngrok.io
# Gunakan URL ini untuk webhook di Meta DashboardTest dengan curl:
bash
curl -X POST https://yourdomain.com/webhook \
-H "Content-Type: application/json" \
-d '{
"object": "whatsapp_business_account",
"entry": [{
"changes": [{
"value": {
"messages": [{
"from": "628123456789",
"type": "text",
"text": {"body": "Test message"}
}]
},
"field": "messages"
}]
}]
}'Best Practices
DO ✅
- Respond 200 dalam 5 detik
- Verify signature
- Process async (queue)
- Log semua events
- Handle all message types
- Idempotent processingDON'T ❌
- Block response untuk processing
- Skip signature verification
- Ignore unknown events
- No error handling
- Process synchronously
- Trust all requestsFAQ
Kenapa webhook tidak diterima?
Cek: URL accessible, SSL valid, respond 200, verify token benar.
Berapa lama timeout webhook?
20 detik. Lebih dari itu dianggap failed dan akan retry.
Perlu handle retry/duplicate?
Ya! Simpan message ID dan skip jika sudah diproses.
Kesimpulan
Webhook = Real-time & efisien!
| Polling | Webhook |
|---|---|
| Tanya terus | Event-driven |
| Boros resource | Efisien |
| Delay | Real-time |
| Complex | Simple |