WA Gateway
Login Dashboard
v1.0

WA Gateway API

Self-hosted WhatsApp API. Kirim pesan teks, gambar, dan file ke nomor WhatsApp manapun menggunakan HTTP request sederhana.

Base URL https://wa.kreasikaryaarjuna.co.id

Quick Start

1

Login ke Dashboard

Buka wa.kreasikaryaarjuna.co.id dan login menggunakan akun Google atau GitHub.

2

Tambah Device

Klik + Add Device, beri nama (misal: "Marketing"), lalu scan QR code menggunakan WhatsApp di HP kamu.
WhatsApp → Perangkat Tertaut → Tautkan Perangkat

3

Ambil API Key

Setelah device Connected, klik card device → klik Show lalu Copy API key.

4

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.

Kunci khusus kirim (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

POST /api/send

Request Body

Parameter Type Required Description
targetstringrequiredNomor tujuan 6281234567890, ID grup 1203...@g.us, atau JID lengkap (...@c.us / ...@lid). Lihat Format Nomor.
messagestringrequiredIsi pesan teks
typestringoptionalDefault: text
quoted_message_idstringoptionalBalas (reply) pesan tertentu. Isi dengan message_id dari payload webhook. Hanya untuk teks.

Request

{
  "target": "6281234567890",
  "message": "Halo! Ini pesan dari WA Gateway 👋"
}
Tautan & pratinjau. Tulis tautan lengkap dengan 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

POST /api/send
Parameter Type Required Description
targetstringrequiredNomor tujuan
typestringrequiredIsi: image
urlstringrequiredURL 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.
captionstringoptionalTeks di bawah gambar
{
  "target": "6281234567890",
  "type": "image",
  "url": "https://example.com/gambar.jpg",
  "caption": "Ini caption gambarnya"
}

Send File / Document

POST /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)

POST /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
targetstringrequiredNomor / ID grup tujuan
typestringrequiredimage atau document
base64stringrequiredData URI data:image/jpeg;base64,…, atau base64 mentah (wajib isi mimetype). Baris baru/spasi di dalamnya diabaikan.
mimetypestringoptionalWajib kalau base64 bukan data URI. Contoh image/png, application/pdf.
filenamestringoptionalNama file yang terlihat penerima (penting untuk dokumen).
captionstringoptionalTeks 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
Batas ukuran body 8 MB — base64 ~33% lebih besar dari filenya, jadi file maksimal sekitar 6 MB. Lebih dari itu dijawab 413. Kecilkan gambar dulu (lebar 1280 px, JPEG kualitas 80 biasanya < 300 KB).

Message Logs

GET /api/messages/logs
Khusus dashboard. Endpoint ini memakai sesi login dashboard (cookie), bukan API key — tidak bisa dipanggil dari aplikasi dengan 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)
directionallinbound atau outbound
limit50Maks 200
offset0Untuk 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
fromJID lengkap pengirim (@c.us, @lid) atau grup (@g.us). Bisa langsung dipakai sebagai target untuk membalas.
typetext, image, video, document, audio, ptt, sticker, location, …
message_idID lengkap pesan — kirim balik sebagai quoted_message_id untuk me-reply.
group, author, author_nameHanya pesan grup. from = grupnya; author = JID penulis; author_name = nama profilnya (bisa kosong).
quotedMessageIdAda kalau pesan ini me-reply pesan lain (ID pendek).
has_media, media_url, media_mimeGambar/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_nameHanya pesan lokasi.
Pesan grup tidak diteruskan secara bawaan. Satu nomor bisa dipakai beberapa aplikasi; tanpa izin eksplisit isi grup bocor ke semua webhook. Minta admin gateway mendaftarkan ID grup (...@g.us, pisahkan koma) ke webhook_groups device-mu — belum ada di form dashboard.
Yang diteruskan tanpa saringan: semua chat pribadi, status WA (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.

Regenerate API Key = kunci tanda tangan ikut berganti. Sampai kunci di aplikasimu diperbarui, semua webhook ditolak aplikasimu sendiri (terlihat sebagai HTTP 401 di tab Delivery Log) dan pesan masuk hilang dari sisi aplikasi. Perbarui kunci di aplikasi segera sesudah menekan Regenerate.
// 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
401API key requiredHeader Authorization tidak ada
401Invalid API keyAPI key salah atau sudah di-regenerate
400Device not connectedWhatsApp device belum terhubung / scan QR
400target is requiredParameter target tidak ada di request body
400message is requiredKirim teks tanpa message
400url atau base64 wajib diisi untuk mediatype bukan text tapi url dan base64 kosong
429Too many requests, slow downLebih dari 60 request/menit dari satu IP. Tunggu lalu coba lagi.
403Kunci-kirim (sk_) hanya bisa … base64Kunci sk_ mengirim media dengan url. Pakai base64.
413Body terlalu besarBody lebih dari 8 MB (file ~6 MB). Kecilkan gambarnya.
400base64 tidak valid / mimetype wajib diisiIsi base64 rusak, atau base64 mentah tanpa mimetype.
400Body bukan JSON yang validJSON 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 + nomor6281234567890✓ Benar
Dengan tanda ++6281234567890✗ Salah
Dengan leading 0081234567890✗ Salah
ID grup120363012345678901@g.us✓ Benar — nomor device harus anggota grup
JID lengkap (dari webhook)6281234567890@c.us✓ Benar