Webhook Media dan Story
Webhook media dan story mengirimkan notifikasi otomatis ke server setiap kali file media diterima atau kontak mengunggah story baru. Data file disertakan langsung dalam payload dalam format base64.
Prasyarat
- Webhook sudah dikonfigurasi dan opsi Webhook Media sudah diaktifkan di dashboard.
Panduan konfigurasi di Integrasi Notifikasi Webhook.
Event: media
Event media dikirim ke server setiap kali file media masuk ke nomor WhatsApp yang terhubung bisa berupa gambar, video, audio, dokumen, atau stiker. File dikirim dalam payload sebagai string base64 yang perlu didecode sebelum digunakan.
{
"type": "media", // Tipe webhook yang dikirim
"results": { // Kumpulan object dari event message
"key": { // Informasi pengirim
"fromMe": false, // Dikirim ke nomer sendiri ?
"id": "xxxxx" // ID pesan
},
"messageTimestamp": 1694502465, // Waktu pesan
"pushName": "xxxxx", // Nama Whatsapp
"broadcast": false, // Apakah pesan broadcast ?
"message": {
... // Object disini adalah raw pesan whatsapp, bisa berubah sewaktu2
},
"verifiedBizName": "WHAPI", // Object untuk nama whatsapp bisnis
"t": 1694502465, // Waktu pesan
"id": "xxxxx", // ID pesan
"fromMe": false, // Dikirim dari nomer sendiri ?
"isGroupMsg": false, // Apakah pesan dari grup ?
"isStories": false, // Apakah pesan story ?
"pushname": "WHAPI", // Object untuk nama whatsapp bisnis
"timestamp": 1691639955, // Waktu pesan
"isEphemeralMessage": false,
"viewOnce": false, // Pesan dilihat 1x
"type": "imageMessage", // imageMessage | videoMessage | stickerMessage | audioMessage | documentMessage | documentWithCaptionMessage
"isMedia": true, // Apakah file media ?
"mimetype": "image/jpeg", // Mime type dari file, akan sesuai dengan file yang dikirim
"quotedMsg": { // Jika pesan berupa quote, akan ada data disini
... // Data object disini adalah raw dari whatsapp.
},
"mentionedJidList": [], // Mention nama akan muncul disini
"body": "Isi Pesan", // Object akan tampil jika file memiliki caption
"apiKey": "xxxxx" // Alamat API Key dari device
},
"base64_file": "..." // Isi dari base64 file
}
Nilai yang mungkin untuk field type:
| Nilai | Keterangan |
|---|---|
| imageMessage | File gambar |
| videoMessage | File video |
| audioMessage | File audio atau voice note |
| stickerMessage | Stiker |
| documentMessage | Dokumen |
| documentWithCaptionMessage | Dokumen dengan caption |
Field base64_file berisi konten file dalam format base64. Decode terlebih dahulu sebelum digunakan sebagai file.
Event: stories
Event stories dikirim ke server setiap kali kontak yang tersimpan di nomor WhatsApp yang terhubung mengunggah story baru. Payload berisi data story beserta file media story dalam format base64.
{
"type": "stories",
"results": {
"key": {
"remoteJid": "status@broadcast",
"fromMe": false,
"id": "1D25F51B1367D75D029ED7203E629D60",
},
"messageTimestamp": 1694503269,
"pushName": "xxxxx", // Nama Whatsapp
"broadcast": true,
"message": {
"..."
},
"verifiedBizName": "WHAPI", // Object untuk nama whatsapp bisnis
"t": 1694503269,
"id": "xxxxx", // ID pesan
"fromMe": false, // Dikirim dari nomer sendiri ?
"isGroupMsg": false, // Apakah pesan dari grup ?
"isStories": true, // Apakah pesan story ?
"pushname": "WHAPI", // Object untuk nama whatsapp bisnis
"timestamp": 1691639955, // Waktu pesan
"isEphemeralMessage": false,
"viewOnce": false, // Pesan dilihat 1x
"type": "imageMessage",
"isMedia": true, // Apakah file media ?
"mimetype": "image/jpeg", // Mime type dari file, akan sesuai dengan file yang dikirim
"quotedMsg": {
} // Jika pesan berupa quote, akan ada data disini
"mentionedJidList": [], // Mention nama akan muncul disini
"body": "Isi Pesan", // Object akan tampil jika file memiliki caption
"apiKey": "xxxxx" // Alamat API Key dari device
},
"base64_file": "..." // Isi dari base64 file
}
Payload stories memiliki struktur yang sama dengan payload media. Perbedaannya ada di field isStories: true dan remoteJid: "status@broadcast".
Cara Decode Base64
Data file dalam field base64_file perlu didecode sebelum bisa digunakan.
- PHP
- Python
- NodeJS ( Express )
header('Content-Type: application/json');
$raw_input = file_get_contents('php://input');
$data = json_decode($raw_input, true);
if (!$data) {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Invalid JSON payload"]);
exit;
}
// 1. Validasi: type harus 'stories' ATAU 'media'
$allowed_types = ['stories', 'media'];
$type = $data['type'] ?? '';
if (!in_array($type, $allowed_types)) {
http_response_code(400);
echo json_encode([
"status" => "error",
"message" => "Tipe data tidak valid. Hanya menerima tipe 'stories' atau 'media'."
]);
exit;
}
// 2. Proses decode file
if (isset($data['base64_file'])) {
$base64_string = $data['base64_file'];
if (preg_match('/^data:image\/(\w+);base64,/', $base64_string, $matches)) {
$base64_string = substr($base64_string, strpos($base64_string, ',') + 1);
}
$decoded_file = base64_decode($base64_string);
$file_name = 'hasil_download.jpg';
file_put_contents($file_name, $decoded_file);
echo json_encode(["status" => "success", "message" => "File dari webhook " . $type . " berhasil disimpan"]);
} else {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Data base64_file tidak ditemukan"]);
}
from flask import Flask, request, jsonify
import base64
import re
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
if not data:
return jsonify({"status": "error", "message": "Body request kosong"}), 400
# 1. Validasi: type harus 'stories' ATAU 'media'
data_type = data.get('type')
if data_type not in ['stories', 'media']:
return jsonify({
"status": "error",
"message": "Tipe data tidak valid. Hanya menerima tipe 'stories' atau 'media'."
}), 400
if 'base64_file' not in data:
return jsonify({"status": "error", "message": "Data base64_file tidak ditemukan"}), 400
base64_string = data['base64_file']
base64_string = re.sub(r'^data:image/.+;base64,', '', base64_string)
try:
file_bytes = base64.b64decode(base64_string)
with open("hasil_download.jpg", "wb") as f:
f.write(file_bytes)
return jsonify({"status": "success", "message": f"File dari webhook {data_type} berhasil disimpan"}), 200
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
if __name__ == '__main__':
app.run(port=5000)
const express = require('express');
const fs = require('fs');
const app = express();
app.use(express.json({ limit: '50mb' }));
app.post('/webhook', (req, res) => {
const data = req.body;
if (!data) {
return res.status(400).json({ status: 'error', message: 'Body request kosong' });
}
// 1. Validasi: type harus 'stories' ATAU 'media'
const allowedTypes = ['stories', 'media'];
if (!allowedTypes.includes(data.type)) {
return res.status(400).json({
status: 'error',
message: "Tipe data tidak valid. Hanya menerima tipe 'stories' atau 'media'."
});
}
if (!data.base64_file) {
return res.status(400).json({ status: 'error', message: 'Data base64_file tidak ditemukan' });
}
let base64String = data.base64_file;
base64String = base64String.replace(/^data:image\/\w+;base64,/, "");
const fileBuffer = Buffer.from(base64String, 'base64');
fs.writeFile('hasil_download.jpg', fileBuffer, (err) => {
if (err) {
return res.status(500).json({ status: 'error', message: 'Gagal menyimpan file' });
}
res.status(200).json({ status: 'success', message: `File dari webhook ${data.type} berhasil disimpan` });
});
});
app.listen(3000, () => {
console.log('Server webhook berjalan di port 3000');
});