WebQRIS API Documentation

Notifikasi QRIS — REST API v3.6
Base URL: https://webqris.com
Authentication: Bearer Token via header Authorization: Bearer YOUR_API_TOKEN
WebSocket: wss://webqris.com/ws (dashboard) | wss://webqris.com/ws/merchant?token=API_TOKEN (merchant)
Ringkasan Create QRIS Check Status Webhook Outbound Webhook APK Callback Notify WebSocket Alur
Ringkasan Integrasi
Aplikasi merchant / backend AndaPanggil POST /api/payments/qris/create dan GET /api/payments/:invoiceId/status dengan Authorization: Bearer YOUR_API_TOKEN.
Server Anda menerima notifikasi dari WebQRISSet Webhook URL di merchant detail. Itu adalah endpoint milik sistem Anda sendiri. WebQRIS akan POST ke sana saat pembayaran sukses.
APK / forwarder mengirim notifikasi ke WebQRISPakai POST /api/webhook/payment atau POST /api/callback/notify dengan Callback Secret. Dua endpoint ini adalah endpoint milik WebQRIS.
Aturan cepat agar tidak tertukar: API Token untuk backend merchant membuat invoice, Webhook URL adalah URL tujuan di server Anda, dan Callback Secret untuk autentikasi notif masuk ke WebQRIS.
Quick Start
  1. Buat merchant di dashboard, lalu generate minimal satu API Token.
  2. Jika ingin sistem Anda menerima status bayar otomatis, isi Webhook URL di merchant detail.
  3. Dari backend merchant Anda, panggil POST /api/payments/qris/create untuk membuat invoice dan tampilkan QR ke pelanggan.
  4. Jika Anda memakai APK Notification Forwarder, arahkan APK ke POST /api/webhook/payment dan isi Callback Secret.
  5. Setelah payment sukses, WebQRIS akan mengirim webhook outbound ke Webhook URL Anda. Poll status via API hanya opsional.
Istilah Penting
API TokenKredensial untuk backend merchant Anda saat membuat invoice QRIS dan cek status via API.
Webhook URLURL tujuan di server Anda sendiri. WebQRIS akan mengirim notifikasi pembayaran sukses ke URL ini.
Callback SecretSecret untuk autentikasi notif masuk ke WebQRIS dari APK atau forwarder lain.
Invoice IDID transaksi dari WebQRIS. Dipakai untuk cek status pembayaran.
merchant_order_idID order dari sistem Anda sendiri. Optional, tapi disarankan agar transaksi mudah dicocokkan.
POST /api/payments/qris/create

Buat transaksi QRIS baru. Akan mengembalikan QRIS payload yang bisa di-generate menjadi QR code.

Gunakan endpoint ini dari aplikasi merchant/server untuk membuat invoice QRIS. Pakai bersama header Authorization: Bearer YOUR_API_TOKEN. Ini bukan URL untuk APK Notification Forwarder dan bukan webhook outbound ke sistem Anda.
Headers
AuthorizationBearer YOUR_API_TOKENRequired
Content-Typeapplication/json
Request Body
{
  "amount": 25000,
  "merchant_order_id": "ORDER-001",
  "customer_name": "John Doe"
}
Contoh cURL
curl -X POST 'https://webqris.com/api/payments/qris/create'   -H 'Authorization: Bearer YOUR_API_TOKEN'   -H 'Content-Type: application/json'   -d '{
    "amount": 25000,
    "merchant_order_id": "ORDER-001",
    "customer_name": "John Doe"
  }'
amountintegerNominal pembayaran (Rupiah)Required
merchant_order_idstringID order dari merchantOptional
customer_namestringNama pelangganOptional
Response (201)
{
  "success": true,
  "invoice_id": "INV-1710000000-abc123",
  "qris_payload": "0002010211...",
  "amount": 25000,
  "unique_code": 42,
  "total_amount": 25042,
  "expired_at": "2026-03-09T10:30:00.000Z"
}
Contoh Framework Backend: Create Invoice
PHP
<?php

  $payload = json_encode([
    'amount' => 25000,
    'merchant_order_id' => 'ORDER-001',
    'customer_name' => 'John Doe',
  ]);

  $ch = curl_init('https://webqris.com/api/payments/qris/create');
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Authorization: Bearer YOUR_API_TOKEN',
      'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => $payload,
  ]);

  $response = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  $data = json_decode($response, true);
JavaScript / Node.js (Express)
app.post('/payments/create-qris', async (req, res) => {
  const response = await fetch('https://webqris.com/api/payments/qris/create', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + process.env.WEBQRIS_API_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: req.body.amount,
      merchant_order_id: req.body.merchant_order_id,
      customer_name: req.body.customer_name,
    }),
  });

  const data = await response.json();
  return res.status(response.status).json(data);
});
React / Next.js (aman via backend sendiri)
// app/api/webqris/create/route.ts
export async function POST(request: Request) {
  const body = await request.json();

  const response = await fetch('https://webqris.com/api/payments/qris/create', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + process.env.WEBQRIS_API_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  return Response.json(await response.json(), { status: response.status });
}

// Komponen React memanggil backend Anda sendiri, bukan WebQRIS langsung.
const resp = await fetch('/api/webqris/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount: 25000, merchant_order_id: 'ORDER-001' }),
});
Laravel
<?php

  namespace AppHttpControllers;

  use IlluminateHttpRequest;
  use IlluminateSupportFacadesHttp;

  class WebqrisPaymentController extends Controller
  {
    public function create(Request $request)
    {
      $response = Http::withToken(config('services.webqris.token'))
        ->post(config('services.webqris.base_url') . '/api/payments/qris/create', [
          'amount' => (int) $request->input('amount'),
          'merchant_order_id' => $request->input('merchant_order_id'),
          'customer_name' => $request->input('customer_name'),
        ]);

      return response()->json($response->json(), $response->status());
    }
  }
CodeIgniter 4
<?php

  namespace AppControllers;

  use CodeIgniterHTTPResponseInterface;
  use ConfigServices;

  class WebqrisPaymentController extends BaseController
  {
    public function create(): ResponseInterface
    {
      $client = Services::curlrequest();
      $response = $client->post(env('webqris.baseUrl') . '/api/payments/qris/create', [
        'headers' => [
          'Authorization' => 'Bearer ' . env('webqris.apiToken'),
          'Content-Type' => 'application/json',
        ],
        'json' => [
          'amount' => (int) $this->request->getJSON(true)['amount'],
          'merchant_order_id' => $this->request->getJSON(true)['merchant_order_id'] ?? null,
          'customer_name' => $this->request->getJSON(true)['customer_name'] ?? null,
        ],
      ]);

      return $this->response
        ->setStatusCode($response->getStatusCode())
        ->setJSON(json_decode($response->getBody(), true));
    }
  }
GET /api/payments/:invoiceId/status

Cek status pembayaran berdasarkan invoice ID.

Headers
AuthorizationBearer YOUR_API_TOKENRequired
Contoh cURL
curl -X GET 'https://webqris.com/api/payments/INV-1710000000-abc123/status'   -H 'Authorization: Bearer YOUR_API_TOKEN'
Response (200)
{
  "success": true,
  "data": {
    "invoice_id": "INV-MERCHANT-1710000000-abc123",
    "merchant_order_id": "ORDER-001",
    "qris_payload": "0002010211...",
    "amount": 25000,
    "unique_code": 42,
    "total_amount": 25042,
    "status": "paid",
    "payment_method": "com.dana.id",
    "paid_at": "2026-03-09T10:15:23.000Z",
    "expired_at": "2026-03-09T10:30:00.000Z"
  }
}
Contoh Framework Backend: Check Status
PHP
<?php

  $invoiceId = 'INV-1710000000-abc123';

  $ch = curl_init('https://webqris.com/api/payments/' . urlencode($invoiceId) . '/status');
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Authorization: Bearer YOUR_API_TOKEN',
    ],
  ]);

  $response = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  $data = json_decode($response, true);
JavaScript / Node.js (Express)
app.get('/payments/:invoiceId/status', async (req, res) => {
  const response = await fetch(
    'https://webqris.com/api/payments/' + encodeURIComponent(req.params.invoiceId) + '/status',
    {
      headers: {
        'Authorization': 'Bearer ' + process.env.WEBQRIS_API_TOKEN,
      },
    }
  );

  const data = await response.json();
  return res.status(response.status).json(data);
});
React / Next.js (aman via backend sendiri)
// app/api/webqris/status/[invoiceId]/route.ts
export async function GET(
  _request: Request,
  { params }: { params: { invoiceId: string } }
) {
  const response = await fetch(
    'https://webqris.com/api/payments/' + encodeURIComponent(params.invoiceId) + '/status',
    {
      headers: {
        'Authorization': 'Bearer ' + process.env.WEBQRIS_API_TOKEN,
      },
      cache: 'no-store',
    }
  );

  return Response.json(await response.json(), { status: response.status });
}

// Komponen React cukup memanggil endpoint backend Anda sendiri.
const resp = await fetch('/api/webqris/status/' + invoiceId);
Laravel
<?php

  namespace AppHttpControllers;

  use IlluminateSupportFacadesHttp;

  class WebqrisStatusController extends Controller
  {
    public function show(string $invoiceId)
    {
      $response = Http::withToken(config('services.webqris.token'))
        ->get(config('services.webqris.base_url') . '/api/payments/' . $invoiceId . '/status');

      return response()->json($response->json(), $response->status());
    }
  }
CodeIgniter 4
<?php

  namespace AppControllers;

  use CodeIgniterHTTPResponseInterface;
  use ConfigServices;

  class WebqrisStatusController extends BaseController
  {
    public function show(string $invoiceId): ResponseInterface
    {
      $client = Services::curlrequest();
      $response = $client->get(env('webqris.baseUrl') . '/api/payments/' . $invoiceId . '/status', [
        'headers' => [
          'Authorization' => 'Bearer ' . env('webqris.apiToken'),
        ],
      ]);

      return $this->response
        ->setStatusCode($response->getStatusCode())
        ->setJSON(json_decode($response->getBody(), true));
    }
  }
POST YOUR_WEBHOOK_URL

WebQRIS akan mengirim webhook ke URL yang Anda konfigurasi saat pembayaran berhasil. Ini adalah endpoint milik sistem Anda sendiri, bukan endpoint milik WebQRIS.

Headers
X-Webhook-SignatureHMAC-SHA256 signature dari body dengan webhook_secret
X-SignatureSama dengan di atas (legacy header, untuk backward compatibility)
Content-Typeapplication/json
User-AgentWebQRIS-Webhook/1.0
Webhook Body
{
  "event": "payment.paid",
  "data": {
    "invoice_id": "INV-MERCHANT-1710000000-abc123",
    "merchant_order_id": "ORDER-001",
    "amount": 25000,
    "unique_code": 42,
    "total_amount": 25042,
    "customer_name": "John Doe",
    "status": "paid",
    "payment_method": "com.dana.id",
    "paid_at": "2026-03-09T10:15:23.000Z"
  }
}
Retry Policy

Webhook akan di-retry hingga jika gagal (HTTP status non-2xx atau timeout). Delay antar retry menggunakan exponential backoff: 2s, 4s, 8s. Timeout per request: 10 detik.

Contoh Receiver Webhook (PHP)
<?php
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, 'YOUR_WEBHOOK_SECRET');

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    echo json_encode(['success' => false, 'message' => 'Invalid signature']);
    exit;
}

$payload = json_decode($body, true);
if (!is_array($payload) || ($payload['event'] ?? '') !== 'payment.paid') {
    http_response_code(400);
    echo json_encode(['success' => false, 'message' => 'Invalid event']);
    exit;
}

$payment = $payload['data'] ?? [];
$invoiceId = $payment['invoice_id'] ?? null;
$merchantOrderId = $payment['merchant_order_id'] ?? null;
$status = $payment['status'] ?? null;

// TODO: cocokan invoice ke database Anda, tandai paid, lalu proses order.

http_response_code(200);
header('Content-Type: application/json');
echo json_encode([
    'success' => true,
    'invoice_id' => $invoiceId,
    'merchant_order_id' => $merchantOrderId,
    'status' => $status,
]);
Contoh Receiver Webhook (Node.js / Express)
const express = require('express');
const crypto = require('crypto');

const app = express();

app.post('/webhook/qris', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'] || '';
  const body = req.body.toString('utf8');
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(body)
    .digest('hex');

  if (signature !== expected) {
    return res.status(401).json({ success: false, message: 'Invalid signature' });
  }

  const payload = JSON.parse(body);
  if (payload.event !== 'payment.paid') {
    return res.status(400).json({ success: false, message: 'Invalid event' });
  }

  const payment = payload.data || {};
  const invoiceId = payment.invoice_id;
  const merchantOrderId = payment.merchant_order_id;
  const status = payment.status;

  // TODO: cocokan invoice ke database Anda, tandai paid, lalu proses order.

  return res.status(200).json({
    success: true,
    invoice_id: invoiceId,
    merchant_order_id: merchantOrderId,
    status,
  });
});
Contoh Receiver Webhook: React / Next.js
import crypto from 'node:crypto';

// app/api/webhook/qris/route.ts
export async function POST(request: Request) {
  const signature = request.headers.get('x-webhook-signature') ?? '';
  const body = await request.text();
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET || '')
    .update(body)
    .digest('hex');

  if (signature !== expected) {
    return Response.json({ success: false, message: 'Invalid signature' }, { status: 401 });
  }

  const payload = JSON.parse(body);
  if (payload.event !== 'payment.paid') {
    return Response.json({ success: false, message: 'Invalid event' }, { status: 400 });
  }

  const payment = payload.data || {};

  // TODO: update order di database Anda.

  return Response.json({
    success: true,
    invoice_id: payment.invoice_id,
    merchant_order_id: payment.merchant_order_id,
    status: payment.status,
  });
}

// React frontend tidak menerima webhook langsung.
// Frontend cukup membaca status dari backend Anda sendiri.
Contoh Receiver Webhook: Laravel
<?php

  use IlluminateHttpRequest;
  use IlluminateSupportFacadesLog;
  use IlluminateSupportFacadesRoute;

  Route::post('/webhook/qris', function (Request $request) {
    $signature = $request->header('X-Webhook-Signature', '');
    $body = $request->getContent();
    $expected = hash_hmac('sha256', $body, env('WEBHOOK_SECRET'));

    if (!hash_equals($expected, $signature)) {
      return response()->json([
        'success' => false,
        'message' => 'Invalid signature',
      ], 401);
    }

    $payload = json_decode($body, true);
    if (($payload['event'] ?? '') !== 'payment.paid') {
      return response()->json([
        'success' => false,
        'message' => 'Invalid event',
      ], 400);
    }

    $payment = $payload['data'] ?? [];

    // TODO: cocokkan invoice/order di database Anda.
    Log::info('WebQRIS payment paid', $payment);

    return response()->json([
      'success' => true,
      'invoice_id' => $payment['invoice_id'] ?? null,
      'merchant_order_id' => $payment['merchant_order_id'] ?? null,
      'status' => $payment['status'] ?? null,
    ]);
  });
Route + Controller Laravel (Versi Terpisah)
// routes/api.php
  use AppHttpControllersWebqrisWebhookController;

  Route::post('/webhook/qris', [WebqrisWebhookController::class, 'paid']);

  // app/Http/Controllers/WebqrisWebhookController.php
  namespace AppHttpControllers;

  use IlluminateHttpRequest;
  use IlluminateSupportFacadesLog;

  class WebqrisWebhookController extends Controller
  {
    public function paid(Request $request)
    {
      $signature = $request->header('X-Webhook-Signature', '');
      $body = $request->getContent();
      $expected = hash_hmac('sha256', $body, env('WEBHOOK_SECRET'));

      if (!hash_equals($expected, $signature)) {
        return response()->json(['success' => false, 'message' => 'Invalid signature'], 401);
      }

      $payload = json_decode($body, true);
      $payment = $payload['data'] ?? [];
      Log::info('WebQRIS payment paid', $payment);

      return response()->json(['success' => true]);
    }
  }
Contoh Receiver Webhook: CodeIgniter 4
<?php

  namespace AppControllers;

  use CodeIgniterHTTPResponseInterface;

  class WebqrisWebhook extends BaseController
  {
    public function paid(): ResponseInterface
    {
      $signature = $this->request->getHeaderLine('X-Webhook-Signature');
      $body = $this->request->getBody();
      $expected = hash_hmac('sha256', $body, env('webqris.webhookSecret'));

      if (!hash_equals($expected, $signature)) {
        return $this->response
          ->setStatusCode(401)
          ->setJSON([
            'success' => false,
            'message' => 'Invalid signature',
          ]);
      }

      $payload = json_decode($body, true);
      if (($payload['event'] ?? '') !== 'payment.paid') {
        return $this->response
          ->setStatusCode(400)
          ->setJSON([
            'success' => false,
            'message' => 'Invalid event',
          ]);
      }

      $payment = $payload['data'] ?? [];

      // TODO: cocokkan invoice/order di database Anda.

      return $this->response->setJSON([
        'success' => true,
        'invoice_id' => $payment['invoice_id'] ?? null,
        'merchant_order_id' => $payment['merchant_order_id'] ?? null,
        'status' => $payment['status'] ?? null,
      ]);
    }
  }
Route + Controller CodeIgniter 4 (Versi Terpisah)
// app/Config/Routes.php
    $routes->post('webhook/qris', 'WebqrisWebhook::paid');

    // app/Controllers/WebqrisWebhook.php
    namespace AppControllers;

    class WebqrisWebhook extends BaseController
    {
      public function paid()
      {
        $signature = $this->request->getHeaderLine('X-Webhook-Signature');
        $body = $this->request->getBody();
        $expected = hash_hmac('sha256', $body, env('webqris.webhookSecret'));

        if (!hash_equals($expected, $signature)) {
          return $this->response->setStatusCode(401)->setJSON([
            'success' => false,
            'message' => 'Invalid signature',
          ]);
        }

        $payload = json_decode($body, true);
        $payment = $payload['data'] ?? [];

        return $this->response->setJSON([
          'success' => true,
          'invoice_id' => $payment['invoice_id'] ?? null,
        ]);
      }
    }
POST /api/webhook/payment

Endpoint milik WebQRIS untuk menerima notifikasi e-wallet yang di-forward dari APK Notification Forwarder. Server akan mem-parse nominal dari field message, mencari payment pending dengan total_amount yang sama (per-merchant), lalu mengubah status menjadi paid dan mengirim webhook outbound ke sistem merchant (jika dikonfigurasi).

Headers
AuthorizationBearer <CALLBACK_SECRET>Required
Content-Typeapplication/json
Request Body (contoh)
{
  "app": "com.dana.id",
  "title": "DANA",
  "message": "Kamu berhasil menerima Rp25.042 dari JOHN DOE",
  "timestamp": "2026-03-21T10:15:23.000Z",
  "device_id": "device-01",
  "notif_id": "1234567890",
  "event_hash": "..."
}
Test Koneksi

Untuk test auth & koneksi tanpa memproses pembayaran, kirim event_hash bernilai test.

{
  "message": "test",
  "event_hash": "test"
}
Contoh cURL Test
curl -X POST 'https://webqris.com/api/webhook/payment'   -H 'Authorization: Bearer YOUR_CALLBACK_SECRET'   -H 'Content-Type: application/json'   -d '{
    "message": "test",
    "event_hash": "test"
  }'
Response (contoh)
{
  "success": true,
  "invoice_id": "INV-1710000000-abc123",
  "parsed_amount": 25042,
  "sender_name": "JOHN DOE"
}
Response — Test Mode (event_hash: "test")
{
  "success": true,
  "message": "Koneksi berhasil! Auth valid ✓ (Merchant: Toko ABC)",
  "test": true
}
Response — Duplicate Notification
{
  "success": false,
  "message": "Duplicate notification",
  "notif_id": "1234567890"
}
Response — Gagal Parse Nominal (422)
{
  "success": false,
  "message": "Tidak dapat mendeteksi nominal dari notifikasi",
  "raw_message": "Pesan yang tidak dikenal"
}
Response — Tidak Ada Payment Cocok (404)
{
  "success": false,
  "message": "No matching pending payment",
  "parsed_amount": 25042,
  "merchant": "Toko ABC"
}
POST /api/callback/notify

Endpoint milik WebQRIS dengan format sederhana untuk menandai payment sebagai paid berdasarkan nominal amount (harus sama dengan total_amount). Cocok untuk integrasi non-APK.

Headers
X-Callback-Key<CALLBACK_SECRET>Required
Content-Typeapplication/json
Request Body
{
  "amount": 25042,
  "sender_name": "JOHN DOE",
  "reference": "optional-reference"
}
Contoh cURL
curl -X POST 'https://webqris.com/api/callback/notify'   -H 'X-Callback-Key: YOUR_CALLBACK_SECRET'   -H 'Content-Type: application/json'   -d '{
    "amount": 25042,
    "sender_name": "JOHN DOE",
    "reference": "optional-reference"
  }'
Contoh Backend Kirim Callback Notify
PHP
<?php

  $payload = json_encode([
    'amount' => 25042,
    'sender_name' => 'JOHN DOE',
    'reference' => 'optional-reference',
  ]);

  $ch = curl_init('https://webqris.com/api/callback/notify');
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'X-Callback-Key: YOUR_CALLBACK_SECRET',
      'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => $payload,
  ]);

  $response = curl_exec($ch);
  curl_close($ch);
JavaScript / Node.js (Express)
app.post('/forward-qris-notify', async (req, res) => {
  const response = await fetch('https://webqris.com/api/callback/notify', {
    method: 'POST',
    headers: {
      'X-Callback-Key': process.env.WEBQRIS_CALLBACK_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: req.body.amount,
      sender_name: req.body.sender_name,
      reference: req.body.reference,
    }),
  });

  const data = await response.json();
  return res.status(response.status).json(data);
});
React / Next.js (aman via backend sendiri)
// app/api/webqris/callback-notify/route.ts
export async function POST(request: Request) {
  const body = await request.json();

  const response = await fetch('https://webqris.com/api/callback/notify', {
    method: 'POST',
    headers: {
      'X-Callback-Key': process.env.WEBQRIS_CALLBACK_SECRET || '',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  return Response.json(await response.json(), { status: response.status });
}

// React frontend memanggil backend Anda sendiri, jangan expose callback secret ke browser.
Laravel
<?php

  use IlluminateSupportFacadesHttp;

  $response = Http::withHeaders([
    'X-Callback-Key' => config('services.webqris.callback_secret'),
  ])->post(config('services.webqris.base_url') . '/api/callback/notify', [
    'amount' => 25042,
    'sender_name' => 'JOHN DOE',
    'reference' => 'optional-reference',
  ]);

  $data = $response->json();
CodeIgniter 4
<?php

  $client = ConfigServices::curlrequest();
  $response = $client->post(env('webqris.baseUrl') . '/api/callback/notify', [
    'headers' => [
      'X-Callback-Key' => env('webqris.callbackSecret'),
      'Content-Type' => 'application/json',
    ],
    'json' => [
      'amount' => 25042,
      'sender_name' => 'JOHN DOE',
      'reference' => 'optional-reference',
    ],
  ]);

  $data = json_decode($response->getBody(), true);
Response
{
  "success": true,
  "invoice_id": "INV-1710000000-abc123"
}
WS /ws  &  /ws/merchant?token=API_TOKEN

WebSocket untuk menerima event realtime. /ws untuk dashboard admin, /ws/merchant untuk merchant-specific (auth via query param token).

Events
new_paymentPayment baru dibuat
payment_paidPayment berhasil dibayar (dari APK, callback, atau manual confirm)
payments_expiredPayment expired (batch, tiap 60 detik)
webhook_deliveredWebhook outbound berhasil terkirim ke merchant
Format Message
{
  "event": "payment_paid",
  "data": {
    "id": 123,
    "invoice_id": "INV-1710000000-abc123",
    "amount": 25000,
    "total_amount": 25042,
    "unique_code": 42,
    "merchant_name": "Toko ABC",
    "sender_name": "JOHN DOE",
    "status": "paid"
  },
  "ts": 1710000000000
}
Ping/Pong

Kirim pesan ping → server membalas pong. Gunakan untuk keep-alive.

Contoh (JavaScript)
const ws = new WebSocket('wss://webqris.com/ws/merchant?token=YOUR_API_TOKEN');
ws.onmessage = (e) => {
  const { event, data } = JSON.parse(e.data);
  if (event === 'payment_paid') {
    console.log('Pembayaran masuk:', data.invoice_id, data.total_amount);
  }
};
// Keep-alive
setInterval(() => ws.send('ping'), 30000);
Error Responses

Semua endpoint mengembalikan format error yang konsisten:

{
  "success": false,
  "message": "Deskripsi error"
}
400Bad Request — parameter tidak valid atau kurang
401Unauthorized — token tidak valid atau tidak ada
404Not Found — resource tidak ditemukan
422Unprocessable — gagal memproses data (contoh: nominal tidak terdeteksi)
500Internal Server Error
503Service Unavailable — QRIS belum dikonfigurasi atau kode unik habis
Alur Integrasi
  1. Register & login ke WebQRIS Dashboard
  2. Buat Merchant dan generate API Token
  3. Set Webhook URL untuk menerima notifikasi pembayaran
  4. Kirim request POST /api/payments/qris/create dari backend Anda
  5. Tampilkan QR Code dari qris_payload ke pelanggan
  6. Jika memakai APK / notif forwarder, kirim notifikasi masuk ke endpoint WebQRIS: POST /api/webhook/payment atau POST /api/callback/notify
  7. Poll status via GET /api/payments/:invoiceId/status (opsional)
  8. Terima webhook outbound dari WebQRIS di Webhook URL milik sistem Anda saat status menjadi paid
  9. Verifikasi HMAC signature dan proses pembayaran
WebQRIS v3.6 — Dashboard