Billingvro Docs

Webhook

Kirim kejadian di Billingvro — pelanggan baru, pembayaran, isolir — seketika ke n8n, Make, Zapier, atau bot WhatsApp. Berisi cara memasang, isi data setiap kejadian, dan cara cek tanda tangan.

Webhook mengirim kabar seketika setiap kali sesuatu terjadi di Billingvro ke alamat URL tujuan — n8n, Make, Zapier, atau bot WhatsApp — untuk diteruskan jadi WhatsApp, catatan, atau alur lain.

Kejadian di BillingvroContoh pemakaian
Pembayaran diterimaWhatsApp terima kasih + tanggal aktif berikutnya ke pelanggan
Pelanggan diisolirWhatsApp pemberitahuan ke pelanggan, kabar ke grup teknisi
Pelanggan baruWhatsApp sambutan, catat ke Google Sheet
Tagihan dibuatRincian tagihan lewat WhatsApp atau email

Ringkasan teknis

HalKeterangan
Cara kirimPOST ke Alamat URL tujuan, isi JSON (Content-Type: application/json)
Alamat tujuanWajib https:// dan bisa dijangkau dari internet (alamat jaringan lokal ditolak)
Bentuk isi{ "id", "type", "timestamp", "test"?, "data" } — lihat Bentuk kiriman
Jenis kejadiancustomer.created, customer.updated, customer.package_changed, customer.deleted, customer.import_completed, invoice.created, invoice.overdue, payment.promised, payment.received, customer.isolated, customer.reactivated
Tanda tanganWajib dicek. Header webhook-id, webhook-timestamp, webhook-signature (HMAC-SHA256, standar Standard Webhooks) — lihat Cek tanda tangan
Kunci rahasiaDiawali whsec_, didapat saat webhook dibuat
Balasan yang diharapkanKode 2xx dalam 10 detik
Percobaan ulang8 kali dalam sekitar 2 hari bila gagal
Kiriman dobelWajib dibuang: kiriman dengan webhook-id yang sudah pernah diproses

Syarat

  • Paket Pro (maksimal 3 webhook) atau Enterprise (maksimal 10). Selama masa coba, maksimal 3.
  • Masuk sebagai Pemilik atau Admin.
  • Alamat URL tujuan wajib diawali https:// dan bisa dijangkau dari internet. Alamat jaringan lokal (mis. 192.168.x.x, 10.x.x.x) ditolak.
  • Untuk template WhatsApp siap pakai: akun n8n dan akun NgirimWA beserta API key-nya.

Pasang webhook

Buka halaman Webhook

Buka Pengaturan → Webhook.

Halaman Webhook sebelum ada webhook, dengan tombol Tambah webhook

Isi halaman saat belum ada webhook:

  • Judul Webhook dengan keterangan "Kirim kejadian di Billingvro — pelanggan baru, pembayaran, isolir — ke n8n, Make, Zapier, atau bot WhatsApp Anda secara otomatis" dan tautan Baca panduan.
  • Tombol + Tambah webhook di kanan atas.
  • Jatah webhook, mis. "0 dari 10 webhook terpakai di paket Anda".
  • Kotak Belum ada webhook: "Tambahkan alamat URL dari n8n, Make, atau Zapier. Setiap ada kejadian yang Anda pilih, Billingvro langsung mengirimkannya ke sana."
  • Kotak Template n8n siap pakai berisi tiga tombol unduh — lihat Template n8n siap pakai.

Isi alamat dan pilih kejadian

Klik Tambah webhook. Isi jendela Tambah Webhook, dari atas ke bawah:

KolomIsiContoh
NamaNama bebasn8n — WA pelanggan
Alamat URL tujuanAlamat penerima. Wajib diawali https://https://n8n.usahaku.id/webhook/billingvro-pembayaran
Kejadian yang dikirimKotak centang dalam tiga kelompok: Pelanggan, Tagihan & Pembayaran, Isolir. Tiap kelompok punya tautan Pilih semuaPembayaran diterima, Pelanggan diisolir
Sertakan kontak pelangganSakelar. Wajib aktif untuk mengirim WhatsApp ke pelanggan — tanpa ini No. WhatsApp dan alamat tidak ikut terkirimAktif

Setiap kotak centang punya keterangan kapan kejadian itu dikirim — sama dengan kolom Kapan dikirim di Kejadian yang bisa dikirim. Keterangan sakelar kontak: "No. WhatsApp dan alamat pelanggan ikut dikirim. Wajib aktif bila Anda ingin mengirim WhatsApp ke pelanggan dari n8n." Saat sakelar dinyalakan, muncul pengingat: "Dengan mengaktifkan ini, Anda bertanggung jawab menjaga data pelanggan yang dikirim ke layanan pihak ketiga. Kata sandi dan data sensitif lain tidak pernah dikirim."

Klik Tambah (atau Batal untuk menutup).

Jendela Tambah Webhook: nama, alamat URL, pilihan kejadian per kelompok, dan sakelar kontak pelanggan

Isi pada gambar: Nama n8n — WA pelanggan, alamat https://n8n.usahaku.id/webhook/billingvro-pembayaran, kejadian Pembayaran diterima dan Pelanggan diisolir dicentang, sakelar Sertakan kontak pelanggan menyala.

Simpan kunci rahasia

Setelah ditambahkan, muncul jendela Simpan Kunci Rahasia:

  • "Webhook sudah dibuat. Salin kunci rahasia ini sekarang dan tempel di layanan penerima."
  • Kotak berisi kunci rahasia berawalan whsec_ dan tombol salin di kanannya (pada gambar, kuncinya dikaburkan).
  • "Tempel kunci ini di layanan penerima (mis. n8n) untuk memastikan kiriman benar-benar berasal dari Billingvro."
  • Tombol Sudah saya simpan.

Salin dan simpan kuncinya. Kunci ini wajib dipakai penerima untuk cek tanda tangan.

Jendela Simpan Kunci Rahasia dengan tombol salin

Lupa menyimpan?

Kunci bisa dilihat lagi lewat tombol Kunci rahasia di kartu webhook. Setiap kali kunci dibuka, kejadiannya dicatat di Log Aktivitas.

Kirim tes

Webhook baru muncul sebagai kartu:

  • Nama webhook (n8n — WA pelanggan) dan label Aktif berwarna hijau.
  • Alamat URL tujuan.
  • Ringkasan: jumlah kejadian dan status kontak, mis. "2 kejadian · kontak pelanggan ikut dikirim" atau "1 kejadian · tanpa kontak pelanggan".
  • Deretan tombol:
TombolKegunaan
Kirim tesKirim contoh kejadian berisi data fiktif
RiwayatLihat kiriman 30 hari terakhir dan kirim ulang
UbahGanti nama, alamat, kejadian, atau sakelar kontak
JedaHentikan pengiriman sementara (berubah jadi Aktifkan saat dijeda)
Kunci rahasiaLihat lagi kunci rahasia
Ganti kunciBuat kunci rahasia baru
HapusHapus webhook (tombol merah)
Kartu webhook berstatus Aktif dengan tombol Kirim tes, Riwayat, Ubah, Jeda, Kunci rahasia, Ganti kunci, dan Hapus

Klik Kirim tes. Isi jendela Kirim Tes:

  1. Alamat tujuan dan keterangan bahwa kiriman tes ditandai "test": true.
  2. Pilihan Kejadian, mis. "Tagihan & Pembayaran — Pembayaran diterima".
  3. Hasil setelah tombol Kirim tes diklik: kotak hijau "Contoh kejadian diterima oleh alamat tujuan. (1,0 detik)" bila berhasil, kotak merah berisi alasannya bila gagal.
  4. Contoh isi yang dikirim — JSON lengkap yang dikirim, sama dengan Contoh isi setiap kejadian ditambah "test": true dan "id": "evt_contoh".
  5. Bila sakelar kontak mati: "No. WhatsApp dan alamat tidak ikut terkirim karena "Sertakan kontak pelanggan" nonaktif."
Jendela Kirim Tes berisi pilihan kejadian, hasil berhasil, dan contoh isi yang dikirim

Hasil hijau berarti webhook siap. Hasil merah: lihat Solusi saat pengiriman gagal.

Template n8n siap pakai

Kotak Template n8n siap pakai di bawah halaman Webhook berisi keterangan "Unduh, impor ke n8n, isi API key NgirimWA dan kunci rahasia webhook, lalu aktifkan. Aktifkan "Sertakan kontak pelanggan" agar nomor WhatsApp ikut terkirim." dan tiga tombol unduh. Semua template mengirim WhatsApp lewat NgirimWA.

Tombol templateKejadian yang wajib dicentang
Pembayaran diterima → WA terima kasihPembayaran diterima (payment.received)
Pelanggan diisolir → WA pemberitahuanPelanggan diisolir (customer.isolated)
Pelanggan baru → WA sambutanPelanggan baru (customer.created)
Kartu Template n8n siap pakai dengan tiga tombol unduh

Unduh dan impor ke n8n

Klik tombol template. Di n8n, buat workflow baru, pilih ⋯ → Import from File, lalu pilih berkas yang diunduh.

Isi node Pengaturan

Ganti nilai setiap kolom:

KolomIsiContoh
webhookSecretKunci rahasia dari Billingvrowhsec_…
ngirimwaApiKeyAPI key dari akun NgirimWAngw_…
tenantNameNama usaha yang muncul di pesan — sama dengan nama tenant di Pengaturan → TenantMaju Net
testWhatsappNumberNomor WhatsApp penerima kiriman tes, format 62… (boleh kosong)6281234567890
verifySignatureWajib truetrue

Nama kolom wajib dibiarkan

Yang diganti hanya nilainya. Node Verifikasi & Susun Pesan membaca nama kolom di atas persis; nama yang diubah membuat template berhenti bekerja.

Teks pesan ada di node Verifikasi & Susun Pesan, bagian bertanda SUSUN PESAN.

Sambungkan ke Billingvro

Salin Production URL dari node Terima dari Billingvro, tempel sebagai Alamat URL tujuan di webhook Billingvro, lalu aktifkan workflow (sakelar Active).

Coba

Isi testWhatsappNumber, lalu klik Kirim tes di Billingvro. WhatsApp contoh masuk ke nomor uji. Kiriman tes tidak pernah dikirim ke nomor pelanggan fiktif.

Wajib: nyalakan Sertakan kontak pelanggan

Template WhatsApp butuh nomor pelanggan. Tanpa sakelar Sertakan kontak pelanggan, nomor tidak ikut dikirim dan template tidak mengirim apa-apa.

n8n di server sendiri

Template memakai modul crypto untuk cek tanda tangan. Di n8n server sendiri, wajib ada pengaturan NODE_FUNCTION_ALLOW_BUILTIN=crypto. Tanpa itu, template berhenti dengan pesan yang menjelaskan hal ini. Di n8n Cloud modul ini sudah tersedia.

Kejadian yang bisa dikirim

Kejadian (di layar) adalah nama kotak centang di jendela Tambah Webhook. Kode type adalah nilai kolom type pada kiriman.

KelompokKejadian (di layar)Kode typeKapan dikirim
PelangganPelanggan barucustomer.createdPelanggan PPPoE atau Hotspot ditambahkan.
PelangganData pelanggan diubahcustomer.updatedNama, kontak, alamat, router, atau tanggal jatuh tempo.
PelangganGanti paketcustomer.package_changedPelanggan pindah ke paket lain.
PelangganPelanggan dihapuscustomer.deletedPelanggan dipindahkan ke Data Terhapus.
PelangganImpor pelanggan selesaicustomer.import_completedSatu ringkasan setelah impor massal, bukan satu per pelanggan.
Tagihan & PembayaranTagihan dibuatinvoice.createdTagihan bulanan pelanggan PPPoE terbit.
Tagihan & PembayaranTagihan lewat jatuh tempoinvoice.overdueTagihan belum dibayar setelah tanggal jatuh tempo.
Tagihan & PembayaranJanji bayarpayment.promisedPelanggan diberi waktu tambahan untuk membayar.
Tagihan & PembayaranPembayaran diterimapayment.receivedPembayaran pelanggan PPPoE atau Hotspot dicatat.
IsolirPelanggan diisolircustomer.isolatedDiisolir manual atau otomatis karena menunggak.
IsolirPelanggan aktif kembalicustomer.reactivatedDiaktifkan manual, setelah bayar, atau janji bayar.

Impor pelanggan dari MikroTik hanya mengirim satu kabar ringkasan setelah selesai, bukan satu per pelanggan.

Data pelanggan yang ikut terkirim

Nama pelanggan, paket, tagihan, dan pembayaran selalu ikut. No. WhatsApp dan alamat hanya ikut bila Sertakan kontak pelanggan dinyalakan. Kata sandi PPPoE, sandi voucher, NIK, dan koordinat tidak pernah dikirim.

Wajib rahasiakan alamat URL dan kunci rahasia

Data yang sudah sampai di penerima berada di luar Billingvro. Alamat URL webhook dan kunci rahasia tidak boleh dibagikan.

Hindari WhatsApp dobel

WhatsApp otomatis bawaan Billingvro (pengingat tagihan, kabar isolir) diatur di Pengaturan → Notifikasi. Bila penerima webhook juga mengirim WhatsApp untuk kejadian yang sama, pelanggan menerima dua pesan. Jendela webhook menampilkan peringatan bila hal ini terdeteksi — matikan salah satunya.

Riwayat dan kirim ulang

Klik Riwayat di kartu webhook. Jendela Riwayat Pengiriman berisi keterangan "Riwayat 30 hari terakhir. Kiriman yang gagal dicoba ulang otomatis sampai ±2 hari.", saringan Semua status, dan tombol muat ulang.

Setiap baris berisi nama kejadian (bertanda (tes) bila dari Kirim tes), waktu kirim, dan label status. Kiriman gagal menampilkan alasannya dalam huruf merah, ditambah waktu percobaan berikutnya bila berstatus Dicoba ulang. Klik baris untuk membuka Isi yang dikirim (JSON lengkap) dan Balasan alamat tujuan beserta kodenya. Tombol Kirim ulang ada di sebelah label status (tidak muncul selama kiriman masih Menunggu).

Riwayat pengiriman berisi dua kiriman Terkirim dan satu kiriman Gagal beserta alasannya

Isi pada gambar: tiga kiriman tes Pembayaran diterima tanggal 29 Sep 2026 pukul 13.42 — satu Gagal dengan alasan "Alamat tujuan membalas dengan kesalahan (kode 500)" dan dua Terkirim. Setiap baris punya tombol Kirim ulang.

StatusArtinya
TerkirimAlamat tujuan menerima kiriman
MenungguAntre untuk dikirim
Dicoba ulangGagal, akan dicoba lagi otomatis
GagalSudah dicoba berkali-kali selama ±2 hari dan tetap gagal. Kiriman tes hanya dicoba sekali, jadi langsung Gagal bila tidak berhasil
DibatalkanWebhook dijeda atau dinonaktifkan sebelum sempat terkirim

Kirim ulang mengirim kejadian yang sama dengan webhook-id yang sama. Template n8n Billingvro otomatis mengabaikan kiriman yang sudah pernah diproses.

Solusi saat pengiriman gagal

Pesan di BillingvroArtinya
Alamat tujuan tidak menjawab dalam 10 detikPenerima tidak membalas dalam 10 detik. Wajib membalas dulu, baru memproses.
Alamat tujuan tidak ditemukanNama domain salah atau belum aktif.
Alamat tujuan menolak sambunganPenerima tidak menyala.
Sertifikat keamanan (SSL) alamat tujuan bermasalahSertifikat https penerima kedaluwarsa atau salah pasang.
Alamat tujuan mengalihkan ke alamat lainPengalihan tidak diikuti. Pakai alamat akhirnya (mis. https://, bukan http://).
Alamat tujuan membalas dengan kesalahan (kode 404)Alamat URL salah, atau penerima belum aktif.
Alamat tujuan membalas dengan kesalahan (kode 500)Penerima menerima kiriman tetapi gagal memprosesnya.
Alamat tujuan mengarah ke jaringan lokalWajib alamat publik yang bisa dijangkau dari internet.

Webhook dinonaktifkan otomatis

Bila alamat tujuan gagal terus lebih dari 3 hari, Billingvro menonaktifkan webhook itu dan mengirim email ke pemilik akun. Setelah penerima beres, klik Aktifkan di kartu webhook, lalu kirim ulang kejadian yang terlewat dari Riwayat.

Ganti kunci rahasia

Klik Ganti kunci bila kunci rahasia pernah terlihat orang lain. Kunci baru langsung dipakai, dan kunci lama masih diterima selama 24 jam — waktu untuk memperbarui kunci di penerima tanpa ada kiriman yang tertolak.

Bentuk kiriman

Setiap kejadian dikirim sebagai POST berisi JSON ke Alamat URL tujuan. Bentuk isinya selalu sama:

KolomIsi
idNomor unik kejadian. Sama persis pada setiap percobaan ulang dan kirim ulang.
typeJenis kejadian, mis. payment.received.
timestampWaktu kejadian (UTC, format ISO).
testHanya ada (bernilai true) pada kiriman dari tombol Kirim tes. Datanya fiktif.
dataIsi kejadian — lihat Contoh isi setiap kejadian.

Header yang ikut dikirim:

HeaderIsi
webhook-idSama dengan kolom id. Dipakai untuk membuang kiriman dobel.
webhook-timestampWaktu pengiriman (detik sejak 1970).
webhook-signatureTanda tangan, mis. v1,K8s…=. Bisa berisi dua tanda tangan dipisah spasi saat kunci baru saja diganti.
content-typeapplication/json
user-agentBillingvro-Webhooks/1

No. WhatsApp dan alamat pelanggan

Kolom whatsapp_number dan address hanya ada bila Sertakan kontak pelanggan diaktifkan di pengaturan webhook. Kata sandi PPPoE, sandi voucher, NIK, dan koordinat tidak pernah dikirim.

Objek customer

KolomIsi
idID pelanggan di Billingvro
servicepppoe atau hotspot
nameNama pelanggan
usernameUsername PPPoE, atau kode voucher langganan untuk Hotspot
whatsapp_numberNo. WhatsApp (format 62…) — bila kontak diizinkan
addressAlamat — bila kontak diizinkan
statusactive (Aktif), suspended (Isolir), atau inactive (Nonaktif)
packagePaket: id, name, price (Rupiah, angka)
routerRouter PPPoE: id, name (null untuk Hotspot)
due_dateJatuh tempo berikutnya, YYYY-MM-DD (tanggal WIB)
expires_atHotspot: batas masa aktif (UTC, ISO)
created_atWaktu pelanggan ditambahkan (UTC, ISO)

Contoh isi setiap kejadian

Data di bawah fiktif, tetapi bentuknya persis sama dengan kiriman sebenarnya.

Pelanggan

customer.created — Pelanggan baru

Pelanggan PPPoE atau Hotspot ditambahkan dari aplikasi.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.created",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    }
  }
}

customer.updated — Data pelanggan diubah

Nama, No. WhatsApp, alamat, catatan, router, atau tanggal jatuh tempo diubah. changed_fields berisi kolom yang berubah. Perubahan status ke Nonaktif juga masuk ke sini dengan status di changed_fields.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.updated",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    },
    "changed_fields": [
      "whatsapp_number",
      "address"
    ]
  }
}

customer.package_changed — Ganti paket

Pelanggan pindah paket. customer.package adalah paket baru, previous_package paket lama.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.package_changed",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "name": "20 Mbps",
        "price": 250000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    },
    "previous_package": {
      "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
      "name": "10 Mbps",
      "price": 150000
    }
  }
}

customer.deleted — Pelanggan dihapus

Pelanggan dipindahkan ke Data Terhapus.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.deleted",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "inactive",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    }
  }
}

customer.import_completed — Impor pelanggan selesai

Impor pelanggan dari MikroTik selesai. Dikirim sekali per impor, bukan per pelanggan.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.import_completed",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "import": {
      "source": "mikrotik",
      "total": 120,
      "imported": 118,
      "skipped": 2,
      "failed": 0,
      "categories": [
        {
          "service": "pppoe",
          "status": "complete",
          "total": 120,
          "imported": 118,
          "skipped": 2,
          "failed": 0
        }
      ],
      "started_at": "2026-09-29T03:00:00.000Z",
      "finished_at": "2026-09-29T03:01:12.000Z"
    }
  }
}

Tagihan & Pembayaran

invoice.created — Tagihan dibuat

Tagihan PPPoE terbit (tagihan bulan pertama saat pelanggan ditambahkan, atau tagihan bulanan otomatis). Pelanggan Hotspot prabayar tidak punya tagihan.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "invoice.created",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "invoice": {
      "id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c",
      "number": null,
      "amount": 150000,
      "status": "pending",
      "period_start": "2026-10-05",
      "period_end": "2026-11-05",
      "due_date": "2026-10-05",
      "paid_at": null,
      "created_at": "2026-10-02T17:15:00.000Z"
    },
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    }
  }
}

invoice.overdue — Tagihan lewat jatuh tempo

Tagihan PPPoE belum dibayar setelah lewat tanggal jatuh tempo.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "invoice.overdue",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "invoice": {
      "id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c",
      "number": null,
      "amount": 150000,
      "status": "overdue",
      "period_start": "2026-10-05",
      "period_end": "2026-11-05",
      "due_date": "2026-10-05",
      "paid_at": null,
      "created_at": "2026-10-02T17:15:00.000Z"
    },
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    }
  }
}

payment.promised — Janji bayar

Pelanggan diberi janji bayar sampai tanggal tertentu.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "payment.promised",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    },
    "promise": {
      "until": "2026-10-10",
      "amount": 150000,
      "note": "Bayar setelah gajian",
      "invoice_id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a4f3e2d1c"
    }
  }
}

payment.received — Pembayaran diterima

Pembayaran pelanggan PPPoE atau Hotspot dicatat.

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "payment.received",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-11-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    },
    "payment": {
      "id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "amount": 150000,
      "method": "cash",
      "months": 1,
      "note": null,
      "paid_at": "2026-10-03T04:20:00.000Z",
      "previous_due_date": "2026-10-05",
      "new_due_date": "2026-11-05"
    }
  }
}

Isolir

customer.isolated — Pelanggan diisolir

Pelanggan diisolir. reason: manual (dari aplikasi), overdue (lewat masa tenggang), promise_expired (janji bayar lewat), expired (masa aktif Hotspot habis).

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.isolated",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "suspended",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-10-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    },
    "reason": "overdue"
  }
}

customer.reactivated — Pelanggan aktif kembali

Pelanggan aktif kembali. reason: manual, payment (setelah bayar), atau payment_promise (diberi janji bayar).

{
  "id": "evt_7f3a0c1e9b2d4e5f8a6b7c8d9e0f1a2b",
  "type": "customer.reactivated",
  "timestamp": "2026-10-03T04:20:00.000Z",
  "data": {
    "customer": {
      "id": "8d1b6a54-2f1e-4c3a-9b7d-0c5e2a1f9e10",
      "service": "pppoe",
      "name": "Budi Santoso",
      "username": "budi01",
      "whatsapp_number": "6281234567890",
      "address": "Jl. Melati No. 3, RT 03/RW 02",
      "status": "active",
      "package": {
        "id": "3f7c2d10-6a4b-4e5f-8c9d-1a2b3c4d5e6f",
        "name": "10 Mbps",
        "price": 150000
      },
      "router": {
        "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "MikroTik Utama"
      },
      "due_date": "2026-11-05",
      "expires_at": null,
      "created_at": "2026-09-05T02:15:00.000Z"
    },
    "reason": "payment"
  }
}

Balasan yang diharapkan

Wajib membalas dengan kode 2xx (mis. 200) dalam 10 detik. Selain itu dianggap gagal: kode 3xx (pengalihan tidak diikuti), 4xx, 5xx, atau tidak menjawab. Proses yang lama (mis. mengirim banyak WhatsApp) dijalankan setelah membalas.

Percobaan ulang

Kiriman gagal dicoba ulang otomatis setelah 1 menit, 5 menit, 30 menit, 2 jam, 6 jam, 12 jam, 24 jam (±10%) — total 8 kali dalam sekitar 2 hari. Setelah itu kiriman ditandai Gagal, tetapi masih bisa dikirim ulang manual dari Riwayat selama 30 hari.

Kalau alamat tujuan gagal terus selama lebih dari 3 hari (dan minimal 5 kali), webhook dinonaktifkan otomatis dan pemilik akun diberi tahu.

Wajib buang kiriman dobel

Percobaan ulang dan tombol Kirim ulang memakai webhook-id yang sama. Simpan webhook-id yang sudah diproses dan abaikan bila datang lagi, supaya pelanggan tidak menerima WhatsApp dua kali.

Urutan kiriman untuk satu webhook dijaga seadanya (mis. customer.created datang sebelum invoice.created dari penambahan yang sama), tetapi tidak dijamin saat ada percobaan ulang. Gunakan timestamp bila urutan penting.

Cek tanda tangan

Wajib dicek. Tanda tangan membuktikan kiriman benar-benar dari Billingvro dan isinya tidak diubah di jalan. Tolak kiriman yang tanda tangannya tidak cocok. Formatnya mengikuti standar terbuka Standard Webhooks, sehingga pustaka resmi standar itu juga bisa dipakai.

Cara menghitungnya:

  1. Ambil kunci rahasia (whsec_…), buang awalan whsec_, lalu decode base64 — itulah kunci HMAC.
  2. Susun teks: webhook-id + . + webhook-timestamp + . + isi kiriman mentah (persis seperti diterima, jangan di-parse ulang).
  3. Hitung HMAC-SHA256, lalu encode base64.
  4. Bandingkan dengan setiap bagian v1,… di header webhook-signature.
  5. Tolak bila webhook-timestamp berbeda lebih dari 5 menit dari jam sekarang.

Node.js

import crypto from "node:crypto";

export function verifyBillingvro(rawBody, headers, secret) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signatures = headers["webhook-signature"];
  if (!id || !timestamp || !signatures) return false;

  // Tolak kiriman lebih dari 5 menit (mencegah kiriman lama diputar ulang).
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto
    .createHmac("sha256", key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest("base64");

  return signatures.split(" ").some((part) => {
    const sig = part.split(",")[1] ?? "";
    return (
      sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
    );
  });
}

PHP

<?php
function verifyBillingvro(string $rawBody, array $headers, string $secret): bool
{
    $id = $headers['webhook-id'] ?? '';
    $timestamp = $headers['webhook-timestamp'] ?? '';
    $signatures = $headers['webhook-signature'] ?? '';
    if ($id === '' || $timestamp === '' || $signatures === '') {
        return false;
    }
    // Tolak kiriman lebih dari 5 menit (mencegah kiriman lama diputar ulang).
    if (abs(time() - (int) $timestamp) > 300) {
        return false;
    }
    $key = base64_decode(preg_replace('/^whsec_/', '', $secret));
    $expected = base64_encode(
        hash_hmac('sha256', $id . '.' . $timestamp . '.' . $rawBody, $key, true)
    );
    foreach (explode(' ', $signatures) as $part) {
        $sig = explode(',', $part, 2)[1] ?? '';
        if (hash_equals($expected, $sig)) {
            return true;
        }
    }
    return false;
}

// Contoh pemakaian di endpoint PHP:
// $raw = file_get_contents('php://input');
// $headers = array_change_key_case(getallheaders(), CASE_LOWER);
// if (!verifyBillingvro($raw, $headers, 'whsec_...')) { http_response_code(401); exit; }
// $event = json_decode($raw, true);

Python

import base64
import hashlib
import hmac
import time


def verify_billingvro(raw_body: bytes, headers: dict, secret: str) -> bool:
    msg_id = headers.get("webhook-id", "")
    timestamp = headers.get("webhook-timestamp", "")
    signatures = headers.get("webhook-signature", "")
    if not (msg_id and timestamp and signatures):
        return False
    # Tolak kiriman lebih dari 5 menit (mencegah kiriman lama diputar ulang).
    if abs(time.time() - int(timestamp)) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + raw_body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(
        hmac.compare_digest(expected, part.split(",", 1)[1])
        for part in signatures.split(" ")
        if "," in part
    )

Saat kunci rahasia diganti

Setelah Ganti kunci, selama 24 jam setiap kiriman ditandatangani dengan kunci lama dan kunci baru. Contoh kode di atas menerima salah satunya, sehingga kunci di penerima bisa diperbarui tanpa ada kiriman yang tertolak.

On this page