Event Webhook
IsiKuota mengirim dua tipe event lewat mekanisme webhook yang sama: transaction.updated (order prabayar/game/pascabayar) dan deposit.updated (deposit saldo via API). Cek nilai event untuk tahu jenis notifikasinya, lalu tentukan tindakan Anda berdasarkan data.status — bukan cuma dari nama event.
Dengan event yang konsisten per domain (transaksi vs deposit), integrasi Anda lebih sederhana: cukup baca event untuk routing, lalu data.status untuk detail penanganan. Tidak perlu menangani banyak nama event yang berbeda-beda per status.
Event transaction.updated
Dikirim setiap kali status order prabayar/game/pascabayar berubah.
Nilai data.status
| Status | Arti |
|---|---|
pending | Transaksi sedang diproses |
success | Transaksi berhasil — sn terisi (untuk produk yang punya SN) |
failed | Transaksi gagal — saldo dikembalikan otomatis |
Contoh Payload per Status
- ⏳ Pending
- ✅ Success
- ❌ Failed
Dikirim saat transaksi baru dibuat / sedang diproses.
{
"event": "transaction.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W2",
"timestamp": "2025-01-15T14:29:55+07:00",
"data": {
"ref_id": "ORDER-2025-001",
"status": "pending",
"product_code": "TSEL10",
"product_name": "Telkomsel 10.000",
"customer_no": "081234567890",
"price": 11500,
"sn": null,
"message": "Transaksi sedang diproses",
"balance": 489000,
"created_at": "2025-01-15T14:29:55+07:00",
"updated_at": "2025-01-15T14:29:55+07:00"
}
}
Dikirim saat transaksi berhasil diproses provider. Field sn sudah terisi untuk produk yang memiliki serial number.
{
"event": "transaction.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W3",
"timestamp": "2025-01-15T14:30:00+07:00",
"data": {
"ref_id": "ORDER-2025-001",
"status": "success",
"product_code": "TSEL10",
"product_name": "Telkomsel 10.000",
"customer_no": "081234567890",
"price": 11500,
"sn": "1234567890123456",
"message": "Transaksi berhasil",
"balance": 489000,
"created_at": "2025-01-15T14:29:55+07:00",
"updated_at": "2025-01-15T14:30:00+07:00"
}
}
Pada status ini, simpan sn dan tampilkan ke pengguna Anda.
Dikirim saat transaksi gagal. Saldo dikembalikan otomatis.
{
"event": "transaction.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W4",
"timestamp": "2025-01-15T14:31:00+07:00",
"data": {
"ref_id": "ORDER-2025-002",
"status": "failed",
"product_code": "TSEL10",
"product_name": "Telkomsel 10.000",
"customer_no": "081234567890",
"price": 11500,
"sn": null,
"message": "Transaksi gagal - nomor tidak aktif",
"balance": 500500,
"created_at": "2025-01-15T14:30:50+07:00",
"updated_at": "2025-01-15T14:31:00+07:00"
}
}
Field message berisi alasan kegagalan dari provider. Tampilkan ke user atau log untuk investigasi. Perhatikan balance sudah termasuk pengembalian saldo.
Field Data Payload
| Field | Tipe | Keterangan |
|---|---|---|
event | string | Selalu transaction.updated |
event_id | string | ID unik per pengiriman (ULID), untuk deduplikasi |
timestamp | string | Waktu event dikirim (ISO 8601, +07:00) |
data.ref_id | string | ID referensi dari sistem Anda |
data.status | string | pending / success / failed |
data.product_code | string | Kode produk |
data.product_name | string | Nama produk |
data.customer_no | string | Nomor tujuan transaksi |
data.price | number | Harga transaksi (Rp) |
data.sn | string|null | Serial number (null jika belum / tidak ada) |
data.message | string|null | Pesan status dari provider |
data.balance | number | Saldo akun Anda setelah transaksi |
data.created_at | string | Waktu transaksi dibuat |
data.updated_at | string | Waktu update terakhir |
Event deposit.updated
Dikirim setiap kali status deposit saldo yang dibuat lewat POST /v1/deposit (cmd=create) mencapai status final. Berbeda dari transaction.updated, event ini tidak dikirim untuk status pending — hanya saat deposit selesai diproses (oleh provider pembayaran, oleh sistem karena kedaluwarsa, atau oleh Anda sendiri lewat cmd=cancel).
Event ini dikirim ke URL webhook yang sama dengan transaction.updated (satu callback_url per akun, diatur di Pengaturan → API → Webhook). Tidak ada konfigurasi terpisah — begitu URL webhook Anda terdaftar, kedua jenis event otomatis terkirim ke sana.
Nilai data.status
| Status | Arti |
|---|---|
success | Deposit berhasil, saldo sudah ditambahkan |
failed | Deposit gagal (mis. gateway pembayaran menolak permintaan) |
expired | Batas waktu pembayaran terlewati tanpa pembayaran masuk |
cancelled | Dibatalkan lewat cmd=cancel atau oleh admin |
Contoh Payload
- ✅ Success
- ❌ Failed
- ⌛ Expired
- 🚫 Cancelled
{
"event": "deposit.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W5",
"timestamp": "2026-09-20T00:55:06+07:00",
"data": {
"ref_id": "DEP20260920005429RKXIEJ",
"status": "success",
"amount": 100000,
"pay_amount": 100700,
"get_amount": 100000,
"deposit_method_name": "QRIS - Semua E-Wallet & Bank",
"deposit_channel": "QRIS",
"note": null,
"balance": 250000,
"paid_at": "2026-09-20T00:55:06+07:00",
"created_at": "2026-09-20T00:54:29+07:00",
"updated_at": "2026-09-20T00:55:06+07:00"
}
}
{
"event": "deposit.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W6",
"timestamp": "2026-09-20T00:56:00+07:00",
"data": {
"ref_id": "DEP20260920005429RKXIEJ",
"status": "failed",
"amount": 100000,
"pay_amount": 100700,
"get_amount": 100000,
"deposit_method_name": "QRIS - Semua E-Wallet & Bank",
"deposit_channel": "QRIS",
"note": "Gateway error: transaksi ditolak",
"balance": 150000,
"paid_at": null,
"created_at": "2026-09-20T00:54:29+07:00",
"updated_at": "2026-09-20T00:56:00+07:00"
}
}
{
"event": "deposit.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W7",
"timestamp": "2026-09-21T00:54:30+07:00",
"data": {
"ref_id": "DEP20260920005429RKXIEJ",
"status": "expired",
"amount": 100000,
"pay_amount": 100700,
"get_amount": 100000,
"deposit_method_name": "QRIS - Semua E-Wallet & Bank",
"deposit_channel": "QRIS",
"note": null,
"balance": 150000,
"paid_at": null,
"created_at": "2026-09-20T00:54:29+07:00",
"updated_at": "2026-09-21T00:54:30+07:00"
}
}
{
"event": "deposit.updated",
"event_id": "evt_01J3K8M2N4P5Q6R7S8T9U0V1W8",
"timestamp": "2026-09-20T01:00:00+07:00",
"data": {
"ref_id": "DEP20260920005429RKXIEJ",
"status": "cancelled",
"amount": 100000,
"pay_amount": 100700,
"get_amount": 100000,
"deposit_method_name": "QRIS - Semua E-Wallet & Bank",
"deposit_channel": "QRIS",
"note": null,
"balance": 150000,
"paid_at": null,
"created_at": "2026-09-20T00:54:29+07:00",
"updated_at": "2026-09-20T01:00:00+07:00"
}
}
Field Data Payload
| Field | Tipe | Keterangan |
|---|---|---|
event | string | Selalu deposit.updated |
event_id | string | ID unik per pengiriman (ULID), untuk deduplikasi |
timestamp | string | Waktu event dikirim (ISO 8601, +07:00) |
data.ref_id | string | ID referensi deposit — sama dengan ref_id dari cmd=create |
data.status | string | success / failed / expired / cancelled |
data.amount | number | Nominal deposit yang diminta — belum termasuk biaya admin/kode unik — sama seperti field amount pada response Deposit Saldo |
data.pay_amount | number | Nominal final yang harus dibayar/ditransfer (= amount + biaya admin + kode unik jika berlaku). Gunakan field ini untuk verifikasi pembayaran, bukan data.amount |
data.get_amount | number | Nominal yang dikreditkan ke saldo Anda — bisa lebih besar dari data.amount kalau metode memakai kode unik (tidak termasuk biaya admin) |
data.deposit_method_name | string | Nama metode deposit yang dipakai |
data.deposit_channel | string|null | Channel spesifik (mis. QRIS, VA BCA) |
data.note | string|null | Catatan tambahan, mis. alasan gagal dari gateway |
data.balance | number | Saldo akun Anda setelah deposit diproses |
data.paid_at | string|null | Waktu pembayaran diterima (null jika belum/tidak pernah dibayar) |
data.created_at | string | Waktu deposit dibuat |
data.updated_at | string | Waktu status terakhir diperbarui |
Contoh Handler Berdasarkan Status
Contoh di bawah fokus pada logika penanganan status setelah payload diterima, untuk event transaction.updated. Untuk verifikasi X-Isikuota-Signature, lihat Verifikasi Signature. Karena kedua event dikirim ke URL yang sama, cek field event lebih dulu sebelum menangani data.status — lihat contoh routing di bawah.
<?php
$payload = json_decode(file_get_contents('php://input'), true);
switch ($payload['event']) {
case 'transaction.updated':
handleTransactionUpdated($payload['data']);
break;
case 'deposit.updated':
handleDepositUpdated($payload['data']);
break;
}
switch (payload.event) {
case 'transaction.updated':
handleTransactionUpdated(payload.data);
break;
case 'deposit.updated':
handleDepositUpdated(payload.data);
break;
}
Contoh berikut mengasumsikan Anda sudah routing berdasarkan event seperti di atas, dan sekarang berada di dalam handleTransactionUpdated():
- PHP
- JavaScript
- Python
- Java
- C#
<?php
$payload = json_decode(file_get_contents('php://input'), true);
$data = $payload['data'];
$refId = $data['ref_id'];
switch ($data['status']) {
case 'success':
// Update status order di DB, kirim SN ke user
updateOrder($refId, 'success', $data['sn']);
notifyUser($refId, $data['sn']);
break;
case 'failed':
// Update status, notifikasi user gagal
updateOrder($refId, 'failed');
notifyUserFailed($refId, $data['message']);
break;
case 'pending':
// Opsional: catat bahwa transaksi sedang diproses
updateOrder($refId, 'pending');
break;
}
http_response_code(200);
echo json_encode(['status' => 'ok']);
app.post('/webhook/isikuota', express.json(), (req, res) => {
const { data } = req.body;
const refId = data.ref_id;
switch (data.status) {
case 'success':
// Update status order di DB, kirim SN ke user
updateOrder(refId, 'success', data.sn);
notifyUser(refId, data.sn);
break;
case 'failed':
// Update status, notifikasi user gagal
updateOrder(refId, 'failed');
notifyUserFailed(refId, data.message);
break;
case 'pending':
// Opsional: catat bahwa transaksi sedang diproses
updateOrder(refId, 'pending');
break;
}
res.status(200).json({ status: 'ok' });
});
@app.route('/webhook/isikuota', methods=['POST'])
def webhook():
payload = request.get_json()
data = payload['data']
ref_id = data['ref_id']
if data['status'] == 'success':
# Update status order di DB, kirim SN ke user
update_order(ref_id, 'success', data['sn'])
notify_user(ref_id, data['sn'])
elif data['status'] == 'failed':
# Update status, notifikasi user gagal
update_order(ref_id, 'failed')
notify_user_failed(ref_id, data['message'])
elif data['status'] == 'pending':
# Opsional: catat bahwa transaksi sedang diproses
update_order(ref_id, 'pending')
return jsonify({'status': 'ok'}), 200
Map<String, Object> data = (Map<String, Object>) payload.get("data");
String refId = (String) data.get("ref_id");
String status = (String) data.get("status");
switch (status) {
case "success" -> {
// Update status order di DB, kirim SN ke user
updateOrder(refId, "success", (String) data.get("sn"));
notifyUser(refId, (String) data.get("sn"));
}
case "failed" -> {
// Update status, notifikasi user gagal
updateOrder(refId, "failed", null);
notifyUserFailed(refId, (String) data.get("message"));
}
case "pending" ->
// Opsional: catat bahwa transaksi sedang diproses
updateOrder(refId, "pending", null);
}
var data = payload.GetProperty("data");
var refId = data.GetProperty("ref_id").GetString()!;
var status = data.GetProperty("status").GetString();
switch (status)
{
case "success":
// Update status order di DB, kirim SN ke user
var sn = data.GetProperty("sn").GetString();
UpdateOrder(refId, "success", sn);
NotifyUser(refId, sn);
break;
case "failed":
// Update status, notifikasi user gagal
var message = data.GetProperty("message").GetString();
UpdateOrder(refId, "failed", null);
NotifyUserFailed(refId, message);
break;
case "pending":
// Opsional: catat bahwa transaksi sedang diproses
UpdateOrder(refId, "pending", null);
break;
}
Gunakan event_id untuk mendeteksi pengiriman ganda. Simpan event_id yang sudah diproses, dan lewati jika menerima ulang.