Realtime

Webhook

Terima notifikasi otomatis ketika ada pesan masuk, status berubah, atau percakapan baru di whatsGo.

Membuat Webhook

Register webhook untuk mulai menerima event secara real-time. Setiap webhook hanya bisa memiliki satu URL, tetapi kamu bisa mendaftarkan beberapa webhook untuk URL yang berbeda.

Endpoint

POST {base_url}/v1/webhooks

Request Body

Field Tipe Wajib Deskripsi
name string Ya Nama label untuk webhook (e.g. "Produksi CRM").
url string Ya URL endpoint kamu yang akan menerima payload. Harus HTTPS.
events array Ya Daftar event yang ingin di-subscribe (e.g. ["message.inbound", "message.sent"]).
secret string Tidak Secret key untuk verifikasi signature HMAC-SHA256. Jika tidak disediakan, sistem akan generate otomatis.

Contoh Request

curl -X POST "https://whats.sahabatmobile.com/external-api/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Produksi CRM",
    "url": "https://crm.example.com/webhooks/whatsapp",
    "events": [
      "message.inbound",
      "message.sent",
      "message.delivered",
      "message.read",
      "conversation.opened"
    ]
  }'

Contoh Response

{
  "success": true,
  "message": "Webhook berhasil dibuat.",
  "data": {
    "id": 1,
    "name": "Produksi CRM",
    "url": "https://crm.example.com/webhooks/whatsapp",
    "events": [
      "message.inbound",
      "message.sent",
      "message.delivered",
      "message.read",
      "conversation.opened"
    ],
    "secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "is_active": true,
    "created_at": "2025-07-16T10:00:00.000000Z"
  }
}
Simpan secret dengan aman

Field secret hanya dikembalikan saat pembuatan webhook. Simpan secret ini di tempat yang aman. Jika kamu kehilangannya, buat ulang webhook atau gunakan endpoint update untuk mengatur secret baru.

Daftar Event

whatsGo mendukung beberapa kategori event yang dikirim ke webhook kamu:

Event Deskripsi
message.inbound Pesan baru diterima dari pengguna WhatsApp.
message.sent Pesan berhasil dikirim dari whatsGo.
message.delivered Pesan berhasil diterima di perangkat tujuan.
message.read Pesan telah dibaca oleh penerima.
message.failed Pesan gagal dikirim karena error tertentu.
contact.created Kontak baru terdaftar di workspace.
contact.updated Data kontak diperbarui.
conversation.opened Percakapan baru dibuka oleh pengguna.
conversation.assigned Percakapan ditugaskan ke agent.
conversation.resolved Percakapan ditandai selesai.

Headers yang Dikirim whatsGo

Setiap request webhook menyertakan header berikut untuk membantu kamu memproses dan memverifikasi payload:

Header Deskripsi
X-WhatsGo-Event Tipe event (e.g. message.inbound).
X-WhatsGo-Timestamp Unix timestamp saat event dibuat (dalam detik).
X-WhatsGo-Delivery-Id ID unik pengiriman webhook untuk deduplikasi.
X-WhatsGo-Signature Signature HMAC-SHA256 untuk memverifikasi keaslian payload (format: sha256=...).

Format Payload

Semua webhook payload dikirim sebagai JSON dengan struktur top-level berikut:

Struktur Top-Level

{
  "id": "evt_a1b2c3d4e5f6",
  "event": "message.inbound",
  "timestamp": 1752662400,
  "data": {
    // ... field spesifik event
  }
}
Field Tipe Deskripsi
id string ID unik event.
event string Tipe event yang dikirim.
timestamp integer Unix timestamp saat event terjadi (detik).
data object Payload data yang relevan dengan event.

Payload: message.inbound

{
  "id": "evt_m1n2b3o4u5n6d",
  "event": "message.inbound",
  "timestamp": 1752662400,
  "data": {
    "message_id": "msg_abc123def456",
    "from": "6281234567890",
    "to": "6289876543210",
    "type": "text",
    "body": "Halo, saya ingin bertanya tentang pesanan saya.",
    "media_url": null,
    "timestamp": 1752662400,
    "conversation_id": "conv_xyz789"
  }
}
Field Tipe Deskripsi
message_id string ID unik pesan.
from string Nomor telepon pengirim dalam format internasional.
to string Nomor telepon tujuan (saluran WhatsApp kamu).
type string Jenis pesan: text, image, audio, video, document, location, sticker.
body string|null Teks isi pesan. null untuk pesan media tanpa caption.
media_url string|null URL file media (berlaku untuk tipe image, audio, video, document). Expired setelah 24 jam.
timestamp integer Unix timestamp saat pesan diterima.
conversation_id string ID percakapan terkait.

Payload: contact.created

{
  "id": "evt_c1r2e3a4t5e6d",
  "event": "contact.created",
  "timestamp": 1752662400,
  "data": {
    "contact_id": 42,
    "phone": "6285512345678",
    "name": "Rina Wati",
    "email": null,
    "tags": ["leads"],
    "custom_fields": {
      "source": "website"
    },
    "created_at": "2025-07-16T10:00:00.000000Z"
  }
}

Payload: conversation.opened

{
  "id": "evt_o1p2e3n4e5d6",
  "event": "conversation.opened",
  "timestamp": 1752662400,
  "data": {
    "conversation_id": "conv_xyz789",
    "contact": {
      "id": 42,
      "phone": "6285512345678",
      "name": "Rina Wati"
    },
    "channel": "whatsapp",
    "status": "open",
    "created_at": "2025-07-16T10:00:00.000000Z"
  }
}

Payload: conversation.assigned

{
  "id": "evt_a1s2s3i4g5n6",
  "event": "conversation.assigned",
  "timestamp": 1752662400,
  "data": {
    "conversation_id": "conv_xyz789",
    "contact": {
      "id": 42,
      "phone": "6285512345678",
      "name": "Rina Wati"
    },
    "agent": {
      "id": 7,
      "name": "Agent Budi",
      "email": "budi@example.com"
    },
    "assigned_at": "2025-07-16T10:05:00.000000Z"
  }
}

Verifikasi Signature

whatsGo menggunakan HMAC-SHA256 untuk menandatangani setiap payload webhook. Kamu harus memverifikasi signature ini untuk memastikan request benar-benar berasal dari whatsGo.

Cara Kerja

Signature dihitung dari body payload JSON menggunakan secret key yang kamu atur saat membuat webhook. Format header signature adalah:

X-WhatsGo-Signature: sha256=<hex_digest>

Mengapa Menggunakan Timestamp?

Timestamp dalam header X-WhatsGo-Timestamp memungkinkan kamu mencegah replay attack. Bandingkan timestamp dengan waktu server kamu — jika perbedaan lebih dari 5 menit, tolak request tersebut.

Node.js + Express

const crypto = require('crypto');
const express = require('express');

const app = express();
const WEBHOOK_SECRET = 'whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6';

app.post('/webhooks/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-whatsgo-signature'];
  const timestamp = req.headers['x-whatsgo-timestamp'];
  const payload = req.body.toString();

  // Cek timestamp (tolak jika lebih dari 5 menit)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
  if (age > 300) {
    return res.status(400).json({ error: 'Timestamp terlalu lama' });
  }

  // Hitung signature
  const signedPayload = `${timestamp}.${payload}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(signedPayload)
    .digest('hex');

  // Bandingkan secara aman (timing-safe)
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: 'Signature tidak valid' });
  }

  // Signature valid, proses payload
  const event = JSON.parse(payload);
  console.log(`Event diterima: ${event.event}`);

  res.status(200).json({ received: true });
});

app.listen(3000);

Python + Flask

import hmac
import hashlib
import time
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = 'whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'

@app.route('/webhooks/whatsapp', methods=['POST'])
def webhook():
    signature = request.headers.get('X-WhatsGo-Signature', '')
    timestamp = request.headers.get('X-WhatsGo-Timestamp', '')
    payload = request.data.decode('utf-8')

    # Cek timestamp (tolak jika lebih dari 5 menit)
    age = int(time.time()) - int(timestamp)
    if age > 300:
        return jsonify({'error': 'Timestamp terlalu lama'}), 400

    # Hitung signature
    signed_payload = f'{timestamp}.{payload}'
    expected = 'sha256=' + hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Bandingkan secara aman
    if not hmac.compare_digest(signature, expected):
        return jsonify({'error': 'Signature tidak valid'}), 401

    # Signature valid, proses payload
    event = request.get_json()
    print(f'Event diterima: {event["event"]}')

    return jsonify({'received': True}), 200

Praktik Terbaik

  • Selalu verifikasi signature sebelum memproses payload. Jangan pernah mempercayai request tanpa validasi.
  • Cek timestamp untuk mencegah replay attack. Tolak request dengan timestamp lebih dari 5 menit yang lalu.
  • Response cepat — kirim response 200 dalam 5 detik. whatsGo akan melakukan retry jika endpoint kamu timeout atau return error.
  • Proses secara async — terima payload, kirim 200, lalu proses di background queue untuk menghindari timeout.
  • Handle retry — whatsGo melakukan retry exponential backoff sampai 5 kali. Gunakan X-WhatsGo-Delivery-Id untuk deduplikasi.
  • Gunakan HTTPS — URL webhook harus menggunakan HTTPS untuk keamanan. HTTP tidak didukung.
  • Monitor endpoint — pantau uptime dan latensi endpoint webhook kamu. whatsGo akan menonaktifkan webhook yang terus-menerus gagal.
  • Logging — log semua event yang diterima untuk debugging dan audit trail.
Retry Policy

whatsGo melakukan retry dengan interval: 10 detik, 60 detik, 5 menit, 30 menit, 2 jam. Webhook akan dinonaktifkan otomatis jika gagal selama 7 hari berturut-turut.