Logo Contoh Integrasi Docs

Contoh Integrasi

Kode siap pakai untuk PHP/Laravel, Node.js, dan Python.


Kode siap pakai untuk menyambungkan aplikasi Anda ke gateway.


PHP / Laravel

Simpan kredensial di .env:

WA_GATEWAY_URL=https://wa.flustra.id
WA_GATEWAY_KEY=fwa_xxxxxxxx.xxxxxxxxxxxx
WA_GATEWAY_SESSION=

Blok itu bisa disalin langsung dari halaman API Keys setelah kunci dibuat. WA_GATEWAY_SESSION boleh tetap kosong — lihat Mulai Cepat.

config/whatsapp.php:

<?php

return [
    'url' => env('WA_GATEWAY_URL', 'https://wa.flustra.id'),
    'key' => env('WA_GATEWAY_KEY'),

    // Kosong = kirim dari nomor yang sedang terhubung. Isi dengan ID sesi
    // hanya kalau workspace Anda punya beberapa nomor.
    'session' => env('WA_GATEWAY_SESSION'),

    // Sengaja pendek. Notifikasi WhatsApp adalah pelengkap, bukan pengganti
    // email — gateway yang lambat tidak boleh menahan permintaan pengguna.
    'timeout' => 5,
];

app/Services/WhatsAppGateway.php:

<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class WhatsAppGateway
{
    /**
     * Selalu mengembalikan bool, tidak pernah melempar exception.
     *
     * Kegagalan mengirim WhatsApp tidak boleh menggagalkan proses yang
     * memanggilnya — invoice tetap harus tersimpan meski notifikasinya gagal.
     */
    public static function kirim(?string $nomor, string $pesan): bool
    {
        if (! $nomor || ! config('whatsapp.key')) {
            return false;
        }

        try {
            $response = Http::baseUrl(rtrim(config('whatsapp.url'), '/'))
                ->withHeader('X-Api-Key', config('whatsapp.key'))
                ->timeout(config('whatsapp.timeout'))
                ->acceptJson()
                ->post('/api/v1/messages/text', [
                    'to' => $nomor,
                    'message' => $pesan,
                ]);
        } catch (\Throwable $e) {
            Log::warning('Gateway WhatsApp tidak bisa dihubungi', ['error' => $e->getMessage()]);

            return false;
        }

        if ($response->successful()) {
            return true;
        }

        Log::warning('Gateway WhatsApp menolak pesan', [
            'status' => $response->status(),
            'error' => $response->json('error.message'),
        ]);

        return false;
    }

    public static function template(?string $nomor, string $template, array $variabel = []): bool
    {
        if (! $nomor || ! config('whatsapp.key')) {
            return false;
        }

        try {
            return Http::baseUrl(rtrim(config('whatsapp.url'), '/'))
                ->withHeader('X-Api-Key', config('whatsapp.key'))
                ->timeout(config('whatsapp.timeout'))
                ->post('/api/v1/messages/template', [
                    'to' => $nomor,
                    'template' => $template,
                    'variables' => $variabel,
                ])
                ->successful();
        } catch (\Throwable $e) {
            Log::warning('Template WhatsApp gagal', ['error' => $e->getMessage()]);

            return false;
        }
    }
}

Pemakaian:

use App\Services\WhatsAppGateway;

WhatsAppGateway::kirim($pelanggan->telepon, "Pesanan #{$pesanan->id} sudah dikirim.");

WhatsAppGateway::template($pelanggan->telepon, 'pengingat-invoice', [
    'nama' => $pelanggan->nama,
    'nomor' => $invoice->nomor,
]);

Lewat antrean Laravel

Untuk pengiriman yang tidak mendesak, jangan panggil langsung di controller — antrekan supaya pengguna tidak menunggu:

class KirimNotifikasiWhatsApp implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly string $nomor,
        public readonly string $pesan,
    ) {}

    public function handle(): void
    {
        WhatsAppGateway::kirim($this->nomor, $this->pesan);
    }
}

KirimNotifikasiWhatsApp::dispatch($pelanggan->telepon, 'Pesanan diterima.');

Node.js

const WA_URL = process.env.WA_GATEWAY_URL ?? 'https://wa.flustra.id';
const WA_KEY = process.env.WA_GATEWAY_KEY;

export async function kirimWhatsApp(nomor, pesan) {
    if (!nomor || !WA_KEY) return false;

    try {
        const res = await fetch(`${WA_URL}/api/v1/messages/text`, {
            method: 'POST',
            headers: {
                'X-Api-Key': WA_KEY,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ to: nomor, message: pesan }),
            signal: AbortSignal.timeout(5000),
        });

        if (!res.ok) {
            const err = await res.json().catch(() => ({}));
            console.warn('Gateway menolak pesan:', err.error?.message ?? res.status);

            return false;
        }

        return true;
    } catch (e) {
        console.warn('Gateway tidak bisa dihubungi:', e.message);

        return false;
    }
}

Python

import os
import logging
import requests

WA_URL = os.getenv("WA_GATEWAY_URL", "https://wa.flustra.id")
WA_KEY = os.getenv("WA_GATEWAY_KEY")


def kirim_whatsapp(nomor: str, pesan: str) -> bool:
    if not nomor or not WA_KEY:
        return False

    try:
        r = requests.post(
            f"{WA_URL}/api/v1/messages/text",
            headers={"X-Api-Key": WA_KEY},
            json={"to": nomor, "message": pesan},
            timeout=5,
        )
    except requests.RequestException as e:
        logging.warning("Gateway tidak bisa dihubungi: %s", e)
        return False

    if not r.ok:
        logging.warning("Gateway menolak pesan: %s", r.text)
        return False

    return True

Mengirim lampiran

PHP

Http::withHeader('X-Api-Key', config('whatsapp.key'))
    ->attach('file', file_get_contents($path), 'invoice.pdf')
    ->post(config('whatsapp.url').'/api/v1/messages/media', [
        'to' => $nomor,
        'caption' => 'Invoice Agustus 2026',
        'type' => 'document',
    ]);

Node.js

const form = new FormData();
form.append('to', nomor);
form.append('caption', 'Invoice Agustus 2026');
form.append('type', 'document');
form.append('file', new Blob([buffer]), 'invoice.pdf');

await fetch(`${WA_URL}/api/v1/messages/media`, {
    method: 'POST',
    headers: { 'X-Api-Key': WA_KEY },
    body: form,
});

Untuk sebagian besar kebutuhan, tidak perlu menunggu — kirim lalu lanjutkan.

Kalau memang perlu memastikan (misalnya kode verifikasi), periksa statusnya:

$id = $response->json('data.id');

for ($i = 0; $i < 30; $i++) {
    sleep(2);

    $status = Http::withHeader('X-Api-Key', $key)
        ->get("{$base}/api/v1/messages/{$id}")
        ->json('data.status');

    if (in_array($status, ['delivered', 'read'], true)) {
        return true;
    }

    if ($status === 'failed') {
        return false;
    }
}

Cara yang lebih hemat: daftarkan webhook message.status dan biarkan kami yang mengabari.


Kesalahan yang sering terjadi

Kesalahan Akibatnya
Menganggap 202 berarti sudah terkirim Status sebenarnya menyusul lewat ID pesan atau webhook
Membuat perulangan sendiri untuk kirim massal Nomor berisiko diblokir. Pakai /messages/bulk
Timeout terlalu panjang Pengguna menunggu lama hanya karena notifikasi
Melempar exception saat gateway gagal Proses utama ikut gagal padahal notifikasi cuma pelengkap
API key ditaruh di kode JavaScript halaman web Siapa pun bisa membacanya lewat Inspect Element
Satu API key untuk semua aplikasi Kunci bocor berarti semua aplikasi harus diganti kuncinya
Tidak memverifikasi tanda tangan webhook Endpoint bisa dikirimi data palsu