Qirim — Developer REST API & Webhooks Reference

REST API v1.2 CONNECTED

Spesifikasi parameter, integrasi device headless (QR / Pairing Code via API), WhatsApp Messaging, Phonebook, OTP Engine, dan Webhooks siap pakai.

1. Autentikasi 2-Tier (Client Secret & Device API Key)

QIRIM menggunakan arsitektur 2-Tier Authentication yang fleksibel dan aman untuk integrasi backend web maupun sistem mikroservis:

Tier 1: Account Level
Master Client Secret

Format: qirim_sec_...

Digunakan oleh backend server Anda untuk mengelola perangkat (membuat device baru, mengambil Base64 QR Code, pairing code, dan menghapus device) serta pengiriman pesan otomatis.

Header: Authorization: Bearer qirim_sec_...
Atau: x-client-secret: qirim_sec_...
Tier 2: Device Level
Device API Key

Format: qirim_live_...

Dibuat khusus untuk setiap line WhatsApp terdaftar. Digunakan untuk mengirim pesan, media, OTP, broadcast dari nomor WhatsApp spesifik tersebut.

Header: Authorization: Bearer qirim_live_...
Atau: x-api-key: qirim_live_...
REST API Base URL:
https://qirim.web.id/api/v1
Format Header HTTP Standar:
Authorization: Bearer <CLIENT_SECRET_ATAU_DEVICE_API_KEY>
Content-Type: application/json
Public Health Check & Uptime Heartbeat:

Endpoint publik (tanpa API key) untuk monitoring server uptime (BetterUptime, UptimeKuma) dan cron keep-alive:

GET https://qirim.web.id/api/v1/health
# Response (200 OK): {"status": true, "engine": "qirim by alula", "timestamp": "2026-10-06T08:50:00.000Z"}
POST

/api/v1/send-message

Kirim pesan teks personal atau grup WhatsApp dengan simulasi mengetik manusia (humanized delay) dan dukungan variasi teks Spintax {Halo|Hai|Selamat Pagi}.

Request Body (JSON):
Parameter Tipe Wajib Keterangan
target String Ya Nomor WhatsApp tujuan (contoh: 081234567890 atau 6281234567890) atau ID Grup (...-group@g.us).
message String Ya Isi pesan teks. Mendukung Spintax {Halo|Hai} Kak.
curl -X POST https://qirim.web.id/api/v1/send-message \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "message": "{Halo|Hai} Kak! Pesanan Anda telah kami terima dan sedang diproses." }'
<?php use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/send-message', [ 'target' => '081234567890', 'message' => 'Halo Kak, tagihan Anda sebesar Rp 150.000 telah terbit.', ]); return $response->json();
const axios = require('axios'); const res = await axios.post('https://qirim.web.id/api/v1/send-message', { target: '081234567890', message: 'Hello from Node.js!' }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log(res.data);
import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "target": "081234567890", "message": "Halo dari Python script!" } response = requests.post("https://qirim.web.id/api/v1/send-message", json=payload, headers=headers) print(response.json())
Response Sukses (200 OK):
{
  "status": true,
  "message": "Message sent successfully",
  "data": {
    "id": "3EB09F2184912...",
    "target": "081234567890",
    "device_id": 1,
    "timestamp": "2026-09-06T09:00:00.000Z"
  }
}
POST

/api/v1/send-media

/send-image /send-document /send-audio /send-video

QIRIM mendukung pengiriman semua jenis lampiran media WhatsApp (Gambar JPG/PNG/WebP, Dokumen PDF/DOCX/XLSX/ZIP/CSV/TXT, Audio & Voice Note PTT, Video MP4). API mendukung dua mode pengiriman yang fleksibel:

Mode A: Direct File Upload (Multipart Form-Data)

Upload file langsung dari server atau aplikasi lokal Anda menggunakan header Content-Type: multipart/form-data dengan nama field file atau media (Maksimal 50MB).

Mode B: Remote URL / Base64 Data URI (JSON)

Kirim URL file publik yang dapat diunduh (misal: https://domain.com/faktur.pdf) atau Data URI Base64 menggunakan header Content-Type: application/json.

Tabel Parameter Request:
Parameter Tipe Wajib Keterangan
target / phone String Ya Nomor WhatsApp tujuan (contoh: 081234567890 atau 6281234567890) atau Group ID.
file / url / media_url File / String Ya File biner yang diunggah via form-data (field file) ATAU URL file publik / Data URI Base64.
type String Opsional Jenis media: image, document, file, audio, voice, video.
(Otomatis dideteksi dari ekstensi file jika tidak dicantumkan).
caption / message String Opsional Teks keterangan yang menyertai file media. Mendukung Spintax {Halo|Hai}.
filename String Opsional Nama tampilan file saat dikirim sebagai dokumen (contoh: Invoice-INV-202609.pdf).
ptt / is_voice Boolean Opsional Set true jika audio ingin dikirimkan sebagai Voice Note (PTT) dengan ikon mikrofon hijau.
Contoh 1: Upload File Langsung (Multipart Form-Data)
# 1. Kirim Dokumen PDF / Invoice Langsung via Upload curl -X POST https://qirim.web.id/api/v1/send-document \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "target=081234567890" \ -F "file=@/path/to/invoice.pdf" \ -F "filename=Invoice-INV-2026.pdf" \ -F "caption=Halo Kak, berikut lampiran invoice resmi pesanan Anda." # 2. Kirim Foto / Gambar Langsung via Upload curl -X POST https://qirim.web.id/api/v1/send-image \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "target=081234567890" \ -F "file=@/path/to/promo.jpg" \ -F "caption=Promo Spesial Hari Ini! Diskon hingga 50%."
Contoh 2: Kirim Media via Remote URL / JSON
curl -X POST https://qirim.web.id/api/v1/send-media \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "type": "document", "url": "https://domain.com/downloads/slip-gaji-september.pdf", "filename": "Slip_Gaji_September.pdf", "caption": "Lampiran slip gaji karyawan periode September 2026." }'
Contoh Laravel Http Client (File Upload & Remote URL)
<?php use Illuminate\Support\Facades\Http; // Contoh A: Upload File Lokal (PDF Invoice) $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->attach( 'file', file_get_contents(storage_path('app/invoices/inv-001.pdf')), 'Invoice-001.pdf' )->post('https://qirim.web.id/api/v1/send-document', [ 'target' => '081234567890', 'caption' => 'Terima kasih atas pembayaran Anda!', ]); return $response->json(); // Contoh B: Kirim Gambar via URL Publik $resImg = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/send-image', [ 'target' => '081234567890', 'url' => 'https://domain.com/images/banner.png', 'caption' => 'Katalog Produk Terbaru 2026', ]);
Contoh Native PHP cURL
<?php $ch = curl_init(); $filePath = realpath('slip-gaji.pdf'); $postData = [ 'target' => '081234567890', 'file' => new CURLFile($filePath, 'application/pdf', 'Slip-Gaji.pdf'), 'caption' => 'Slip Gaji Resmi Anda', 'filename' => 'Slip-Gaji.pdf' ]; curl_setopt($ch, CURLOPT_URL, 'https://qirim.web.id/api/v1/send-document'); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer YOUR_API_KEY' ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); curl_close($ch); echo $result;
Contoh Node.js Axios (File Upload & JSON URL)
const axios = require('axios'); const fs = require('fs'); const FormData = require('form-data'); // 1. Upload File Lokal (Dokumen / PDF / Gambar) async function sendLocalDocument() { const form = new FormData(); form.append('target', '081234567890'); form.append('caption', 'Berikut file laporan bulanan.'); form.append('filename', 'Laporan-Q3.pdf'); form.append('file', fs.createReadStream('./laporan.pdf')); const res = await axios.post('https://qirim.web.id/api/v1/send-document', form, { headers: { ...form.getHeaders(), 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log(res.data); } // 2. Kirim Gambar / Voice Note via Remote URL async function sendRemoteMedia() { const res = await axios.post('https://qirim.web.id/api/v1/send-media', { target: '081234567890', type: 'image', url: 'https://images.unsplash.com/photo-1579546929518-9e396f3cc809', caption: 'Foto pemandangan indah!' }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log(res.data); }
Contoh Python Requests (Multipart Upload & JSON Payload)
import requests API_KEY = "YOUR_API_KEY" headers = {"Authorization": f"Bearer {API_KEY}"} # 1. Upload File Lokal Langsung (PDF / Word / Excel / Image) with open("invoice.pdf", "rb") as f: files = {"file": ("Invoice-2026.pdf", f, "application/pdf")} data = { "target": "081234567890", "caption": "Halo Kak, ini invoice pesanan Anda." } res = requests.post("https://qirim.web.id/api/v1/send-document", headers=headers, data=data, files=files) print(res.json()) # 2. Kirim Voice Note PTT via URL voice_payload = { "target": "081234567890", "type": "voice", "url": "https://domain.com/audio/greeting.mp3", "ptt": True } res_voice = requests.post("https://qirim.web.id/api/v1/send-voice", headers=headers, json=voice_payload) print(res_voice.json())
Response Sukses (200 OK):
{
  "status": true,
  "message": "DOCUMENT sent successfully",
  "data": {
    "id": "3EB09F2184912...",
    "target": "081234567890",
    "type": "document",
    "media": "Invoice-INV-2026.pdf",
    "filename": "Invoice-INV-2026.pdf",
    "device_id": 1,
    "timestamp": "2026-09-06T09:00:00.000Z"
  }
}
POST

/api/v1/send-location

Kirim pin koordinat lokasi Google Maps langsung ke ruang obrolan WhatsApp pelanggan.

curl -X POST https://qirim.web.id/api/v1/send-location \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "latitude": -6.200000, "longitude": 106.816666, "name": "Kantor Pusat", "address": "Jl. Jenderal Sudirman No. 1, Jakarta" }'
POST

/api/v1/send-contact

Kirim kartu kontak (vCard) interaktif yang bisa langsung disimpan ke buku kontak penerima dengan satu ketukan.

curl -X POST https://qirim.web.id/api/v1/send-contact \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "contact_name": "Customer Support", "contact_phone": "628999888777" }'
POST

/api/v1/send-template

Kirim pesan menggunakan template tersimpan dengan penggantian variabel dinamis seperti {name}, {order_id}, {total}.

curl -X POST https://qirim.web.id/api/v1/send-template \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "template_name": "Konfirmasi Pembayaran", "variables": { "name": "Budi Santoso", "order_id": "INV-2026-001", "total": "Rp 250.000" } }'
POST

/api/v1/broadcast

Kirim pesan massal ke banyak nomor sekaligus secara asynchronous di background dengan proteksi interval pengiriman cerdas (rate limiting).

curl -X POST https://qirim.web.id/api/v1/broadcast \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "recipients": ["081234567890", "081234567891", "081234567892"], "message": "{Halo|Hai} Pelanggan setia, nikmati promo spesial hari ini!", "delay_seconds": 3 }'
POST

/api/v1/schedule-message

Jadwalkan pengiriman pesan di masa depan (one-time) atau berulang otomatis (daily, weekly, monthly).

curl -X POST https://qirim.web.id/api/v1/schedule-message \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "message": "Pengingat: Tagihan bulanan Anda jatuh tempo besok.", "schedule_time": "2026-09-15 09:00:00", "recurring_type": "monthly" }'
Endpoint Pesan Terjadwal Lainnya:
Method & Endpoint Fungsi
GET /api/v1/scheduled Mendapatkan daftar seluruh pesan terjadwal akun Anda.
DELETE /api/v1/scheduled/:id Membatalkan atau menghapus antrean jadwal pesan.
POST

/api/v1/otp/send & /api/v1/otp/verify

/api/v1/2fa/send /api/v1/2fa/verify /send-otp /verify-otp

Engine One-Time Password (OTP) & Two-Factor Authentication (2FA) resmi bawaan Qirim. Menghasilkan token angka kriptografis acak (secure CSPRNG), pembatasan durasi masa berlaku (TTL countdown), verifikasi Single-Use Token (anti replay-attack), serta dukungan kustomisasi pesan template dengan variabel {otp} dan {expiry}.

Langkah 1
Generate & Kirim Kode OTP / 2FA

Endpoint: POST /api/v1/otp/send atau POST /api/v1/2fa/send

Tabel Parameter Request Body (JSON):
Parameter Tipe Wajib Keterangan
target / phone String Ya Nomor WhatsApp tujuan penerima OTP (contoh: 081234567890 atau 6281234567890).
length / digits Integer Opsional Panjang digit kode OTP (default: 6 digit, rentang: 4 - 8 digit).
expiry_minutes / expiry Integer Opsional Masa berlaku kode dalam menit (default: 5 menit).
type String Opsional Tipe kode: otp atau 2fa (default: otp).
template String Opsional Format teks pesan kustom. Gunakan placeholder {otp} untuk kode dan {expiry} untuk durasi menit.
curl -X POST https://qirim.web.id/api/v1/otp/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "length": 6, "expiry_minutes": 5, "type": "2fa", "template": "🔒 [2FA] Kode rahasia login Anda: {otp}. Berlaku {expiry} menit. Jangan berikan ke siapapun!" }'
<?php use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/otp/send', [ 'target' => '081234567890', 'length' => 6, 'expiry_minutes' => 5, 'type' => 'otp' ]); return $response->json();
const axios = require('axios'); const res = await axios.post('https://qirim.web.id/api/v1/otp/send', { target: '081234567890', length: 6, expiry_minutes: 5, type: '2fa' }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log(res.data);
import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "target": "081234567890", "length": 6, "expiry_minutes": 5, "type": "otp" } response = requests.post("https://qirim.web.id/api/v1/otp/send", json=payload, headers=headers) print(response.json())
Contoh Response Kirim Sukses (200 OK):
{
  "status": true,
  "message": "2FA sent successfully to WhatsApp target.",
  "data": {
    "otp_id": 42,
    "target": "081234567890",
    "type": "2fa",
    "expires_at": "2026-09-18T08:35:00.000Z",
    "message_id": "3EB0B8E7A12..."
  }
}
Langkah 2
Verifikasi & Validasi Kode OTP / 2FA

Endpoint: POST /api/v1/otp/verify atau POST /api/v1/2fa/verify

Tabel Parameter Request Body (JSON):
Parameter Tipe Wajib Keterangan
target / phone String Ya Nomor WhatsApp pengguna yang diverifikasi (contoh: 081234567890).
otp_code / code / otp String Ya Kode angka yang diterima dan diinput oleh user (contoh: "849201").
curl -X POST https://qirim.web.id/api/v1/otp/verify \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target": "081234567890", "otp_code": "849201" }'
<?php use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/otp/verify', [ 'target' => '081234567890', 'otp_code' => $request->input('otp_code'), ]); if ($response->successful()) { // OTP VALID -> Berhasil Login / Verifikasi return redirect('/dashboard'); } else { // OTP SALAH / KADALUARSA return back()->withErrors(['otp' => $response->json('message')]); }
const axios = require('axios'); try { const res = await axios.post('https://qirim.web.id/api/v1/otp/verify', { target: '081234567890', otp_code: req.body.otp_code }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log('OTP Valid!', res.data); } catch (err) { console.error('OTP Invalid or Expired:', err.response?.data?.message); }
import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "target": "081234567890", "otp_code": "849201" } response = requests.post("https://qirim.web.id/api/v1/otp/verify", json=payload, headers=headers) data = response.json() if response.status_code == 200 and data.get("status"): print("Verifikasi Berhasil:", data["data"]) else: print("Gagal:", data.get("message"))
Response Verifikasi Berhasil (200 OK):
{
  "status": true,
  "message": "OTP verified successfully.",
  "data": {
    "otp_id": 42,
    "target": "081234567890",
    "type": "2fa",
    "verified_at": "2026-09-18T08:31:15.000Z"
  }
}
Response Gagal / Expired (400 Bad Request):
{
  "status": false,
  "message": "Invalid OTP code or already used."
}
// ATAU jika sudah lewat masa berlaku:
{
  "status": false,
  "message": "OTP code has expired. Please request a new one."
}
Fitur Keamanan Baru
Proteksi Anti-Fraud & Penangguhan Otomatis (24-Hour Instant Lockout)

Setiap pesan OTP & 2FA yang dikirimkan dapat menyertakan token laporan {report_link}. Jika pemilik nomor mengeklik tautan tersebut karena tidak merasa melakukan login/registrasi, sistem Qirim akan segera membatalkan semua OTP aktif dan membekukan nomor tersebut selama 24 jam dari segala permintaan OTP dan login baru.

Response Akun Terkunci (423 Locked):
{
  "status": false,
  "locked": true,
  "remaining_hours": "23.8",
  "expires_at": "2026-09-19T14:30:00.000Z",
  "message": "⛔ Permintaan OTP / Login ke nomor 081234567890 telah DITANGGUHKAN sementara selama 24 jam demi keamanan akun karena adanya laporan insiden keamanan."
}
Response Rate Limited / Anti-Spam (429):
{
  "status": false,
  "rate_limited": true,
  "reason": "burst_limit",
  "message": "Terlalu banyak permintaan OTP dalam waktu singkat (3/3). Harap tunggu 15 menit sebelum meminta kode baru demi keamanan."
}
Endpoint Pemeriksaan Status Kunci & Laporan Keamanan via API:
Method & Endpoint Fungsi Contoh Payload / Query
GET /api/v1/security/lockouts Memeriksa apakah suatu nomor sedang dalam status penangguhan / terkunci 24 jam. ?phone=081234567890
POST /api/v1/security/report-incident Membekukan nomor atau memproses token laporan keamanan secara programmatic. {"phone": "081234567890", "reason": "Fraud suspected"}

10. Manajemen Perangkat & Hubungkan WhatsApp via API

Integrasi manajemen perangkat secara headless dari aplikasi/web Anda sendiri (render QR Code di website Anda, generate pairing code, atau buat perangkat baru via API).

Method & Endpoint Fungsi Payload / Keterangan
GET /api/v1/devices Daftar semua perangkat akun Mengembalikan status live gateway dan sisa kuota tiap perangkat.
POST /api/v1/devices Buat perangkat baru via API Body: {"name": "Device Kasir", "webhook_url": "https://..."}
GET /api/v1/device/status Status perangkat aktif Status koneksi (connected, scan_qr, disconnected) & kuota.
GET /api/v1/device/qr Dapatkan QR Code Base64 Mengembalikan qr_image_url (Data URL) untuk disematkan langsung di <img src="..."> web Anda.
POST /api/v1/device/pairing-code Generate Pairing Code 8 digit Body: {"phone": "081234567890"} untuk koneksi WhatsApp via nomor telepon.
POST /api/v1/device/regenerate-key Regenerate API Key Menghasilkan API Key baru secara instan dan menonaktifkan key lama.
POST /api/v1/device/disconnect Disconnect / Logout Session Memutus koneksi socket WhatsApp dan membersihkan session auth.
POST /api/v1/device/restart Restart Socket (Zero Unlink) Memperbarui koneksi socket WhatsApp secara instan tanpa logout/unpair dan tanpa perlu scan QR ulang. Memulihkan socket zombie/stalled dan memperpanjang sewa saluran channel. (Mendukung alias: /devices/:id/restart, /device/reconnect, /devices/:id/reconnect).
POST /api/v1/webhook/set Set / Update Webhook URL Body: {"webhook_url": "https://...", "channel_jid": "...", "channel_name": "..."}. Mengatur endpoint webhook perangkat sekaligus mendaftarkan pemantauan saluran jika disertakan.
Contoh Integrasi Buat Device, Ambil QR & Restart Socket di Web Anda (via Client Secret):
# 1. Buat Device Baru curl -X POST https://qirim.web.id/api/v1/devices \ -H "Authorization: Bearer YOUR_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{"name": "WhatsApp Web User", "webhook_url": "https://mysite.com/webhook"}' # 2. Ambil QR Code Base64 untuk ditampilkan di Web Frontend curl -X GET https://qirim.web.id/api/v1/devices/1/qr \ -H "Authorization: Bearer YOUR_CLIENT_SECRET" # 3. Atau Minta Pairing Code 8-Digit via Nomor WhatsApp curl -X POST https://qirim.web.id/api/v1/devices/1/pairing-code \ -H "Authorization: Bearer YOUR_CLIENT_SECRET" \ -H "Content-Type: application/json" \ -d '{"phone": "6281234567890"}' # 4. Restart / Power-Cycle Socket WhatsApp (Tanpa Unlink / Tanpa Scan QR) curl -X POST https://qirim.web.id/api/v1/devices/1/restart \ -H "Authorization: Bearer YOUR_CLIENT_SECRET"
<?php use Illuminate\Support\Facades\Http; // 1. Mengambil QR Code Base64 untuk tag <img src="..."> $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_CLIENT_SECRET'), ])->get('https://qirim.web.id/api/v1/devices/' . $deviceId . '/qr'); $data = $response->json(); if ($data['status'] && !empty($data['qr_image_url'])) { echo '<img src="' . $data['qr_image_url'] . '" alt="Scan QR WA" />'; } // 2. Restart / Reconnect Socket Tanpa Logout (Auto-Recovery) $restartRes = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_CLIENT_SECRET'), ])->post('https://qirim.web.id/api/v1/devices/' . $deviceId . '/restart');
const axios = require('axios'); // 1. Buat device dan inisialisasi sesi WhatsApp const createRes = await axios.post('https://qirim.web.id/api/v1/devices', { name: 'Device Kasir Toko' }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_CLIENT_SECRET } }); const deviceId = createRes.data.data.id; // 2. Ambil data QR Code Data URL const qrRes = await axios.get(`https://qirim.web.id/api/v1/devices/${deviceId}/qr`, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_CLIENT_SECRET } }); console.log('QR Data URL:', qrRes.data.qr_image_url); // 3. Restart / Reconnect Socket Tanpa Logout (Zero-Unlink) const restartRes = await axios.post(`https://qirim.web.id/api/v1/devices/${deviceId}/restart`, {}, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_CLIENT_SECRET } }); console.log('Restart Socket:', restartRes.data.message);
import requests headers = { "Authorization": "Bearer YOUR_CLIENT_SECRET", "Content-Type": "application/json" } # 1. Ambil QR code untuk device ID tertentu resp = requests.get("https://qirim.web.id/api/v1/devices/1/qr", headers=headers) data = resp.json() print("Status Koneksi:", data.get("connection_status")) print("QR Data URL:", data.get("qr_image_url")) # 2. Restart socket perangkat tanpa logout restart_resp = requests.post("https://qirim.web.id/api/v1/devices/1/restart", headers=headers) print("Restart Result:", restart_resp.json().get("message"))
Contoh Response Get QR Code via API:
{
  "status": true,
  "device_id": 1,
  "connection_status": "scan_qr",
  "is_connected": false,
  "qr_image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
  "pairing_code": null,
  "message": "QR code generated. Display qr_image_url as <img src=\"...\">"
}
Contoh Response Restart / Reconnect Socket via API:
{
  "status": true,
  "message": "Device #1 (Device Kasir) session restarted successfully. Reconnecting socket in background without unlinking.",
  "device_id": 1,
  "connection_status": "reconnecting"
}

11. Phonebook Contacts & Grouping API

Kelola buku kontak dan pengelompokan pelanggan Anda via REST API.

Method & Endpoint Keterangan
GET /api/v1/contacts Ambil daftar kontak (mendukung filter ?group_id=...).
POST /api/v1/contacts Tambah kontak: {"name": "Budi", "phone": "081234567890", "group_id": 1, "custom_var1": "VIP"}.
DELETE /api/v1/contacts/:id Hapus kontak berdasarkan ID.
GET /api/v1/groups Ambil daftar grup kontak dan jumlah anggota tiap grup.
POST /api/v1/groups Tambah grup kontak baru: {"name": "Pelanggan VIP", "description": "Grup prioritas"}.
DELETE /api/v1/groups/:id Hapus grup kontak berdasarkan ID.

12. Auto-Reply Chatbot & AI Rules API

Kelola aturan respon otomatis bot WhatsApp (Teks Statis / Template Spintax maupun Dynamic AI Custom Prompt).

Method & Endpoint Keterangan & Contoh Payload
GET /api/v1/autoreply Ambil daftar seluruh aturan auto-reply pada perangkat ini.
POST /api/v1/autoreply Tambah aturan Teks Statis atau AI Prompt:
// Contoh 1: Aturan Teks Statis dengan Spintax & Variabel
{
  "keyword": "info produk",
  "response": "{Halo|Hai} {name}, katalog produk kami ada di https://katalog.web.id",
  "match_type": "contains", // exact, contains, starts_with
  "reply_type": "text",
  "is_default": 0
}

// Contoh 2: Aturan AI Custom Prompt
{
  "keyword": "tanya ai",
  "reply_type": "ai_prompt",
  "ai_custom_prompt": "Jelaskan paket hosting Qirim dan pandu user memilih paket yang sesuai secara ramah.",
  "match_type": "starts_with"
}
DELETE /api/v1/autoreply/:id Hapus aturan auto-reply berdasarkan ID.

13. Message Templates API

Kelola template pesan siap pakai untuk kampanye dan notifikasi.

Method & Endpoint Keterangan & Contoh Payload
GET /api/v1/templates Dapatkan daftar seluruh template pesan tersimpan.
POST /api/v1/templates Buat template baru:
{
  "name": "Invoice Notifikasi",
  "category": "billing",
  "content": "Halo {name}, invoice #{order_id} sebesar {total} sudah diterbitkan."
}
DELETE /api/v1/templates/:id Hapus template pesan berdasarkan ID.
GET

/api/v1/account

Cek profil akun, paket langganan aktif, fitur yang diizinkan oleh paket, dan akumulasi penggunaan kuota pesan.

curl -X GET https://qirim.web.id/api/v1/account \ -H "Authorization: Bearer YOUR_API_KEY"

15. Webhooks & Event Payloads

REAL-TIME ENGINE

Qirim mengirimkan notifikasi HTTP POST real-time ke URL Webhook yang Anda daftarkan di perangkat setiap kali terjadi pesan masuk, postingan saluran baru, status story, maupun perubahan koneksi perangkat.

Keamanan Webhook & Verifikasi Tanda Tangan HMAC-SHA256:
Setiap pengiriman webhook menyertakan signature HMAC-SHA256 yang dihitung dari raw body JSON menggunakan Device API Key Anda sebagai shared secret.
  • X-Hub-Signature-256: sha256=<hmac_hex_digest>
  • X-Webhook-Token: <YOUR_DEVICE_API_KEY>
  • X-Api-Key: <YOUR_DEVICE_API_KEY>
Contoh Verifikasi Signature Webhook (PHP & Node.js):
<?php // Verifikasi Webhook di PHP / Laravel $payload = file_get_contents('php://input'); $signatureHeader = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? ''; $secret = env('QIRIM_API_KEY'); // Device API Key $expectedSig = 'sha256=' . hash_hmac('sha256', $payload, $secret); if (!hash_equals($expectedSig, $signatureHeader)) { http_response_code(401); exit('Invalid Webhook Signature'); } $data = json_decode($payload, true); // Selalu kirimkan HTTP 200 OK dengan cepat http_response_code(200); echo json_encode(['status' => true]);
const crypto = require('crypto'); app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-hub-signature-256'] || ''; const secret = process.env.QIRIM_API_KEY; const hmac = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex'); if (signature !== hmac) { return res.status(401).send('Unauthorized signature'); } const payload = JSON.parse(req.body.toString()); console.log('Event received:', payload.event, payload.data); // Return HTTP 200 segera untuk menghindari timeout gateway (12 detik) res.status(200).json({ ok: true }); });
Event: messages.received (Pesan 1-on-1 Masuk)
Inbound Chat
{
  "event": "messages.received",
  "timestamp": "2026-10-06T08:30:00.000Z",
  "data": {
    "device_id": 1,
    "device_name": "CS Support Utama",
    "sender": "6281234567890",
    "message": "Halo, apakah stok barang ini masih tersedia?",
    "type": "text",
    "is_group": false,
    "timestamp": 1728203400
  }
}
Event: channel.post_received (Postingan Saluran WhatsApp Masuk)
@newsletter

Dikirim saat saluran WhatsApp (@newsletter) yang dipantau mempublikasikan postingan baru (ditangkap via live update WebSocket maupun endpoint fetch-messages):

{
  "event": "channel.post_received",
  "timestamp": "2026-10-06T08:35:12.000Z",
  "data": {
    "device_id": 1,
    "device_name": "Device KAI Updates",
    "channel_jid": "120363198765432@newsletter",
    "channel_name": "KAI Access Channel",
    "message": "Informasi Perjalanan: Update Keterlambatan KA 202 per 06 Okt 2026.",
    "type": "text",
    "media_url": null,
    "timestamp": 1728203712
  }
}
Event: status.received (Status / Story WhatsApp Kontak Masuk)
Story / Status
{
  "event": "status.received",
  "timestamp": "2026-10-06T08:36:00.000Z",
  "data": {
    "device_id": 1,
    "device_name": "CS Support Utama",
    "sender": "6289876543210",
    "message": "Promo flash sale berlangsung hingga malam ini!",
    "type": "text",
    "media_url": null,
    "timestamp": 1728203760
  }
}
Event: messages.failed (Pengiriman Pesan Gagal)
Failure Notice
{
  "event": "messages.failed",
  "timestamp": "2026-10-06T08:37:00.000Z",
  "data": {
    "device_id": 1,
    "target": "6281234567890",
    "error": "Number not registered on WhatsApp or connection timeout",
    "timestamp": 1728203820
  }
}
Event: device.connected & device.disconnected
Lifecycle
{
  "event": "device.connected",
  "timestamp": "2026-10-06T08:40:00.000Z",
  "data": {
    "device_id": 1,
    "phone": "6281234567890",
    "status": "connected",
    "timestamp": "2026-10-06T08:40:00.000Z"
  }
}
Ketentuan & Rekomendasi Teknis Webhook:
  • Timeout 12 Detik: Server webhook Anda harus merespon dengan status HTTP 200 OK dalam waktu < 12 detik. Jika perlu proses berat (misal AI atau query panjang), masukkan ke job queue / background worker di server Anda.
  • Proteksi SSRF: QIRIM secara otomatis menolak pengiriman ke IP lokal/loopback (127.0.0.1, 10.x.x.x, 192.168.x.x) demi keamanan infrastruktur multi-tenant. Gunakan domain publik dengan HTTPS aktif.
  • Idempotensi: Simpan ID pesan atau kombinasi timestamp + channel_jid untuk menghindari duplikasi pemrosesan data.
POST

/api/v1/check-message-safety

/api/v1/check_message_safety

Scanner Pre-flight bertenaga AI Smart Engine & Heuristic Delivery Safety Rules untuk menganalisis risiko kepatuhan format pesan sebelum mengirim broadcast massal atau kampanye. Mendeteksi URL shortener mencurigakan, kata kunci pemicu spam/judol/urgensi agresif, huruf kapital berlebih, serta memberikan skor risiko (0 - 100), rekomendasi mitigasi, dan variasi pesan aman otomatis (Spintax).

Tabel Parameter Request Body (JSON):
Parameter Tipe Wajib Keterangan
message / text String Ya Teks isi pesan WhatsApp yang akan diuji tingkat keamanan konten dan kepatuhannya.
curl -X POST https://qirim.web.id/api/v1/check-message-safety \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "BURUAN KLIK LINK INI SEKARANG JUGA http://bit.ly/hadiah-gratis KLAIM 500RB TANPA SYARAT DEPO CEPAT!!!" }'
<?php use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/check-message-safety', [ 'message' => 'Halo {name}, dapatkan diskon 20% khusus hari ini melalui website resmi kami.', ]); $safety = $response->json(); if ($safety['data']['isBannable']) { // Pesan berisiko tinggi kena banned echo "Peringatan: Skor risiko " . $safety['data']['riskScore'] . "/100"; } else { // Pesan aman untuk dikirim echo "Pesan Aman! Level: " . $safety['data']['riskLevel']; }
const axios = require('axios'); const res = await axios.post('https://qirim.web.id/api/v1/check-message-safety', { message: 'Halo Kak {name}, pesanan Anda sudah kami kirim dengan resi JNE 12345.' }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log('Skor Risiko Ban:', res.data.data.riskScore); console.log('Rekomendasi AI:', res.data.data.suggestions);
import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "message": "PROMO TERBATAS HARI INI SAJA! HUBUNGI KAMI DI WA." } response = requests.post("https://qirim.web.id/api/v1/check-message-safety", json=payload, headers=headers) data = response.json() print("Risk Level:", data["data"]["riskLevel"]) print("Detected Issues:", data["data"]["detectedIssues"])
Format Response Analisis Keamanan (200 OK):
{
  "status": true,
  "data": {
    "riskScore": 65,
    "riskLevel": "HIGH",
    "isBannable": true,
    "detectedIssues": [
      "Menggunakan URL shortener (bit.ly) yang sering ditandai sebagai phishing oleh WhatsApp",
      "Huruf kapital berlebih (> 30%) memicu filter spam WhatsApp",
      "Kata urgensi agresif: BURUAN, SEKARANG JUGA, KLAIM GRATIS"
    ],
    "suggestions": [
      "Ganti shortlink bit.ly dengan domain website resmi Anda sendiri",
      "Gunakan huruf kecil normal dengan kapitalisasi kalimat yang wajar",
      "Gunakan format Spintax {Halo|Hai} untuk memvariasikan teks broadcast"
    ],
    "safeAlternative": "{Halo|Hai} Kak {name}, berikut informasi penawaran spesial untuk Anda di website resmi kami: https://example.com"
  }
}

17. API Saluran (@newsletter) & Grup WhatsApp (@g.us)

COMMUNITY HUB v1.0

Kelola dan ambil daftar Saluran WhatsApp (@newsletter) serta Grup WhatsApp (@g.us) secara otomatis melalui REST API, dapatkan data subscriber/member terkini, dan publikasikan siaran pengumuman berformat teks maupun media secara langsung.

GET Ambil Daftar Saluran WhatsApp

GET https://qirim.web.id/api/v1/channels atau GET https://qirim.web.id/api/v1/channels/list

Mengembalikan seluruh Saluran WhatsApp yang terhubung dengan akun/perangkat. Tambahkan parameter <code>?refresh=true</code> untuk memicu sinkronisasi langsung dari WhatsApp.

Tabel Parameter URL (Query String):
Parameter Tipe Wajib Keterangan
device_id Integer / String Opsional ID Device WhatsApp spesifik. Jika kosong, sistem otomatis memakai device aktif pertama.
refresh Boolean (true/1) Opsional Jika true, engine akan query langsung ke WhatsApp untuk menyinkronkan subscriber dan saluran baru.
curl -X GET "https://qirim.web.id/api/v1/channels?refresh=true" -H "Authorization: Bearer YOUR_API_KEY"
<?php use IlluminateSupportFacadesHttp; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->get('https://qirim.web.id/api/v1/channels', [ 'refresh' => 'true' ]); $channels = $response->json(); foreach ($channels['data']['channels'] as $ch) { echo $ch['name'] . " (" . $ch['jid'] . ") - " . $ch['subscribers'] . " subscribers\n"; }
const axios = require('axios'); const res = await axios.get('https://qirim.web.id/api/v1/channels', { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY }, params: { refresh: 'true' } }); console.log('Total Saluran:', res.data.data.total); console.log(res.data.data.channels);
import requests headers = { "Authorization": "Bearer YOUR_API_KEY" } response = requests.get("https://qirim.web.id/api/v1/channels", headers=headers, params={"refresh": "true"}) data = response.json() for ch in data.get("data", {}).get("channels", []): print(f"{ch['name']}: {ch['subscribers']} pengikut (JID: {ch['jid']})")
Contoh Response Ambil Saluran (200 OK):
{
  "status": true,
  "message": "Channels retrieved successfully",
  "data": {
    "total": 2,
    "device_id": 1,
    "channels": [
      {
        "id": 1,
        "device_id": 1,
        "jid": "120363144123456789@newsletter",
        "name": "QIRIM Official Updates & Promo",
        "subscribers": 1420,
        "role": "admin",
        "description": "Saluran informasi resmi, changelog, dan promo diskon layanan QIRIM.",
        "last_synced_at": "2026-09-18T23:30:00.000Z"
      }
    ]
  }
}
POST Kirim / Publikasikan Postingan ke Saluran WhatsApp

POST https://qirim.web.id/api/v1/channels/send atau POST https://qirim.web.id/api/v1/channels/post

Tabel Parameter Request Body (JSON / Multipart Form):
Parameter Tipe Wajib Keterangan
channel_jid / target String Ya JID Saluran WhatsApp tujuan (contoh: <code>120363144123456789@newsletter</code>)
message / text String Ya* Isi teks postingan. Mendukung formatting WhatsApp (*tebal*, _miring_, ~coret~).
media_url / file String / File Opsional URL media publik atau unggah file langsung (gambar JPG/PNG, video MP4, dokumen PDF).
media_type String Opsional image | video | document (Otomatis dideteksi sistem).
curl -X POST https://qirim.web.id/api/v1/channels/send -H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" -d '{ "channel_jid": "120363144123456789@newsletter", "message": "*PENGUMUMAN PENTING:* Kami baru saja merilis fitur WhatsApp Channels API! 🚀\n\nKunjungi https://qirim.web.id/docs untuk membaca dokumentasi lengkap.", "media_url": "https://qirim.web.id/assets/images/banner-update.png" }'
<?php use IlluminateSupportFacadesHttp; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/channels/send', [ 'channel_jid' => '120363144123456789@newsletter', 'message' => '*UPDATE TERBARU:* Fitur baru telah aktif di dashboard.', 'media_url' => 'https://example.com/promo.jpg' ]); $result = $response->json(); echo $result['message'];
const axios = require('axios'); const res = await axios.post('https://qirim.web.id/api/v1/channels/send', { channel_jid: '120363144123456789@newsletter', message: 'Halo pengikut setia saluran! Berikut info promo minggu ini.', media_url: 'https://example.com/banner.png' }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log('Post ID:', res.data.data.id);
import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "channel_jid": "120363144123456789@newsletter", "message": "*Info Penting Saluran:* Jadwal pemeliharaan server malam ini jam 00:00 WIB." } res = requests.post("https://qirim.web.id/api/v1/channels/send", json=payload, headers=headers) print(res.json())
POST Tarik Pesan & Histori Saluran WhatsApp On-Demand

POST https://qirim.web.id/api/v1/channels/fetch-messages

Mengambil postingan terkini dari satu atau beberapa Saluran WhatsApp secara aktif langsung dari server WhatsApp. Setiap pesan yang berhasil ditarik otomatis dicatat ke riwayat log database dan didistribusikan ke URL Webhook Anda (event <code>channel.post_received</code>).

Tabel Parameter Request Body (JSON):
Parameter Tipe Wajib Keterangan
channel_jid / target String Ya JID Saluran WhatsApp (contoh: <code>120363144123456789@newsletter</code>). Mendukung banyak JID sekaligus dipisahkan koma.
count Integer Opsional Jumlah pesan terakhir yang ingin ditarik (default: <code>10</code>, maksimal: <code>50</code>).
dispatch_webhook Boolean Opsional Jika true (default: <code>true</code>), setiap pesan baru otomatis diteruskan ke Webhook Anda sebagai event <code>channel.post_received</code>.
channel_name String Opsional Nama alias saluran WhatsApp opsional.
curl -X POST https://qirim.web.id/api/v1/channels/fetch-messages \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel_jid": "120363144123456789@newsletter", "count": 10, "dispatch_webhook": true }'
<?php use Illuminate\Support\Facades\Http; $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . env('QIRIM_API_KEY'), ])->post('https://qirim.web.id/api/v1/channels/fetch-messages', [ 'channel_jid' => '120363144123456789@newsletter', 'count' => 15, 'dispatch_webhook' => true ]); $data = $response->json(); echo "Berhasil mengambil " . $data['total'] . " pesan saluran.\n"; foreach ($data['data'] as $msg) { echo "[" . $msg['timestamp'] . "] " . $msg['message'] . "\n"; }
const axios = require('axios'); const res = await axios.post('https://qirim.web.id/api/v1/channels/fetch-messages', { channel_jid: '120363144123456789@newsletter', count: 10, dispatch_webhook: true }, { headers: { 'Authorization': 'Bearer ' + process.env.QIRIM_API_KEY } }); console.log('Total Pesan:', res.data.total); console.log('Diteruskan ke Webhook:', res.data.dispatched_to_webhook);
import requests headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "channel_jid": "120363144123456789@newsletter", "count": 10, "dispatch_webhook": True } resp = requests.post("https://qirim.web.id/api/v1/channels/fetch-messages", json=payload, headers=headers) data = resp.json() print("Total pesan ditarik:", data.get("total", 0))
Contoh Response Tarik Pesan (200 OK):
{
  "status": true,
  "message": "Fetched 3 messages across 1 channel(s)",
  "total": 3,
  "dispatched_to_webhook": 3,
  "data": [
    {
      "id": "120363144123456789_1728203712",
      "channel_jid": "120363144123456789@newsletter",
      "channel_name": "KAI Access Channel",
      "message": "Informasi Perjalanan: Update Keterlambatan KA 202 per 06 Okt 2026.",
      "type": "text",
      "timestamp": 1728203712
    }
  ]
}
POST & GET Resolve Link Tautan Publik Saluran WhatsApp

POST https://qirim.web.id/api/v1/channels/resolve atau GET https://qirim.web.id/api/v1/channels/resolve?url=...

Menerjemahkan tautan undangan saluran publik WhatsApp (contoh: <code>https://whatsapp.com/channel/0029Va...</code>) menjadi JID permanen <code>@newsletter</code>, nama resmi, total pengikut, dan menyimpannya secara otomatis ke database.

# Contoh cURL: Resolve Tautan Saluran curl -X POST https://qirim.web.id/api/v1/channels/resolve \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://whatsapp.com/channel/0029VaABCD1234efgh" }'
Contoh Response Resolve Saluran (200 OK):
{
  "status": true,
  "message": "Channel berhasil ditemukan!",
  "data": {
    "jid": "120363198765432@newsletter",
    "name": "KAI Access Official",
    "subscribers": 34500,
    "desc": "Saluran resmi informasi operasional kereta api PT Kereta Api Indonesia (Persero)."
  }
}
POST Berlangganan Notifikasi Real-Time Saluran

POST https://qirim.web.id/api/v1/channels/subscribe

Mendaftarkan socket WhatsApp perangkat untuk menerima event live update real-time langsung dari saluran WhatsApp tersebut.

# Contoh cURL: Subscribe Saluran curl -X POST https://qirim.web.id/api/v1/channels/subscribe \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "channel_jid": "120363198765432@newsletter", "channel_name": "KAI Access Official" }'
POST & GET Pembersihan Pesan Saluran & Penyimpanan (Kebijakan 24 Jam)

POST https://qirim.web.id/api/v1/channels/cleanup atau GET https://qirim.web.id/api/v1/channels/cleanup

Memicu pembersihan pesan Saluran WhatsApp dari database yang lebih lama dari 24 jam (atau sesuai parameter older_than_hours). Secara default, pembersihan ini juga berjalan otomatis setiap menit di latar belakang.

Tabel Parameter Request:
Parameter Tipe Wajib Keterangan
older_than_hours Integer Opsional Batas retensi pesan dalam hitungan jam (default: <code>24</code> jam).
device_id Integer / String Opsional ID Device WhatsApp spesifik.
# Contoh cURL: Pembersihan Manual Pesan Saluran >24 Jam curl -X POST https://qirim.web.id/api/v1/channels/cleanup \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "older_than_hours": 24 }'
Contoh Response Cleanup Saluran (200 OK):
{
  "status": true,
  "message": "Berhasil membersihkan 18 pesan saluran WhatsApp yang lebih lama dari 24 jam.",
  "retention_hours": 24,
  "deleted_message_logs": 18,
  "deleted_community_posts": 0
}
GET & POST Kelola & Kirim Pesan ke Grup WhatsApp (@g.us)

Dapatkan daftar seluruh grup yang diikuti nomor Anda via <code>GET /api/v1/wa-groups</code> dan kirimkan pesan ke grup via <code>POST /api/v1/wa-groups/send</code>.

# 1. Ambil Semua Grup WhatsApp Terhubung curl -X GET "https://qirim.web.id/api/v1/wa-groups?refresh=true" \ -H "Authorization: Bearer YOUR_API_KEY" # 2. Kirim Pesan / Broadcast ke Grup WhatsApp curl -X POST https://qirim.web.id/api/v1/wa-groups/send \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "group_jid": "120363028123456789@g.us", "message": "Halo rekan-rekan grup! Pengingat rapat bulanan hari ini pukul 14:00 WIB." }'