WA Gateway API
Self-hosted WhatsApp API. Kirim pesan teks, gambar, dan file ke nomor WhatsApp manapun menggunakan HTTP request sederhana.
https://wa.kreasikaryaarjuna.co.id
Quick Start
Login ke Dashboard
Buka wa.kreasikaryaarjuna.co.id dan login menggunakan akun Google atau GitHub.
Tambah Device
Klik + Add Device, beri nama (misal: "Marketing"), lalu scan QR code menggunakan WhatsApp di HP kamu.
WhatsApp → Perangkat Tertaut → Tautkan Perangkat
Ambil API Key
Setelah device Connected, klik card device → klik Show lalu Copy API key.
Kirim Pesan Pertama
curl -X POST https://wa.kreasikaryaarjuna.co.id/api/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"target":"6281234567890","message":"Hello!"}'
Authentication
Setiap request harus menyertakan API Key milik device di header Authorization.
Authorization: Bearer your_api_key_here
Query parameter juga diterima, tapi tidak disarankan — kunci di URL ikut tercatat di log proxy & riwayat browser:
POST /api/send?token=your_api_key_here
Penting: Satu API Key hanya berlaku untuk satu device. Jika kamu punya beberapa nomor WhatsApp, setiap nomor punya API Key-nya sendiri.
Perlakukan seperti sandi. API Key yang sama dipakai untuk menandatangani webhook device itu (lihat Webhook). Siapa pun yang memegangnya bisa mengirim pesan atas nama nomor itu dan memalsukan pesan masuk ke aplikasi penerima webhook. Kalau bocor: dashboard → device → Regenerate key, lalu perbarui aplikasi yang memakainya.
sk_…). Kalau aplikasimu cuma perlu mengirim lewat nomor milik orang lain, minta admin gateway membuatkan kunci-kirim — jangan minta API Key device-nya. Cara pakainya sama persis (Authorization: Bearer sk_…), tapi hanya berlaku untuk POST /api/send, media hanya lewat base64 (bukan url), tidak bisa dipakai untuk memalsukan webhook, tiap pesan tercatat atas nama pemegangnya, dan bisa dicabut sendiri-sendiri tanpa mengganggu aplikasi lain.
Send Text Message
/api/send
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | required | Nomor tujuan 6281234567890, ID grup 1203...@g.us, atau JID lengkap (...@c.us / ...@lid). Lihat Format Nomor. |
message | string | required | Isi pesan teks |
type | string | optional | Default: text |
quoted_message_id | string | optional | Balas (reply) pesan tertentu. Isi dengan message_id dari payload webhook. Hanya untuk teks. |
Request
{
"target": "6281234567890",
"message": "Halo! Ini pesan dari WA Gateway 👋"
}
https://, sebaiknya di baris sendiri. Kartu pratinjau dibuat WhatsApp dari tag Open Graph halaman itu (og:title, og:image — pakai JPG). Tanpa skema, WhatsApp mencoba http:// lewat pengalihan dan pratinjaunya sering kosong. WhatsApp juga menyimpan pratinjau yang gagal; tambahkan mis. ?v=2 untuk memaksanya mengambil ulang.
Response
{
"success": true,
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"wa_id": "3EB0C767D97B2C123456"
}
Send Image
/api/send
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | required | Nomor tujuan |
type | string | required | Isi: image |
url | string | required | URL gambar (jpg, png, gif, webp). Diunduh oleh server gateway, jadi harus URL publik — alamat jaringan internal / localhost / di balik login akan gagal. Tidak tersedia untuk kunci sk_; pakai base64. |
caption | string | optional | Teks di bawah gambar |
{
"target": "6281234567890",
"type": "image",
"url": "https://example.com/gambar.jpg",
"caption": "Ini caption gambarnya"
}
Send File / Document
/api/send
{
"target": "6281234567890",
"type": "document",
"url": "https://example.com/laporan.pdf",
"caption": "Laporan Bulan Ini"
}
Mendukung: PDF, DOCX, XLSX, ZIP, dan format file lainnya.
Send Media (base64)
/api/send
File dikirim langsung di body, jadi server gateway tak perlu mengunduh apa pun. Pakai ini kalau gambarmu ada di jaringan internal, dibuat oleh aplikasi (tangkapan layar, grafik), atau kamu memakai kunci sk_. Teks ditaruh di caption — gambar + teks jadi satu pesan.
| Parameter | Type | Required | Description |
|---|---|---|---|
target | string | required | Nomor / ID grup tujuan |
type | string | required | image atau document |
base64 | string | required | Data URI data:image/jpeg;base64,…, atau base64 mentah (wajib isi mimetype). Baris baru/spasi di dalamnya diabaikan. |
mimetype | string | optional | Wajib kalau base64 bukan data URI. Contoh image/png, application/pdf. |
filename | string | optional | Nama file yang terlihat penerima (penting untuk dokumen). |
caption | string | optional | Teks di bawah gambar |
{
"target": "6281234567890",
"type": "image",
"base64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...",
"caption": "Alarm: pintu gudang terbuka"
}
Dari file — cURL (Linux/macOS)
# Body ditulis ke file dulu: base64 gambar biasa lebih panjang dari batas # satu argumen shell (128 KB), jadi "-d '{...}'" langsung akan gagal. # macOS: ganti "base64 -w0 foto.jpg" dengan "base64 -i foto.jpg" printf '{"target":"6281234567890","type":"image","mimetype":"image/jpeg","caption":"Tes","base64":"%s"}' \ "$(base64 -w0 foto.jpg)" > body.json curl -X POST https://wa.kreasikaryaarjuna.co.id/api/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d @body.json
Python
import base64, requests with open("foto.jpg", "rb") as f: b64 = base64.b64encode(f.read()).decode() requests.post("https://wa.kreasikaryaarjuna.co.id/api/send", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={"target": "6281234567890", "type": "image", "base64": f"data:image/jpeg;base64,{b64}", "caption": "Tes"}, timeout=60)
PHP
$body = json_encode([
'target' => '6281234567890',
'type' => 'image',
'base64' => 'data:image/jpeg;base64,' . base64_encode(file_get_contents('foto.jpg')),
'caption' => 'Tes',
]);
// kirim $body dengan curl seperti contoh PHP di bawah
413. Kecilkan gambar dulu (lebar 1280 px, JPEG kualitas 80 biasanya < 300 KB).
Message Logs
/api/messages/logs
Authorization: Bearer (hasilnya 401). Simpan sendiri message_id dari respons /api/send kalau aplikasimu perlu riwayat.
| Query Param | Default | Description |
|---|---|---|
device_id | — | ID device (lihat di dashboard) |
direction | all | inbound atau outbound |
limit | 50 | Maks 200 |
offset | 0 | Untuk pagination |
{
"success": true,
"messages": [
{
"id": "550e8400-...",
"direction": "outbound",
"to_number": "6281234567890",
"type": "text",
"content": "Halo!",
"status": "sent",
"created_at": 1745590800
}
]
}
Webhook (Pesan Masuk)
Setiap pesan masuk dikirim via HTTP POST (JSON) ke URL webhook yang kamu set di dashboard (klik device → isi Webhook URL → Save). Pesan yang dikirim dari nomor device itu sendiri tidak diteruskan — termasuk yang diketik di HP atau WhatsApp Desktop yang login di nomor itu. Untuk menguji, kirim dari nomor lain.
Payload — chat pribadi
{
"event": "message.received",
"device_id": "uuid-device-kamu",
"from": "6281234567890@c.us",
"type": "text",
"message": "Halo, saya mau tanya...",
"timestamp": 1745590800000,
"message_id": "false_6281234567890@c.us_3EB0C767D97B2C123456"
}
| Field | Keterangan |
|---|---|
from | JID lengkap pengirim (@c.us, @lid) atau grup (@g.us). Bisa langsung dipakai sebagai target untuk membalas. |
type | text, image, video, document, audio, ptt, sticker, location, … |
message_id | ID lengkap pesan — kirim balik sebagai quoted_message_id untuk me-reply. |
group, author, author_name | Hanya pesan grup. from = grupnya; author = JID penulis; author_name = nama profilnya (bisa kosong). |
quotedMessageId | Ada kalau pesan ini me-reply pesan lain (ID pendek). |
has_media, media_url, media_mime | Gambar/video/dokumen/audio. File diunduh gateway lalu disajikan di media_url. Kalau unduhan gagal, media_url tidak ada. Media grup dihapus otomatis sesudah 7 hari — simpan sendiri kalau perlu. |
lat, lng, location_name | Hanya pesan lokasi. |
...@g.us, pisahkan koma) ke webhook_groups device-mu — belum ada di form dashboard.
from: "status@broadcast") dan channel (...@newsletter) yang diterima nomor itu. Saringan hanya berlaku untuk grup. Kalau aplikasimu cuma butuh grup tertentu, periksa group dan from, lalu abaikan sisanya (tetap jawab 2xx).
Verifikasi tanda tangan (wajib)
Tiap request membawa header X-Webhook-Signature: sha256=<hex> — HMAC-SHA256 dari body mentah dengan API Key device sebagai kuncinya. Tolak request yang tanda tangannya tidak cocok: URL webhook bisa ditebak orang, tanda tangan tidak.
// Express.js — pakai body MENTAH, bukan hasil JSON.parse const crypto = require('crypto'); app.post('/webhook/wa', express.raw({ type: 'application/json' }), (req, res) => { const harap = 'sha256=' + crypto.createHmac('sha256', API_KEY) .update(req.body).digest('hex'); const dapat = req.get('X-Webhook-Signature') || ''; if (dapat.length !== harap.length || !crypto.timingSafeEqual(Buffer.from(dapat), Buffer.from(harap))) { return res.status(401).end(); } const { from, message, message_id } = JSON.parse(req.body); // balas: kirim ke /api/send dengan target = from, quoted_message_id = message_id res.json({ received: true }); });
# Python (FastAPI / Flask sama saja: yang di-hash adalah body mentah) import hashlib, hmac def sah(body: bytes, header: str, api_key: str) -> bool: harap = "sha256=" + hmac.new(api_key.encode(), body, hashlib.sha256).hexdigest() return hmac.compare_digest(harap, header or "")
Tombol Test di dashboard
Mengirim payload di bawah, ditandatangani dengan cara yang sama, tanpa field group. Jawab 2xx (boleh diabaikan isinya). Hasil 401 hampir selalu berarti kunci di aplikasimu tidak sama dengan API Key device saat ini — misalnya sesudah Regenerate.
{
"event": "test",
"device_id": "uuid-device-kamu",
"message": "This is a test webhook from WA Gateway",
"timestamp": 1745590800000
}
Balas dengan status 2xx secepatnya (proses berat di belakang). Kalau gagal atau lewat 5 detik (WEBHOOK_TIMEOUT di server), gateway mencoba ulang 2 kali lagi (jeda 1 dan 2 detik) — jadikan handler-mu aman terhadap pesan ganda, misalnya dengan menyimpan message_id. Redirect 301/302/303 diikuti (cocok untuk Google Apps Script).
JavaScript / Node.js
const API_KEY = 'your_api_key_here'; const BASE_URL = 'https://wa.kreasikaryaarjuna.co.id'; async function sendMessage(target, message) { const res = await fetch(`${BASE_URL}/api/send`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ target, message }), }); return res.json(); } // Kirim pesan sendMessage('6281234567890', 'Halo dari Node.js! 👋') .then(console.log); // Kirim gambar async function sendImage(target, url, caption = '') { const res = await fetch(`${BASE_URL}/api/send`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ target, type: 'image', url, caption }), }); return res.json(); }
PHP
<?php function sendWhatsApp($target, $message, $apiKey) { $ch = curl_init('https://wa.kreasikaryaarjuna.co.id/api/send'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'target' => $target, 'message' => $message, ]), ]); $result = json_decode(curl_exec($ch), true); curl_close($ch); return $result; } // Pemakaian $response = sendWhatsApp( '6281234567890', 'Notifikasi dari sistem kami!', 'your_api_key_here' ); if ($response['success']) { echo "Pesan terkirim: " . $response['message_id']; }
Python
import requests API_KEY = "your_api_key_here" BASE_URL = "https://wa.kreasikaryaarjuna.co.id" def send_message(target: str, message: str) -> dict: headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } payload = {"target": target, "message": message} res = requests.post(f"{BASE_URL}/api/send", json=payload, headers=headers) return res.json() def send_image(target: str, url: str, caption: str = "") -> dict: headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } payload = {"target": target, "type": "image", "url": url, "caption": caption} res = requests.post(f"{BASE_URL}/api/send", json=payload, headers=headers) return res.json() # Pemakaian result = send_message("6281234567890", "Hello dari Python! 🐍") print(result)
cURL
Kirim teks
curl -X POST https://wa.kreasikaryaarjuna.co.id/api/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"target":"6281234567890","message":"Halo!"}'
Kirim gambar
curl -X POST https://wa.kreasikaryaarjuna.co.id/api/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"target":"6281234567890","type":"image","url":"https://example.com/img.jpg","caption":"Lihat ini!"}'
Cek message logs
curl "https://wa.kreasikaryaarjuna.co.id/api/messages/logs?device_id=DEVICE_ID&limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
Error Codes
| HTTP Status | Message | Penyebab |
|---|---|---|
| 401 | API key required | Header Authorization tidak ada |
| 401 | Invalid API key | API key salah atau sudah di-regenerate |
| 400 | Device not connected | WhatsApp device belum terhubung / scan QR |
| 400 | target is required | Parameter target tidak ada di request body |
| 400 | message is required | Kirim teks tanpa message |
| 400 | url atau base64 wajib diisi untuk media | type bukan text tapi url dan base64 kosong |
| 429 | Too many requests, slow down | Lebih dari 60 request/menit dari satu IP. Tunggu lalu coba lagi. |
| 403 | Kunci-kirim (sk_) hanya bisa … base64 | Kunci sk_ mengirim media dengan url. Pakai base64. |
| 413 | Body terlalu besar | Body lebih dari 8 MB (file ~6 MB). Kecilkan gambarnya. |
| 400 | base64 tidak valid / mimetype wajib diisi | Isi base64 rusak, atau base64 mentah tanpa mimetype. |
| 400 | Body bukan JSON yang valid | JSON terpotong atau tanda kutip tidak di-escape. |
| 500 | (pesan error aslinya) | Gagal kirim — nomor tak terdaftar di WhatsApp, URL media tak bisa diunduh, dll. Tercatat failed di Message Logs. |
Format Nomor Telepon
Gunakan format internasional tanpa tanda plus (+) dan tanpa leading zero.
| Format | Contoh | Status |
|---|---|---|
| Kode negara + nomor | 6281234567890 | ✓ Benar |
| Dengan tanda + | +6281234567890 | ✗ Salah |
| Dengan leading 0 | 081234567890 | ✗ Salah |
| ID grup | 120363012345678901@g.us | ✓ Benar — nomor device harus anggota grup |
| JID lengkap (dari webhook) | 6281234567890@c.us | ✓ Benar |