Logo Cutiezy
Cutiezy Portal / Dokumentasi
PT CUTIEZY ID NUSANTARA Production
Cutiezy • Developer Docs

Webhook Mutasi QRIS Realtime

Terima notifikasi otomatis (HTTP POST Callback) secara instan setiap kali pelanggan berhasil melakukan pembayaran QRIS di toko atau merchant Anda.

🔔

Informasi Layanan & Ketentuan Webhook

  • Kecepatan Realtime: Webhook dikirimkan dalam hitungan detik setelah transaksi terkonfirmasi oleh jaringan perbankan QRIS / Hub.
  • Biaya Langganan Terjangkau: Hanya Rp 2.000 / slot / bulan (30 hari). Anda dapat membeli lebih dari 1 slot webhook.
  • Routing Khusus per QRIS Toko: Setiap slot webhook dapat diarahkan khusus ke QRIS toko tertentu atau menerima seluruh mutasi QRIS Anda.
  • Bebas Batas Transaksi (Unlimited): Tidak ada batasan jumlah pengiriman webhook selama masa aktif langganan slot berjalan.
  • Keamanan HMAC SHA-256: Setiap request dilengkapi tanda tangan digital kriptografi yang dapat diverifikasi menggunakan Secret Token akun Anda.

Alur Pengiriman Webhook

1 Pembayaran QRIS

Pelanggan memindai QRIS merchant Anda melalui aplikasi m-banking atau e-wallet (BCA, Mandiri, BRI, Dana, GoPay, dll).

2 Deteksi Mutasi

Sistem background worker Cutiezy mendeteksi transaksi masuk secara realtime (< 1 detik).

3 Dispatch POST

Sistem mengirimkan HTTP POST JSON ke URL Webhook Anda membawa signature kriptografi HMAC SHA-256.

4 Fulfillment Order

Server Anda memverifikasi signature, mengupdate status order pembeli, dan mengembalikan respon HTTP 200 OK.

Spesifikasi Request Header

Setiap payload webhook yang dikirimkan oleh Cutiezy selalu menyertakan header berikut:

Nama Header Contoh Nilai Keterangan
Content-Type application/json Format data yang dikirim adalah JSON terstruktur.
X-Cutiezy-Signature a3f5c78b9... (64 hex characters) Tanda tangan HMAC SHA-256 dari raw body request menggunakan Secret Token Anda.
X-Cutiezy-Event qris.payment Jenis event: qris.payment untuk transaksi baru, atau test.ping untuk uji coba.
X-Cutiezy-Timestamp 2026-10-08T23:30:00+07:00 Waktu saat webhook dipancarkan (format ISO-8601).
User-Agent Cutiezy-Webhook/1.0 (+https://api.cutiezy.id) Identitas crawler pengirim webhook resmi Cutiezy.

Format Payload JSON

Struktur body request yang dikirimkan ke webhook server Anda.

Event: qris.payment
{
  "event": "qris.payment",
  "timestamp": "2026-10-08T23:30:00+07:00",
  "slot_no": 1,
  "target_qris": "QRIS Toko Utama",
  "data": {
    "id": "202610080019283719",
    "provider": "bri",
    "reference": "202610080019283719",
    "partner_reference": "00000001",
    "amount": 25000,
    "currency": "IDR",
    "status": "SUCCESS",
    "customer": "Cutiezy PASURUAN ID",
    "issuer": "QRIS - DANA",
    "channel": "QRIS",
    "occurred_at": "2026-10-08 23:29:55",
    "received_at": "2026-10-08T23:30:00+07:00"
  }
}

Deskripsi Field Payload

Field Tipe Keterangan
event string Tipe notifikasi, misal qris.payment atau test.ping.
slot_no integer Nomor slot webhook penerima (misal: 1, 2).
target_qris string Nama QRIS target yang dikonfigurasi pada slot (contoh: QRIS Toko Utama atau Semua QRIS (Global)).
data.reference string Nomor referensi unik transaksi dari perbankan/RRN (contoh: 202610080019283719). Gunakan untuk mencegah duplikasi (Idempotency).
data.amount integer Nominal pembayaran dalam Rupiah bulat (contoh: 25000).
data.status string Status mutasi, biasanya bernilai SUCCESS.
data.issuer string Aplikasi / Bank yang dipakai pembeli untuk scan QRIS (contoh: QRIS - DANA, BCA Mobile, GOPAY).
data.occurred_at string Waktu transaksi terjadi menurut bank.

Verifikasi Signature HMAC SHA-256

Untuk keamanan maksimal, selalu verifikasi keaslian pengirim sebelum memproses transaksi di server Anda.

🔒 Catatan Kriptografi: Signature dihitung dari RAW body JSON mentah (tanpa di-decode) menggunakan algoritma sha256 dengan kunci secret_key yang ada di menu pengaturan Webhook akun Anda.
PHP (Native / Laravel)
<?php
// 1. Ambil Secret Key Anda dari menu /qris
$secretKey = "YOUR_WEBHOOK_SECRET_KEY";

// 2. Baca RAW payload body dan header signature
$rawBody = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_CUTIEZY_SIGNATURE'] ?? '';

// 3. Hitung HMAC SHA-256
$expectedSignature = hash_hmac('sha256', $rawBody, $secretKey);

// 4. Verifikasi keaslian request dengan hash_equals
if (!hash_equals($expectedSignature, $receivedSignature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// 5. Decode JSON dan proses transaksi
$payload = json_decode($rawBody, true);
$event = $payload['event'] ?? '';
$data = $payload['data'] ?? [];

if ($event === 'qris.payment') {
    $ref = $data['reference'];
    $amount = $data['amount'];
    
    // TODO: Update status invoice order Anda di database menjadi PAID
    // Contoh: Orders::markAsPaid($ref, $amount);
}

// 6. Selalu kembalikan HTTP 200 OK
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['received' => true]);
Node.js (Express.js)
const express = require('express');
const crypto = require('crypto');
const app = express();

const SECRET_KEY = 'YOUR_WEBHOOK_SECRET_KEY';

// Penting: Gunakan express.raw() atau tangkap rawBody untuk verifikasi signature
app.post('/api/webhook/qris', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body.toString('utf8');
  const signature = req.headers['x-cutiezy-signature'] || '';

  const expectedSignature = crypto
    .createHmac('sha256', SECRET_KEY)
    .update(rawBody)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const payload = JSON.parse(rawBody);
  console.log('Notifikasi Transaksi Diterima:', payload);

  if (payload.event === 'qris.payment') {
    const { reference, amount, customer } = payload.data;
    // TODO: Proses pembayaran invoice Anda
  }

  res.status(200).json({ received: true });
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));
Python (FastAPI / Flask)
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
import json

app = FastAPI()
SECRET_KEY = b"YOUR_WEBHOOK_SECRET_KEY"

@app.post("/api/webhook/qris")
async def handle_qris_webhook(request: Request):
    raw_body = await request.body()
    signature = request.headers.get("x-cutiezy-signature", "")

    expected_signature = hmac.new(SECRET_KEY, raw_body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected_signature, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    payload = json.loads(raw_body.decode("utf-8"))
    
    if payload.get("event") == "qris.payment":
        data = payload.get("data", {})
        print(f"Pembayaran Masuk: Rp {data.get('amount')} (Ref: {data.get('reference')})")
        # TODO: Jalankan proses fulfillment order

    return {"received": True}

Respon Server & Best Practices

✓
Wajib Kembalikan HTTP 200 OK:

Server Anda wajib merespon dengan status code 200 OK dengan body {"received": true} dalam batas waktu maksimal 5 detik.

✓
Terapkan Idempotency (Pengecekan Ref Unik):

Gunakan nilai data.reference sebagai identifier unik. Simpan referensi transaksi ke database Anda agar order tidak diproses ganda jika terdapat retry jaringan.

✓
Gunakan Background Queue untuk Proses Berat:

Jika Anda perlu mengirimkan notifikasi WhatsApp, email PDF, atau interaksi API pihak ketiga yang membutuhkan waktu > 2 detik, masukkan ke antrean (queue) dan segera kembalikan response 200 OK ke Cutiezy.