Pas user tap pesan tombol atau menu list yang kamu kirim, Wazapin bakal trigger webhook.
Ikuti panduan ini buat kenali balasan interaktif dan ambil pilihan yang user klik.
Kapan kamu butuh ini
Balasan interaktif kepakai buat:
- Arahkan pelanggan ke alur yang beda (mis. Sales vs Support).
- Konfirmasi aksi transaksi (mis. tap “Konfirmasi Pesanan” atau “Jadwal Ulang”).
- Bikin menu chatbot otomatis dan navigator FAQ.
Alur proses
Handle pilihan interaktif cuma butuh tiga langkah:
graph TD
A[User tap tombol atau item list] --> B[Webhook message.new msg_type: interactive]
B --> C[GET /v1/messages/{id} buat ambil detail]
C --> D{interactive.type?}
D -->|button_reply| E[Baca ID & title tombol]
D -->|list_reply| F[Baca row ID, title & description]1. Terima webhook interaktif
Wazapin kirim webhook message.new yang ringan dengan msg_type: "interactive".
Contoh payload webhook-nya:
{
"message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"conversation_id": "0f89b0f9-74b4-44f9-b9b6-48f6d4de57aa",
"contact_id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
"channel_id": "wzp_abc123",
"direction": "inbound",
"from_phone": "6281234567890",
"msg_type": "interactive"
}2. Ambil detail pilihannya
Panggil GET /v1/messages/{messageID} pakai message_id dari payload. Di response bakal ada object interactive di dalam blok content yang jelasin input user.
Struktur interaktif
Tergantung tipe pesannya, detail yang balik punya dua bentuk:
Tombol quick-reply (button_reply)
Kalau user tap tombol, blok interactive bakal punya type "button_reply":
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "interactive",
"content": {
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "confirm_yes",
"title": "Yes, I confirm"
}
}
}
}Baris menu list (list_reply)
Kalau user pilih opsi dari menu list, blok interactive bakal punya type "list_reply":
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "interactive",
"content": {
"interactive": {
"type": "list_reply",
"list_reply": {
"id": "support_agent",
"title": "Talk to Support",
"description": "Connect to a live agent"
}
}
}
}Contoh kode
Cara handle webhook dan proses pilihan user:
# Ambil detail pesan buat cek pilihan user
curl -X GET "https://api.wazapin.com/v1/messages/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Accept: application/json"import express from "express";
import { Webhook } from "svix";
import { WazapinClient } from "@wazapin/sdk";
const app = express();
const wazapin = new WazapinClient({ apiKey: process.env.WAZAPIN_API_KEY });
const wh = new Webhook(process.env.WAZAPIN_WEBHOOK_SECRET!);
app.post("/webhooks/wazapin", express.raw({ type: "application/json" }), async (req, res) => {
try {
wh.verify(req.body, req.headers as Record<string, string>);
} catch (err) {
return res.status(403).send("Invalid signature");
}
const payload = JSON.parse(req.body.toString("utf8"));
if (payload.direction === "inbound" && payload.msg_type === "interactive") {
const messageId = payload.message_id;
// 1. Ambil pilihan interaktif
const { data: message } = await wazapin.messages.get(messageId);
const interactive = message.content?.interactive;
if (interactive) {
if (interactive.type === "button_reply") {
const button = interactive.button_reply;
console.log(`User tap tombol ID: ${button.id} label: ${button.title}`);
// Arahkan pelanggan ke flow yang sesuai
} else if (interactive.type === "list_reply") {
const row = interactive.list_reply;
console.log(`User pilih opsi list ID: ${row.id} label: ${row.title}`);
// Handle pilihan list
}
}
}
res.status(200).send("OK");
});from fastapi import FastAPI, Request, HTTPException
from svix.webhooks import Webhook, WebhookVerificationError
import requests
import os
app = FastAPI()
wh = Webhook(os.environ["WAZAPIN_WEBHOOK_SECRET"])
API_KEY = os.environ["WAZAPIN_API_KEY"]
@app.post("/webhooks/wazapin")
async def handle_webhook(request: Request):
body = await request.body()
try:
wh.verify(body, dict(request.headers))
except WebhookVerificationError:
raise HTTPException(status_code=403, detail="Invalid signature")
payload = await request.json()
if payload.get("direction") == "inbound" and payload.get("msg_type") == "interactive":
message_id = payload.get("message_id")
# 1. Ambil detail pesan
response = requests.get(
f"https://api.wazapin.com/v1/messages/{message_id}",
headers={"X-Api-Key": API_KEY, "Accept": "application/json"}
)
if response.status_code == 200:
message_data = response.json().get("data", {})
interactive = message_data.get("content", {}).get("interactive", {})
interactive_type = interactive.get("type")
if interactive_type == "button_reply":
button = interactive.get("button_reply", {})
print(f"User tap tombol: {button.get('id')} ({button.get('title')})")
elif interactive_type == "list_reply":
row = interactive.get("list_reply", {})
print(f"User pilih opsi: {row.get('id')} ({row.get('title')})")
return {"ok": True}Troubleshooting
Webhook masuk tapi interactive kosong
Pastiin kamu ambil pesan pakai ID lewat GET /v1/messages/{messageID}. Body webhook memang sengaja dibikin ringkas dan nggak bawa blok interactive langsung.
User pencet banyak tombol
WhatsApp nggak bolehin pilih banyak opsi di satu pesan. Tiap tap itu atomic dan masuk sebagai webhook terpisah. Kalau user coba tap tombol di pesan lama, sistem kamu bakal dapat webhook baru dengan message_id yang beda. Kamu bisa lacak pesan aslinya lewat timeline conversation atau header parent message kalau ada.