Dokumentasi NotivGo

Cara menerima notifikasi pembayaran di sistem Anda sendiri, dan cara memakai API QRIS & pesanan.

1. Webhook: yang NotivGo kirim ke Anda

HP Anda mengirim POST ke URL webhook Anda setiap ada notifikasi yang cocok. Nominal diekstrak di HP, dan kiriman webhook berangkat langsung dari HP ke URL Anda — tidak lewat server kami.

POST https://webhook-anda.com/notivgo
Content-Type: application/json; charset=utf-8
X-Device-Id: dev_xxx
X-Timestamp: 1723800000
X-Nonce: 3f2a…
X-Signature: sha256=…
X-NotivGo-Event: notification.forwarded
X-NotivGo-Schema: 2
X-NotivGo-Attempt: 1
Idempotency-Key: evt_01H…

{
  "event_id": "evt_01H…",
  "device_id": "dev_xxx",
  "source": "notivgo",
  "package": "id.dana",
  "app_name": "DANA",
  "post_time": 1723799995000,
  "captured_at": 1723799995120,
  "amount": 150000,
  "direction": "in",
  "currency": "IDR",
  "title": "Dana masuk",
  "text": "Anda menerima Rp150.000 dari B***",
  "big_text": "",
  "sub_text": "",
  "summary_text": "",
  "ticker_text": "",
  "channel_id": "…",
  "event": "notification.forwarded",
  "schema_version": 2,
  "attempt": 1
}

Selain webhook, HP juga bisa mengirim ke Telegram (pesan ke chat/grup Anda) dan Google Sheets (baris baru lewat Apps Script). Atur di portal → Tujuan kiriman.

2. Isi kiriman (field)

FieldArti
event_idID unik notifikasi. Sama di setiap percobaan ulang — pakai untuk menolak duplikat.
package, app_nameAplikasi sumber notifikasi (mis. id.dana, DANA).
post_time, captured_atWaktu notifikasi muncul & waktu dibaca HP, dalam milidetik epoch.
amountNominal rupiah (angka bulat) hasil baca di HP, atau null bila tidak terbaca.
direction"in" (uang masuk), "out" (uang keluar), atau null (tidak yakin). Hanya "in" yang aman dianggap pembayaran.
currency"IDR" bila nominal terbaca, selain itu null.
title, text, big_text, …Teks notifikasi apa adanya (disamarkan bila Anda menyalakan penyamaran di HP).
eventnotification.forwarded, atau webhook.test untuk kiriman contoh.
attemptPercobaan ke berapa (1, 2, …).

Keputusan akhir "sudah dibayar" tetap di tangan Anda: cocokkan dengan mutasi resmi sebelum menyerahkan barang bernilai besar.

3. Verifikasi tanda tangan (WAJIB)

Jangan percaya isi request sebelum tanda tangannya cocok. Basis tanda tangan adalah timestamp.nonce.rawbody — pakai raw body, bukan hasil parse JSON, karena spasi sekecil apa pun mengubah hasilnya.

<?php
$raw   = file_get_contents('php://input');
$ts    = $_SERVER['HTTP_X_TIMESTAMP'];
$nonce = $_SERVER['HTTP_X_NONCE'];
$sig   = $_SERVER['HTTP_X_SIGNATURE'];

$want = 'sha256=' . hash_hmac('sha256', "$ts.$nonce.$raw", $WEBHOOK_SECRET);
if (!hash_equals($want, $sig)) { http_response_code(401); exit; }

// Tolak yang terlalu tua (anti replay) — balas clock_skew supaya HP mengoreksi jamnya.
if (abs(time() - (int)$ts) > 300) {
  http_response_code(401);
  header('Content-Type: application/json');
  echo json_encode(['error' => 'clock_skew', 'server_time' => time()]);
  exit;
}
// Lalu abaikan event_id yang sudah pernah diproses.
// Node.js (Express, body mentah)
const crypto = require('crypto');
app.post('/notivgo', express.raw({type: '*/*'}), (req, res) => {
  const ts = req.get('X-Timestamp'), nonce = req.get('X-Nonce');
  const want = 'sha256=' + crypto.createHmac('sha256', SECRET)
    .update(ts + '.' + nonce + '.' + req.body).digest('hex');
  const got = req.get('X-Signature') || '';
  if (got.length !== want.length ||
      !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want))) return res.sendStatus(401);
  const ev = JSON.parse(req.body);
  // … simpan ev.event_id, abaikan bila sudah pernah …
  res.sendStatus(200);
});

Secret webhook ada di portal → Webhook. Bisa dirotasi kapan saja; HP mengikuti otomatis lewat heartbeat (≤15 menit), tanpa pairing ulang.

4. Balasan & coba ulang

Balasan server AndaYang dilakukan HP
2xxTerkirim — selesai.
408, 429, 5xx, jaringan putus/timeoutDicoba ulang otomatis dengan jeda bertambah, sampai batas percobaan.
401 berisi {"error":"clock_skew","server_time":…}HP mengoreksi selisih jamnya lalu mencoba lagi.
401/403 lainnyaBerhenti — biasanya secret salah. Periksa secret di portal.
3xxTidak diikuti (redirect bisa membuang isi POST) — dianggap gagal. Pakai URL akhir.
4xx lainnyaGagal tanpa diulang.

Idempotensi

Jaringan seluler bisa membuat satu event terkirim lebih dari sekali. Simpan event_id dan abaikan yang sudah pernah diproses — inilah pengaman agar pesanan tidak terhitung dua kali.

5. API QRIS dinamis (opsional)

Buat QR bernominal dari QRIS statis Anda lewat API. Dibayar per QR yang dibuat. API key dibuat di portal → Developer.

POST /v1/generate
Authorization: Bearer ngk_…
Content-Type: application/json

{"order_ref":"INV-001","amount":"150000","format":"payload"}
Field kirimanArti
order_refNomor pesanan Anda, unik per akun. Mengirim ulang ref yang sama mengembalikan QR yang sama tanpa menagih dua kali (HTTP 200).
amountNominal rupiah (angka atau teks angka).
qris_idOpsional: QRIS mana yang dipakai bila akun punya lebih dari satu. Kosong = QRIS pertama.
format"payload" (bawaan) atau "png" (ikut mengirim gambar QR base64).
HTTP 201
{"generate_id":"gen_…","order_ref":"INV-001","qris_payload":"000201…6304ABCD","metered":true}

metered:false + HTTP 200 berarti ulang-kirim ref yang sama (tidak ditagih lagi).

6. API pesanan (pencocokan terkelola)

Daftarkan pesanan beserta nominalnya; saat HP melaporkan uang masuk bernominal sama dalam jendela waktunya, server menandai pesanan itu paid. Nominal kembar pada saat bersamaan tidak ditebak — ditandai untuk Anda tinjau di portal.

POST /v1/orders
Authorization: Bearer ngk_…
{"order_ref":"INV-001","amount":150000,"note":"Meja 4","expires_in":1800}

→ 201 {"order_ref":"INV-001","amount":150000,"status":"open","expires_at":1723801800}

GET  /v1/orders/INV-001          → {"order_ref":…,"status":"open|paid|expired|canceled","paid_at":…}
POST /v1/orders/INV-001/cancel   → {"order_ref":"INV-001","status":"canceled"}

expires_in dalam detik (0 = tanpa kedaluwarsa, maks 90 hari). Mengirim ulang order_ref yang sama dengan isi sama → 200 "duplicate":true; dengan nominal/catatan berbeda → 409. Batas 600 permintaan per menit per akun.

7. Kode galat API

Semua galat berbentuk {"error":"kode","message":"penjelasan"}.

HTTPerrorArtinya
400bad_json, bad_request, unsupported_currency, bad_formatIsi permintaan tidak sah — lihat message.
401unauthorizedAPI key salah atau sudah dicabut.
402credit_limit_reachedBatas tagihan tercapai — lunasi tagihan di portal, QR baru terbuka lagi.
403account_inactiveAkun belum aktif (verifikasi email) atau ditangguhkan.
404qris_not_found, not_foundQRIS/pesanan tidak ada di akun ini.
409order_ref_conflict, webhook_required, not_cancelableNomor pesanan sudah dipakai dengan isi lain · akun belum punya tujuan kiriman · pesanan bukan berstatus open.
429rate_limitedTerlalu cepat — tunggu sebentar (lihat header Retry-After).

NotivGo adalah alat bantu, bukan payment gateway: kami tidak pernah memegang uang Anda. ← kembali ke portal