<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mutasibank Tech Blog]]></title><description><![CDATA[Tutorial lengkap integrasi API mutasi bank Indonesia. Webhook setup,
REST API examples, security best practices. Untuk developer &amp; technical team.]]></description><link>https://blog.mutasibank.co.id</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1759380527572/6d027630-5f5e-49d6-b739-9673a15380ea.png</url><title>Mutasibank Tech Blog</title><link>https://blog.mutasibank.co.id</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 02:36:11 GMT</lastBuildDate><atom:link href="https://blog.mutasibank.co.id/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Cara Mengaktifkan Two-Factor Authentication (2FA) di Mutasibank]]></title><description><![CDATA[Published: 2025-10-02
Reading Time: 5 minutes
Category: Security, Tutorial
Tags: 2fa, security, google-authenticator, mutasibank, authentication

Pengantar
Keamanan akun adalah prioritas utama saat mengelola data finansial. Two-Factor Authentication ...]]></description><link>https://blog.mutasibank.co.id/cara-mengaktifkan-two-factor-authentication-2fa-di-mutasibank</link><guid isPermaLink="true">https://blog.mutasibank.co.id/cara-mengaktifkan-two-factor-authentication-2fa-di-mutasibank</guid><category><![CDATA[2FA]]></category><dc:creator><![CDATA[Mutasibank Tech Blog]]></dc:creator><pubDate>Thu, 02 Oct 2025 07:19:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759389304855/8bb1f86a-2efe-4128-bff6-e63584c2303f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Published:</strong> 2025-10-02</p>
<p><strong>Reading Time:</strong> 5 minutes</p>
<p><strong>Category:</strong> Security, Tutorial</p>
<p><strong>Tags:</strong> 2fa, security, google-authenticator, mutasibank, authentication</p>
<hr />
<h2 id="heading-pengantar">Pengantar</h2>
<p>Keamanan akun adalah prioritas utama saat mengelola data finansial. Two-Factor Authentication (2FA) menambahkan lapisan keamanan ekstra pada akun Mutasibank Anda dengan mengharuskan kode verifikasi 6 digit selain password saat login.</p>
<p>Dengan mengaktifkan 2FA, bahkan jika seseorang mengetahui password Anda, mereka <strong>tidak bisa login</strong> tanpa akses ke perangkat authenticator Anda.</p>
<p><strong>Dalam tutorial ini, Anda akan belajar:</strong></p>
<ul>
<li><p>✅ Cara mengaktifkan 2FA di Mutasibank</p>
</li>
<li><p>✅ Setup Google Authenticator atau Authy</p>
</li>
<li><p>✅ Backup recovery codes untuk keamanan</p>
</li>
<li><p>✅ Troubleshooting masalah umum 2FA</p>
</li>
</ul>
<hr />
<h2 id="heading-mengapa-2fa-itu-penting">Mengapa 2FA Itu Penting?</h2>
<h3 id="heading-statistik-keamanan">Statistik Keamanan:</h3>
<ul>
<li><p><strong>81% dari data breach</strong> disebabkan oleh password yang lemah atau dicuri</p>
</li>
<li><p><strong>2FA mengurangi risiko</strong> account takeover hingga <strong>99.9%</strong></p>
</li>
<li><p><strong>Banking systems</strong> wajib menggunakan multi-factor authentication (MFA)</p>
</li>
</ul>
<h3 id="heading-skenario-tanpa-2fa">Skenario Tanpa 2FA:</h3>
<p>Bayangkan password Anda bocor karena:</p>
<ul>
<li><p>Phishing email palsu</p>
</li>
<li><p>Malware di komputer</p>
</li>
<li><p>Database leak dari website lain yang menggunakan password sama</p>
</li>
</ul>
<p>❌ <strong>Tanpa 2FA:</strong> Hacker langsung bisa login dan akses semua data bank Anda ✅ <strong>Dengan 2FA:</strong> Hacker butuh perangkat fisik Anda untuk mendapat kode verifikasi</p>
<hr />
<h2 id="heading-persyaratan">Persyaratan</h2>
<p>Sebelum memulai, pastikan Anda punya:</p>
<ol>
<li><p><strong>Akun Mutasibank</strong> yang sudah terdaftar</p>
</li>
<li><p><strong>Smartphone</strong> (Android atau iOS)</p>
</li>
<li><p><strong>Aplikasi Authenticator</strong> - pilih salah satu:</p>
<ul>
<li><p>Google Authenticator (gratis, populer)</p>
</li>
<li><p>Authy (gratis, support multi-device &amp; backup)</p>
</li>
<li><p>Microsoft Authenticator (gratis)</p>
</li>
<li><p>1Password (berbayar, all-in-one)</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-langkah-1-download-aplikasi-authenticator">Langkah 1: Download Aplikasi Authenticator</h2>
<h3 id="heading-google-authenticator">Google Authenticator</h3>
<p><strong>Android:</strong></p>
<ol>
<li><p>Buka Google Play Store</p>
</li>
<li><p>Cari "Google Authenticator"</p>
</li>
<li><p>Install aplikasi dari Google LLC</p>
</li>
<li><p>Buka aplikasi</p>
</li>
</ol>
<p><strong>iOS:</strong></p>
<ol>
<li><p>Buka App Store</p>
</li>
<li><p>Cari "Google Authenticator"</p>
</li>
<li><p>Install aplikasi dari Google LLC</p>
</li>
<li><p>Buka aplikasi</p>
</li>
</ol>
<h3 id="heading-authy-alternatif-dengan-backup-cloud">Authy (Alternatif dengan Backup Cloud)</h3>
<p><strong>Keunggulan Authy:</strong></p>
<ul>
<li><p>✅ Multi-device support</p>
</li>
<li><p>✅ Cloud backup (jika HP hilang, codes tetap aman)</p>
</li>
<li><p>✅ Lebih user-friendly</p>
</li>
</ul>
<p><strong>Download:</strong></p>
<ul>
<li><p>Android: Play Store → "Authy"</p>
</li>
<li><p>iOS: App Store → "Authy"</p>
</li>
</ul>
<hr />
<h2 id="heading-langkah-2-login-ke-mutasibank-dashboard">Langkah 2: Login ke Mutasibank Dashboard</h2>
<ol>
<li><p>Buka browser dan kunjungi <strong>https://mutasibank.co.id</strong></p>
</li>
<li><p>Klik tombol <strong>"Login"</strong> di pojok kanan atas</p>
</li>
<li><p>Masukkan email dan password Anda</p>
</li>
<li><p>Klik <strong>"Login"</strong></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759389341455/b2048efe-3b91-4832-80b8-907c1f01c8c9.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-langkah-3-akses-pengaturan-keamanan">Langkah 3: Akses Pengaturan Keamanan</h2>
<p>Setelah login berhasil:</p>
<ol>
<li><p>Klik <strong>foto profil</strong> Anda di pojok kanan atas</p>
</li>
<li><p>Pilih menu <strong>"Profile"</strong> atau <strong>"Settings"</strong></p>
</li>
<li><p>Temukan opsi <strong>"Two-Factor Authentication"</strong> atau <strong>"Google 2FA"</strong></p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759389431819/991369aa-ac64-4056-a144-fd5e26539064.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-langkah-4-scan-qr-code">Langkah 4: Scan QR Code</h2>
<p>Di halaman 2FA, Anda akan melihat:</p>
<h3 id="heading-a-qr-code">A. QR Code</h3>
<ol>
<li><p>Buka aplikasi Google Authenticator di HP</p>
</li>
<li><p>Tap tombol <strong>"+"</strong> atau <strong>"Add account"</strong></p>
</li>
<li><p>Pilih <strong>"Scan QR code"</strong></p>
</li>
<li><p>Arahkan kamera ke QR code di layar komputer</p>
</li>
<li><p>Akun "Mutasibank" akan otomatis ditambahkan</p>
</li>
</ol>
<h3 id="heading-b-manual-entry-jika-qr-code-tidak-bisa-discan">B. Manual Entry (Jika QR Code Tidak Bisa Discan)</h3>
<p>Jika kamera tidak berfungsi atau ada error:</p>
<ol>
<li><p>Di Google Authenticator, pilih <strong>"Enter a setup key"</strong></p>
</li>
<li><p>Isi form:</p>
<ul>
<li><p><strong>Account name:</strong> Mutasibank</p>
</li>
<li><p><strong>Your key:</strong> [copy secret key dari website]</p>
</li>
<li><p><strong>Type of key:</strong> Time-based</p>
</li>
</ul>
</li>
<li><p>Tap <strong>"Add"</strong></p>
</li>
</ol>
<p><strong>Secret Key Format:</strong></p>
<pre><code class="lang-plaintext">JBSWY3DPEHPK3PXP
</code></pre>
<hr />
<h2 id="heading-langkah-5-simpan-recovery-codes">Langkah 5: Simpan Recovery Codes</h2>
<p>⚠️ <strong>SANGAT PENTING!</strong> Recovery codes adalah backup jika HP hilang atau rusak.</p>
<h3 id="heading-cara-menyimpan-recovery-codes">Cara Menyimpan Recovery Codes:</h3>
<ol>
<li><p><strong>Download</strong> file recovery codes (biasanya format <code>.txt</code>)</p>
</li>
<li><p><strong>Print</strong> dan simpan di tempat aman (bukan di komputer yang sama!)</p>
</li>
<li><p><strong>Screenshot</strong> dan simpan di password manager (1Password, Bitwarden)</p>
</li>
<li><p><strong>Catat</strong> di buku fisik (old-school tapi aman!)</p>
</li>
</ol>
<p><strong>Contoh Recovery Codes:</strong></p>
<pre><code class="lang-plaintext">1. 5d41402a-bc4b-1234
2. 7c9e6679-8a9b-5678
3. 98f13708-2c3d-9012
4. 3c59dc04-8e5f-3456
5. eccbc874-9f6a-7890
</code></pre>
<p><strong>⚠️ JANGAN:</strong></p>
<ul>
<li><p>Simpan di email (bisa di-hack)</p>
</li>
<li><p>Share ke orang lain</p>
</li>
<li><p>Simpan di cloud storage tanpa enkripsi</p>
</li>
</ul>
<hr />
<h2 id="heading-langkah-6-verifikasi-setup">Langkah 6: Verifikasi Setup</h2>
<p>Setelah scan QR code, sistem akan minta verifikasi:</p>
<ol>
<li><p>Lihat <strong>kode 6 digit</strong> di Google Authenticator</p>
<ul>
<li><p>Contoh: <code>123 456</code></p>
</li>
<li><p><strong>Kode berubah setiap 30 detik</strong></p>
</li>
</ul>
</li>
<li><p>Ketik kode tersebut di form verifikasi Mutasibank</p>
</li>
<li><p>Klik <strong>"Verify and Activate"</strong></p>
</li>
</ol>
<p>✅ <strong>Jika berhasil:</strong> Anda akan melihat pesan "2FA Successfully Enabled!"</p>
<p>❌ <strong>Jika error:</strong> Pastikan waktu di HP sama dengan server (automatic time zone)</p>
<hr />
<h2 id="heading-langkah-7-test-login-dengan-2fa">Langkah 7: Test Login dengan 2FA</h2>
<p>Untuk memastikan 2FA berfungsi:</p>
<ol>
<li><p><strong>Logout</strong> dari akun Mutasibank</p>
</li>
<li><p>Klik <strong>"Login"</strong> lagi</p>
</li>
<li><p>Masukkan email dan password seperti biasa</p>
</li>
<li><p><strong>Halaman baru akan muncul:</strong> "Enter 2FA Code"</p>
</li>
<li><p>Buka Google Authenticator di HP</p>
</li>
<li><p>Lihat kode 6 digit untuk Mutasibank</p>
</li>
<li><p>Ketik kode tersebut</p>
</li>
<li><p>Klik <strong>"Verify"</strong></p>
</li>
</ol>
<p>✅ <strong>Login berhasil!</strong> 2FA Anda sudah aktif!</p>
<hr />
<h2 id="heading-troubleshooting-masalah-umum">Troubleshooting: Masalah Umum</h2>
<h3 id="heading-1-kode-2fa-selalu-invalid">1. Kode 2FA Selalu Invalid</h3>
<p><strong>Penyebab:</strong> Waktu di HP tidak sinkron dengan server</p>
<p><strong>Solusi:</strong></p>
<pre><code class="lang-plaintext">Android:
Settings → System → Date &amp; time → Enable "Automatic date &amp; time"

iOS:
Settings → General → Date &amp; Time → Enable "Set Automatically"
</code></pre>
<p><strong>Verifikasi waktu server:</strong></p>
<ul>
<li><p>Mutasibank menggunakan UTC+7 (Jakarta Time)</p>
</li>
<li><p>Google Authenticator auto-sync ke time server</p>
</li>
</ul>
<h3 id="heading-2-hp-hilangrusak-tidak-bisa-login">2. HP Hilang/Rusak, Tidak Bisa Login</h3>
<p><strong>Solusi:</strong></p>
<p><strong>Opsi 1: Gunakan Recovery Code</strong></p>
<ol>
<li><p>Di halaman login 2FA, klik <strong>"Use recovery code"</strong></p>
</li>
<li><p>Masukkan salah satu recovery code yang Anda simpan</p>
</li>
<li><p>Login berhasil</p>
</li>
<li><p><strong>Segera setup 2FA lagi</strong> dengan HP baru</p>
</li>
</ol>
<p><strong>Opsi 2: Hubungi Support</strong></p>
<ul>
<li><p>WhatsApp: <strong>+62 856 120 5976</strong></p>
</li>
<li><p>Email: <strong>support@mutasibank.co.id</strong></p>
</li>
<li><p>Siapkan data verifikasi:</p>
<ul>
<li><p>Email terdaftar</p>
</li>
<li><p>Nomor HP</p>
</li>
<li><p>Screenshot KTP (untuk verifikasi identitas)</p>
</li>
<li><p>Transaksi terakhir (jika ada)</p>
</li>
</ul>
</li>
</ul>
<p>⏱️ <strong>Waktu respon:</strong> 1-24 jam (hari kerja)</p>
<h3 id="heading-3-kode-tidak-muncul-di-authenticator">3. Kode Tidak Muncul di Authenticator</h3>
<p><strong>Penyebab:</strong> Account belum ditambahkan dengan benar</p>
<p><strong>Solusi:</strong></p>
<ol>
<li><p>Hapus account "Mutasibank" dari Google Authenticator</p>
</li>
<li><p>Ulangi proses scan QR code atau manual entry</p>
</li>
<li><p>Pastikan secret key di-copy dengan benar (tanpa spasi)</p>
</li>
</ol>
<h3 id="heading-4-this-code-has-already-been-used">4. "This code has already been used"</h3>
<p><strong>Penyebab:</strong> Kode yang sama dipakai 2x (kode valid hanya 1x dalam 30 detik)</p>
<p><strong>Solusi:</strong></p>
<ul>
<li><p>Tunggu kode berikutnya (refresh otomatis setiap 30 detik)</p>
</li>
<li><p>Jangan spam klik "Verify"</p>
</li>
<li><p>Pastikan jam di HP akurat</p>
</li>
</ul>
<hr />
<h2 id="heading-best-practices-keamanan-2fa">Best Practices Keamanan 2FA</h2>
<h3 id="heading-do-lakukan">✅ DO (Lakukan):</h3>
<ol>
<li><p><strong>Backup recovery codes</strong> di 2-3 tempat berbeda</p>
</li>
<li><p><strong>Update Authenticator app</strong> secara berkala</p>
</li>
<li><p><strong>Enable biometric lock</strong> di Authenticator app (fingerprint/face ID)</p>
</li>
<li><p><strong>Gunakan password manager</strong> untuk simpan recovery codes terenkripsi</p>
</li>
<li><p><strong>Test recovery codes</strong> sebelum HP hilang (gunakan 1 code untuk test, simpan sisanya)</p>
</li>
</ol>
<h3 id="heading-dont-jangan">❌ DON'T (Jangan):</h3>
<ol>
<li><p><strong>Screenshot QR code</strong> dan simpan di galeri HP (bisa di-hack)</p>
</li>
<li><p><strong>Share secret key</strong> ke orang lain</p>
</li>
<li><p><strong>Gunakan SMS-based 2FA</strong> (vulnerable to SIM swap attack)</p>
</li>
<li><p><strong>Install Authenticator di emulator</strong> (security risk)</p>
</li>
<li><p><strong>Disable 2FA setelah aktif</strong> (kecuali ada alasan kuat)</p>
</li>
</ol>
<hr />
<h2 id="heading-faq-pertanyaan-yang-sering-diajukan">FAQ: Pertanyaan yang Sering Diajukan</h2>
<h3 id="heading-1-apakah-2fa-wajib-di-mutasibank">1. Apakah 2FA Wajib di Mutasibank?</h3>
<p>Saat ini 2FA <strong>opsional</strong>, tapi <strong>sangat disarankan</strong> untuk:</p>
<ul>
<li><p>✅ Akun dengan banyak bank terhubung</p>
</li>
<li><p>✅ Akun bisnis/perusahaan</p>
</li>
<li><p>✅ Akun yang diakses dari berbagai lokasi/IP</p>
</li>
</ul>
<p><strong>Rencana:</strong> 2FA akan menjadi <strong>mandatory</strong> untuk paket ADVANCE di Q2 2025.</p>
<h3 id="heading-2-bisakah-pakai-sms-untuk-2fa">2. Bisakah Pakai SMS untuk 2FA?</h3>
<p>Saat ini Mutasibank <strong>hanya support Authenticator-based 2FA</strong> (TOTP).</p>
<p><strong>Mengapa bukan SMS?</strong></p>
<ul>
<li><p>❌ SIM swap attack (hacker bisa pindah nomor Anda)</p>
</li>
<li><p>❌ SMS delay (kode bisa terlambat sampai)</p>
</li>
<li><p>❌ Biaya SMS untuk server</p>
</li>
<li><p>✅ Authenticator lebih aman &amp; gratis</p>
</li>
</ul>
<h3 id="heading-3-bagaimana-jika-ganti-hp-baru">3. Bagaimana Jika Ganti HP Baru?</h3>
<p><strong>Metode 1: Transfer via Authy (Recommended)</strong></p>
<ul>
<li><p>Authy support multi-device</p>
</li>
<li><p>Login dengan akun yang sama di HP baru</p>
</li>
<li><p>Semua codes otomatis tersinkron</p>
</li>
</ul>
<p><strong>Metode 2: Google Authenticator Export (Google Auth v5.0+)</strong></p>
<ol>
<li><p>HP lama: Google Authenticator → Menu → Export accounts</p>
</li>
<li><p>HP baru: Google Authenticator → Import accounts → Scan QR</p>
</li>
<li><p>Semua accounts ter-transfer</p>
</li>
</ol>
<p><strong>Metode 3: Setup Ulang Manual</strong></p>
<ol>
<li><p>Di Mutasibank, <strong>disable 2FA</strong> (butuh kode dari HP lama)</p>
</li>
<li><p><strong>Enable lagi</strong> dan scan QR code dengan HP baru</p>
</li>
<li><p>Simpan recovery codes baru</p>
</li>
</ol>
<h3 id="heading-4-apakah-2fa-mempengaruhi-akses-api">4. Apakah 2FA Mempengaruhi Akses API?</h3>
<p><strong>Tidak!</strong> 2FA hanya untuk <strong>web dashboard login</strong>.</p>
<p><strong>API Authentication</strong> tetap menggunakan:</p>
<ul>
<li><p>Bearer token</p>
</li>
<li><p>API key</p>
</li>
<li><p>Webhook signatures</p>
</li>
</ul>
<p><strong>Tidak ada perubahan</strong> pada integrasi API Anda.</p>
<h3 id="heading-5-bisakah-multiple-user-akses-dengan-2fa">5. Bisakah Multiple User Akses dengan 2FA?</h3>
<p><strong>Ya</strong>, tapi setiap user harus punya:</p>
<ul>
<li><p>✅ Email login terpisah (invite via dashboard)</p>
</li>
<li><p>✅ Authenticator setup sendiri</p>
</li>
<li><p>✅ Recovery codes masing-masing</p>
</li>
</ul>
<p><strong>Tidak bisa:</strong> Share 1 akun untuk multiple user (security risk!)</p>
<hr />
<h2 id="heading-keamanan-lanjutan-advanced">Keamanan Lanjutan (Advanced)</h2>
<h3 id="heading-multi-device-backup-dengan-authy">Multi-Device Backup dengan Authy</h3>
<p>Jika Anda sering ganti HP atau punya tablet/laptop:</p>
<ol>
<li><p>Download <strong>Authy</strong> (bukan Google Authenticator)</p>
</li>
<li><p>Setup 2FA Mutasibank dengan Authy</p>
</li>
<li><p><strong>Enable multi-device:</strong></p>
<ul>
<li>Authy Settings → Devices → Allow multi-device</li>
</ul>
</li>
<li><p>Install Authy di perangkat lain (tablet/PC)</p>
</li>
<li><p>Login dengan nomor HP yang sama</p>
</li>
<li><p><strong>Disable multi-device</strong> setelah setup (untuk keamanan)</p>
</li>
</ol>
<h3 id="heading-hardware-security-keys-future">Hardware Security Keys (Future)</h3>
<p><strong>Coming Soon:</strong> Mutasibank akan support hardware keys seperti:</p>
<ul>
<li><p>YubiKey</p>
</li>
<li><p>Titan Security Key</p>
</li>
<li><p>Nitrokey</p>
</li>
</ul>
<p><strong>Keunggulan:</strong></p>
<ul>
<li><p>Phishing-resistant</p>
</li>
<li><p>No battery required</p>
</li>
<li><p>Support USB, NFC, Bluetooth</p>
</li>
</ul>
<hr />
<h2 id="heading-kesimpulan">Kesimpulan</h2>
<p><strong>Two-Factor Authentication (2FA)</strong> adalah cara paling efektif melindungi akun Mutasibank Anda dari unauthorized access. Dengan hanya <strong>5 menit setup</strong>, Anda bisa:</p>
<p>✅ <strong>Kurangi risiko</strong> account takeover hingga 99.9% ✅ <strong>Lindungi data bank</strong> dari hacker ✅ <strong>Comply</strong> dengan standar keamanan banking ✅ <strong>Peace of mind</strong> saat mengelola transaksi</p>
<p><strong>Langkah Cepat:</strong></p>
<ol>
<li><p>Download Google Authenticator (2 menit)</p>
</li>
<li><p>Login → Settings → Security (1 menit)</p>
</li>
<li><p>Scan QR code (30 detik)</p>
</li>
<li><p>Simpan recovery codes (1 menit)</p>
</li>
<li><p>Test login (30 detik)</p>
</li>
</ol>
<p><strong>Total waktu:</strong> 5 menit untuk keamanan seumur hidup!</p>
<hr />
<h2 id="heading-resources">Resources</h2>
<h3 id="heading-download-authenticator-apps">Download Authenticator Apps:</h3>
<ul>
<li><p><strong>Google Authenticator:</strong></p>
<ul>
<li><p>Android: https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2</p>
</li>
<li><p>iOS: https://apps.apple.com/app/google-authenticator/id388497605</p>
</li>
</ul>
</li>
<li><p><strong>Authy:</strong></p>
<ul>
<li><p>Android: https://play.google.com/store/apps/details?id=com.authy.authy</p>
</li>
<li><p>iOS: https://apps.apple.com/app/authy/id494168017</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-related-articles">Related Articles:</h3>
<ul>
<li><p><a target="_blank" href="https://blog.mutasibank.co.id">REST API Mutasi Bank - Dokumentasi Lengkap</a></p>
</li>
<li><p><a target="_blank" href="https://blog.mutasibank.co.id">Webhook Signature Verification</a></p>
</li>
<li><p><a target="_blank" href="https://blog.mutasibank.co.id">Best Practices Keamanan API Banking</a></p>
</li>
</ul>
<h3 id="heading-mutasibank-resources">Mutasibank Resources:</h3>
<ul>
<li><p><strong>Homepage:</strong> https://mutasibank.co.id</p>
</li>
<li><p><strong>API Documentation:</strong> https://mutasibank.co.id/api/docs</p>
</li>
<li><p><strong>Support:</strong> support@mutasibank.co.id</p>
</li>
<li><p><strong>WhatsApp:</strong> +62 856 120 5976</p>
</li>
</ul>
<hr />
<h2 id="heading-call-to-action">Call to Action</h2>
<h3 id="heading-belum-punya-akun-mutasibank">Belum Punya Akun Mutasibank?</h3>
<p><strong>Daftar sekarang dan dapatkan:</strong></p>
<ul>
<li><p>✅ Free trial 7 hari (tanpa biaya di awal)</p>
</li>
<li><p>✅ Support 34 bank Indonesia</p>
</li>
<li><p>✅ API lengkap dengan dokumentasi</p>
</li>
<li><p>✅ Webhook dengan signature verification</p>
</li>
<li><p>✅ 24/7 monitoring otomatis</p>
</li>
</ul>
<p>👉 <a target="_blank" href="https://mutasibank.co.id/login"><strong>Coba Gratis 7 Hari →</strong></a></p>
<h3 id="heading-butuh-bantuan-setup-2fa">Butuh Bantuan Setup 2FA?</h3>
<p>Hubungi tim support kami:</p>
<ul>
<li><p>💬 WhatsApp: +62 856 120 5976</p>
</li>
<li><p>📧 Email: support@mutasibank.co.id</p>
</li>
<li><p>⏱️ Response time: 1-24 jam</p>
</li>
</ul>
<hr />
<p><strong>Tags:</strong> #2FA #TwoFactorAuthentication #Keamanan #GoogleAuthenticator #Authy #Mutasibank #SecurityTutorial #Banking #API</p>
<p><strong>Share artikel ini jika bermanfaat!</strong> 🔒🚀</p>
]]></content:encoded></item><item><title><![CDATA[Webhook Signature Verification: Panduan Keamanan untuk API Mutasi Bank]]></title><description><![CDATA[Protect your webhook endpoints dari spoofing attacks dengan HMAC-SHA256 signature verification


📌 TL;DR (Quick Summary)
Webhook tanpa signature verification = SANGAT BERBAHAYA! Siapa saja bisa kirim fake webhook dan manipulasi sistem Anda.
Learn ho...]]></description><link>https://blog.mutasibank.co.id/webhook-signature-verification-panduan-keamanan-untuk-api-mutasi-bank</link><guid isPermaLink="true">https://blog.mutasibank.co.id/webhook-signature-verification-panduan-keamanan-untuk-api-mutasi-bank</guid><category><![CDATA[programming]]></category><dc:creator><![CDATA[Mutasibank Tech Blog]]></dc:creator><pubDate>Thu, 02 Oct 2025 06:32:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759386712349/4b3c63b8-dd76-42c4-a012-896f3e31019e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Protect your webhook endpoints dari spoofing attacks dengan HMAC-SHA256 signature verification</p>
</blockquote>
<hr />
<h2 id="heading-tldr-quick-summary"><strong>📌 TL;DR (Quick Summary)</strong></h2>
<p>Webhook tanpa signature verification = <strong>SANGAT BERBAHAYA!</strong> Siapa saja bisa kirim fake webhook dan manipulasi sistem Anda.</p>
<p>Learn how to:</p>
<ul>
<li><p>✅ <strong>Verify HMAC-SHA256 signatures</strong> dengan benar</p>
</li>
<li><p>✅ <strong>Prevent replay attacks</strong> menggunakan timestamp validation</p>
</li>
<li><p>✅ <strong>Implement di 3 bahasa</strong>: PHP, Node.js, Python</p>
</li>
<li><p>✅ <strong>Production-ready code</strong> dengan error handling</p>
</li>
<li><p>✅ <strong>Test locally</strong> sebelum deploy</p>
</li>
</ul>
<p><strong>Reading time:</strong> 10 menit • <strong>Level:</strong> Intermediate • <strong>Security:</strong> Critical 🔒</p>
<hr />
<h2 id="heading-daftar-isi"><strong>Daftar Isi</strong></h2>
<ol>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/daffigusti/development/laravel/mutasibank/docs/BLOG_ARTICLE_02_DRAFT.md#kenapa-webhook-signature-penting">Kenapa Webhook Signature Penting?</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/daffigusti/development/laravel/mutasibank/docs/BLOG_ARTICLE_02_DRAFT.md#cara-kerja-hmac-sha256">Cara Kerja HMAC-SHA256</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/daffigusti/development/laravel/mutasibank/docs/BLOG_ARTICLE_02_DRAFT.md#implementation-guide">Implementation Guide</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/daffigusti/development/laravel/mutasibank/docs/BLOG_ARTICLE_02_DRAFT.md#testing-debugging">Testing &amp; Debugging</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/daffigusti/development/laravel/mutasibank/docs/BLOG_ARTICLE_02_DRAFT.md#common-mistakes">Common Mistakes</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/daffigusti/development/laravel/mutasibank/docs/BLOG_ARTICLE_02_DRAFT.md#faq">FAQ</a></p>
</li>
</ol>
<hr />
<h2 id="heading-kenapa-webhook-signature-penting"><strong>Kenapa Webhook Signature Penting?</strong></h2>
<h3 id="heading-scenario-tanpa-signature-verification"><strong>🚨 Scenario Tanpa Signature Verification:</strong></h3>
<p>Bayangkan Anda punya endpoint untuk auto-confirm order:</p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ DANGEROUS CODE - DON'T DO THIS!</span>
<span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// webhook.php</span>
$data = json_decode(file_get_contents(<span class="hljs-string">'php://input'</span>), <span class="hljs-literal">true</span>);

<span class="hljs-keyword">foreach</span> ($data[<span class="hljs-string">'data_mutasi'</span>] <span class="hljs-keyword">as</span> $tx) {
    <span class="hljs-keyword">if</span> ($tx[<span class="hljs-string">'amount'</span>] == <span class="hljs-number">100000</span>) {
        <span class="hljs-comment">// Auto-confirm order</span>
        confirmOrder($orderId);  <span class="hljs-comment">// 😱 SIAPA SAJA BISA TRIGGER INI!</span>
    }
}
</code></pre>
<p><strong>Attacker bisa:</strong></p>
<ol>
<li><p>Kirim fake POST request ke endpoint Anda</p>
</li>
<li><p>Dengan payload yang dibuat-buat (fake transaction)</p>
</li>
<li><p>Trigger auto-confirm untuk order yang belum dibayar</p>
</li>
<li><p><strong>FRAUD!</strong> 💸</p>
</li>
</ol>
<h3 id="heading-dengan-signature-verification"><strong>✅ Dengan Signature Verification:</strong></h3>
<pre><code class="lang-php"><span class="hljs-comment">// ✅ SECURE CODE</span>
<span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// 1. Verify signature FIRST</span>
<span class="hljs-keyword">if</span> (!verifySignature($payload, $signature, $secret)) {
    <span class="hljs-keyword">die</span>(<span class="hljs-string">'Invalid signature'</span>); <span class="hljs-comment">// Reject fake webhooks</span>
}

<span class="hljs-comment">// 2. THEN process (only if signature valid)</span>
$data = json_decode($payload, <span class="hljs-literal">true</span>);
confirmOrder($orderId); <span class="hljs-comment">// ✅ Aman!</span>
</code></pre>
<p><strong>Benefit:</strong></p>
<ul>
<li><p>🔒 Only webhooks dari Mutasibank yang diterima</p>
</li>
<li><p>🛡️ Protect dari spoofing/fraud</p>
</li>
<li><p>✅ Compliance dengan security best practices</p>
</li>
</ul>
<hr />
<h2 id="heading-cara-kerja-hmac-sha256"><strong>Cara Kerja HMAC-SHA256</strong></h2>
<h3 id="heading-what-is-hmac"><strong>What is HMAC?</strong></h3>
<p><strong>HMAC</strong> = Hash-based Message Authentication Code</p>
<p>Ini adalah cara untuk <strong>"menandatangani" message</strong> dengan secret key:</p>
<pre><code class="lang-plaintext">Signature = HMAC-SHA256(Message, Secret Key)
</code></pre>
<h3 id="heading-flow-diagram"><strong>Flow Diagram:</strong></h3>
<pre><code class="lang-plaintext">Mutasibank Server:
1. Ada transaksi baru
2. Buat payload JSON
3. Calculate: signature = hmac_sha256(payload, webhook_secret)
4. Send POST dengan headers:
   - X-Mutasibank-Signature: [signature]
   - X-Mutasibank-Timestamp: [unix_time]
   - Body: [payload]

Your Server:
5. Receive POST request
6. Extract signature dari header
7. Get raw payload dari request body
8. Calculate: expected = hmac_sha256(payload, webhook_secret)
9. Compare: expected == signature?
   - ✅ YES: Process webhook
   - ❌ NO: Reject (fake webhook!)
</code></pre>
<h3 id="heading-headers-yang-dikirim"><strong>Headers yang Dikirim:</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Header</strong></td><td><strong>Format</strong></td><td><strong>Purpose</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>X-Mutasibank-Signature</code></td><td>64 hex chars</td><td>HMAC-SHA256 signature</td></tr>
<tr>
<td><code>X-Mutasibank-Timestamp</code></td><td>Unix timestamp</td><td>Prevent replay attacks</td></tr>
<tr>
<td><code>X-Mutasibank-Webhook-Id</code></td><td>UUID</td><td>Webhook identifier</td></tr>
<tr>
<td><code>Content-Type</code></td><td>application/json</td><td>Payload format</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-implementation-guide"><strong>Implementation Guide</strong></h2>
<h3 id="heading-php-implementation-recommended"><strong>PHP Implementation (RECOMMENDED)</strong></h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">/**
 * Secure Webhook Handler with Signature Verification
 */</span>

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// CONFIGURATION</span>
<span class="hljs-comment">// ============================================================================</span>

<span class="hljs-comment">// Get from Mutasibank Dashboard → Webhook Settings</span>
define(<span class="hljs-string">'WEBHOOK_SECRET'</span>, getenv(<span class="hljs-string">'WEBHOOK_SECRET'</span>));
define(<span class="hljs-string">'TIMESTAMP_TOLERANCE'</span>, <span class="hljs-number">300</span>); <span class="hljs-comment">// 5 minutes</span>

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// STEP 1: EXTRACT HEADERS</span>
<span class="hljs-comment">// ============================================================================</span>

$signature = $_SERVER[<span class="hljs-string">'HTTP_X_MUTASIBANK_SIGNATURE'</span>] ?? <span class="hljs-string">''</span>;
$timestamp = $_SERVER[<span class="hljs-string">'HTTP_X_MUTASIBANK_TIMESTAMP'</span>] ?? <span class="hljs-number">0</span>;
$webhookId = $_SERVER[<span class="hljs-string">'HTTP_X_MUTASIBANK_WEBHOOK_ID'</span>] ?? <span class="hljs-string">''</span>;

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// STEP 2: VALIDATE HEADERS</span>
<span class="hljs-comment">// ============================================================================</span>

<span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($signature) || <span class="hljs-keyword">empty</span>($timestamp)) {
    http_response_code(<span class="hljs-number">401</span>);
    <span class="hljs-keyword">die</span>(json_encode([
        <span class="hljs-string">'error'</span> =&gt; <span class="hljs-string">'Missing required headers'</span>,
        <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">'X-Mutasibank-Signature and X-Mutasibank-Timestamp are required'</span>
    ]));
}

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// STEP 3: CHECK TIMESTAMP (Prevent Replay Attacks)</span>
<span class="hljs-comment">// ============================================================================</span>

$currentTime = time();
$timeDiff = abs($currentTime - $timestamp);

<span class="hljs-keyword">if</span> ($timeDiff &gt; TIMESTAMP_TOLERANCE) {
    http_response_code(<span class="hljs-number">401</span>);
    <span class="hljs-keyword">die</span>(json_encode([
        <span class="hljs-string">'error'</span> =&gt; <span class="hljs-string">'Request expired'</span>,
        <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Timestamp outside tolerance window (<span class="hljs-subst">{$timeDiff}</span>s difference)"</span>
    ]));
}

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// STEP 4: GET RAW PAYLOAD</span>
<span class="hljs-comment">// ============================================================================</span>

<span class="hljs-comment">// ⚠️ IMPORTANT: Use raw input, NOT $_POST!</span>
<span class="hljs-comment">// Signature is calculated on exact JSON bytes</span>
$payload = file_get_contents(<span class="hljs-string">'php://input'</span>);

<span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($payload)) {
    http_response_code(<span class="hljs-number">400</span>);
    <span class="hljs-keyword">die</span>(json_encode([<span class="hljs-string">'error'</span> =&gt; <span class="hljs-string">'Empty payload'</span>]));
}

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// STEP 5: VERIFY SIGNATURE</span>
<span class="hljs-comment">// ============================================================================</span>

<span class="hljs-comment">// Calculate expected signature</span>
$expectedSignature = hash_hmac(<span class="hljs-string">'sha256'</span>, $payload, WEBHOOK_SECRET);

<span class="hljs-comment">// ⚠️ Use hash_equals() for constant-time comparison</span>
<span class="hljs-comment">// DON'T use == or === (vulnerable to timing attacks!)</span>
<span class="hljs-keyword">if</span> (!hash_equals($expectedSignature, $signature)) {
    error_log(<span class="hljs-string">"Invalid webhook signature from webhook_id: <span class="hljs-subst">{$webhookId}</span>"</span>);

    http_response_code(<span class="hljs-number">401</span>);
    <span class="hljs-keyword">die</span>(json_encode([
        <span class="hljs-string">'error'</span> =&gt; <span class="hljs-string">'Invalid signature'</span>,
        <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">'Signature verification failed'</span>
    ]));
}

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// ✅ SIGNATURE VALID - PROCESS WEBHOOK</span>
<span class="hljs-comment">// ============================================================================</span>

$data = json_decode($payload, <span class="hljs-literal">true</span>);

<span class="hljs-comment">// Log successful webhook</span>
error_log(<span class="hljs-string">"Webhook verified successfully: <span class="hljs-subst">{$webhookId}</span>"</span>);

<span class="hljs-comment">// Process transactions</span>
<span class="hljs-keyword">foreach</span> ($data[<span class="hljs-string">'data_mutasi'</span>] <span class="hljs-keyword">as</span> $transaction) {
    $type = $transaction[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span> ? <span class="hljs-string">'MASUK'</span> : <span class="hljs-string">'KELUAR'</span>;

    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Processing [<span class="hljs-subst">{$type}</span>] Rp "</span> . number_format($transaction[<span class="hljs-string">'amount'</span>]) . <span class="hljs-string">"\n"</span>;

    <span class="hljs-keyword">if</span> ($transaction[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span>) {
        <span class="hljs-comment">// Handle incoming transaction</span>
        processIncomingPayment($transaction);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Handle outgoing transaction</span>
        processOutgoingPayment($transaction);
    }
}

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// SEND SUCCESS RESPONSE</span>
<span class="hljs-comment">// ============================================================================</span>

http_response_code(<span class="hljs-number">200</span>);
<span class="hljs-keyword">echo</span> json_encode([
    <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
    <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">'Webhook processed'</span>,
    <span class="hljs-string">'webhook_id'</span> =&gt; $webhookId,
    <span class="hljs-string">'transactions_processed'</span> =&gt; count($data[<span class="hljs-string">'data_mutasi'</span>])
]);

<span class="hljs-comment">// ============================================================================</span>
<span class="hljs-comment">// HELPER FUNCTIONS</span>
<span class="hljs-comment">// ============================================================================</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processIncomingPayment</span>(<span class="hljs-params">$transaction</span>) </span>{
    <span class="hljs-comment">// Your business logic here</span>
    <span class="hljs-comment">// Example: Auto-confirm order, send email, update database</span>

    <span class="hljs-comment">// Extract order ID from description</span>
    <span class="hljs-comment">// Example: "TRANSFER FROM CUSTOMER ORDER-12345"</span>
    <span class="hljs-keyword">if</span> (preg_match(<span class="hljs-string">'/ORDER-(\d+)/'</span>, $transaction[<span class="hljs-string">'description'</span>], $matches)) {
        $orderId = $matches[<span class="hljs-number">1</span>];

        <span class="hljs-comment">// Confirm order in your database</span>
        <span class="hljs-comment">// DB::table('orders')-&gt;where('id', $orderId)-&gt;update(['status' =&gt; 'paid']);</span>

        <span class="hljs-comment">// Send notification</span>
        <span class="hljs-comment">// sendWhatsApp($customer, "✅ Pembayaran order #{$orderId} confirmed!");</span>

        <span class="hljs-keyword">echo</span> <span class="hljs-string">"✅ Order #<span class="hljs-subst">{$orderId}</span> confirmed\n"</span>;
    }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processOutgoingPayment</span>(<span class="hljs-params">$transaction</span>) </span>{
    <span class="hljs-comment">// Handle debit transactions if needed</span>
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Debit transaction logged\n"</span>;
}
</code></pre>
<p><a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample/blob/main/webhook_with_signature.php"><strong>📥 Download complete example</strong></a></p>
<hr />
<h3 id="heading-nodejs-implementation-express"><strong>Node.js Implementation (Express)</strong></h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> crypto = <span class="hljs-built_in">require</span>(<span class="hljs-string">'crypto'</span>);

<span class="hljs-keyword">const</span> app = express();
app.use(express.json());
app.use(express.raw({ <span class="hljs-attr">type</span>: <span class="hljs-string">'application/json'</span> }));

<span class="hljs-comment">// Configuration</span>
<span class="hljs-keyword">const</span> WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
<span class="hljs-keyword">const</span> TIMESTAMP_TOLERANCE = <span class="hljs-number">300</span>; <span class="hljs-comment">// 5 minutes</span>

<span class="hljs-comment">// Webhook endpoint</span>
app.post(<span class="hljs-string">'/webhook/mutasibank'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-comment">// Extract headers</span>
    <span class="hljs-keyword">const</span> signature = req.headers[<span class="hljs-string">'x-mutasibank-signature'</span>] || <span class="hljs-string">''</span>;
    <span class="hljs-keyword">const</span> timestamp = <span class="hljs-built_in">parseInt</span>(req.headers[<span class="hljs-string">'x-mutasibank-timestamp'</span>]) || <span class="hljs-number">0</span>;
    <span class="hljs-keyword">const</span> webhookId = req.headers[<span class="hljs-string">'x-mutasibank-webhook-id'</span>] || <span class="hljs-string">''</span>;

    <span class="hljs-comment">// Validate headers</span>
    <span class="hljs-keyword">if</span> (!signature || !timestamp) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({
            <span class="hljs-attr">error</span>: <span class="hljs-string">'Missing signature headers'</span>
        });
    }

    <span class="hljs-comment">// Check timestamp (prevent replay attacks)</span>
    <span class="hljs-keyword">const</span> currentTime = <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>);
    <span class="hljs-keyword">const</span> timeDiff = <span class="hljs-built_in">Math</span>.abs(currentTime - timestamp);

    <span class="hljs-keyword">if</span> (timeDiff &gt; TIMESTAMP_TOLERANCE) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({
            <span class="hljs-attr">error</span>: <span class="hljs-string">'Request expired'</span>,
            <span class="hljs-attr">message</span>: <span class="hljs-string">`Timestamp outside tolerance (<span class="hljs-subst">${timeDiff}</span>s difference)`</span>
        });
    }

    <span class="hljs-comment">// Get raw payload</span>
    <span class="hljs-keyword">const</span> payload = <span class="hljs-built_in">JSON</span>.stringify(req.body);

    <span class="hljs-comment">// Calculate expected signature</span>
    <span class="hljs-keyword">const</span> expectedSignature = crypto
        .createHmac(<span class="hljs-string">'sha256'</span>, WEBHOOK_SECRET)
        .update(payload)
        .digest(<span class="hljs-string">'hex'</span>);

    <span class="hljs-comment">// Verify signature (constant-time comparison)</span>
    <span class="hljs-keyword">if</span> (!crypto.timingSafeEqual(
        Buffer.from(expectedSignature),
        Buffer.from(signature)
    )) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Invalid signature:'</span>, webhookId);
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({
            <span class="hljs-attr">error</span>: <span class="hljs-string">'Invalid signature'</span>
        });
    }

    <span class="hljs-comment">// ✅ Signature valid - process webhook</span>
    <span class="hljs-keyword">const</span> data = req.body;

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'✅ Webhook verified:'</span>, webhookId);

    <span class="hljs-comment">// Process transactions</span>
    data.data_mutasi.forEach(<span class="hljs-function"><span class="hljs-params">transaction</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (transaction.type === <span class="hljs-string">'CR'</span>) {
            <span class="hljs-comment">// Handle incoming payment</span>
            processIncomingPayment(transaction);
        }
    });

    res.json({
        <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">webhook_id</span>: webhookId,
        <span class="hljs-attr">transactions_processed</span>: data.data_mutasi.length
    });
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processIncomingPayment</span>(<span class="hljs-params">transaction</span>) </span>{
    <span class="hljs-comment">// Extract order ID</span>
    <span class="hljs-keyword">const</span> match = transaction.description.match(<span class="hljs-regexp">/ORDER-(\d+)/</span>);

    <span class="hljs-keyword">if</span> (match) {
        <span class="hljs-keyword">const</span> orderId = match[<span class="hljs-number">1</span>];
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`✅ Order #<span class="hljs-subst">${orderId}</span> confirmed`</span>);

        <span class="hljs-comment">// Your business logic:</span>
        <span class="hljs-comment">// - Update database</span>
        <span class="hljs-comment">// - Send email</span>
        <span class="hljs-comment">// - Send WhatsApp notification</span>
    }
}

app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Webhook server running on port 3000'</span>);
});
</code></pre>
<p><a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample/blob/main/webhook_nodejs.js"><strong>📥 Download complete example</strong></a></p>
<hr />
<h3 id="heading-python-implementation-flask"><strong>Python Implementation (Flask)</strong></h3>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, jsonify
<span class="hljs-keyword">import</span> hmac
<span class="hljs-keyword">import</span> hashlib
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> os

app = Flask(__name__)

<span class="hljs-comment"># Configuration</span>
WEBHOOK_SECRET = os.getenv(<span class="hljs-string">'WEBHOOK_SECRET'</span>).encode()
TIMESTAMP_TOLERANCE = <span class="hljs-number">300</span>  <span class="hljs-comment"># 5 minutes</span>

<span class="hljs-meta">@app.route('/webhook/mutasibank', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">webhook_handler</span>():</span>
    <span class="hljs-comment"># Extract headers</span>
    signature = request.headers.get(<span class="hljs-string">'X-Mutasibank-Signature'</span>, <span class="hljs-string">''</span>)
    timestamp = int(request.headers.get(<span class="hljs-string">'X-Mutasibank-Timestamp'</span>, <span class="hljs-number">0</span>))
    webhook_id = request.headers.get(<span class="hljs-string">'X-Mutasibank-Webhook-Id'</span>, <span class="hljs-string">''</span>)

    <span class="hljs-comment"># Validate headers</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> signature <span class="hljs-keyword">or</span> <span class="hljs-keyword">not</span> timestamp:
        <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">'error'</span>: <span class="hljs-string">'Missing signature headers'</span>}), <span class="hljs-number">401</span>

    <span class="hljs-comment"># Check timestamp (prevent replay attacks)</span>
    current_time = int(time.time())
    time_diff = abs(current_time - timestamp)

    <span class="hljs-keyword">if</span> time_diff &gt; TIMESTAMP_TOLERANCE:
        <span class="hljs-keyword">return</span> jsonify({
            <span class="hljs-string">'error'</span>: <span class="hljs-string">'Request expired'</span>,
            <span class="hljs-string">'time_diff'</span>: time_diff
        }), <span class="hljs-number">401</span>

    <span class="hljs-comment"># Get raw payload</span>
    payload = request.get_data()

    <span class="hljs-comment"># Calculate expected signature</span>
    expected_signature = hmac.new(
        WEBHOOK_SECRET,
        payload,
        hashlib.sha256
    ).hexdigest()

    <span class="hljs-comment"># Verify signature (constant-time comparison)</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> hmac.compare_digest(expected_signature, signature):
        print(<span class="hljs-string">f'Invalid signature: <span class="hljs-subst">{webhook_id}</span>'</span>)
        <span class="hljs-keyword">return</span> jsonify({<span class="hljs-string">'error'</span>: <span class="hljs-string">'Invalid signature'</span>}), <span class="hljs-number">401</span>

    <span class="hljs-comment"># ✅ Signature valid - process webhook</span>
    data = request.json

    print(<span class="hljs-string">f'✅ Webhook verified: <span class="hljs-subst">{webhook_id}</span>'</span>)

    <span class="hljs-comment"># Process transactions</span>
    <span class="hljs-keyword">for</span> transaction <span class="hljs-keyword">in</span> data[<span class="hljs-string">'data_mutasi'</span>]:
        <span class="hljs-keyword">if</span> transaction[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span>:
            process_incoming_payment(transaction)

    <span class="hljs-keyword">return</span> jsonify({
        <span class="hljs-string">'success'</span>: <span class="hljs-literal">True</span>,
        <span class="hljs-string">'webhook_id'</span>: webhook_id,
        <span class="hljs-string">'transactions_processed'</span>: len(data[<span class="hljs-string">'data_mutasi'</span>])
    })

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_incoming_payment</span>(<span class="hljs-params">transaction</span>):</span>
    <span class="hljs-comment"># Extract order ID from description</span>
    <span class="hljs-keyword">import</span> re
    match = re.search(<span class="hljs-string">r'ORDER-(\d+)'</span>, transaction[<span class="hljs-string">'description'</span>])

    <span class="hljs-keyword">if</span> match:
        order_id = match.group(<span class="hljs-number">1</span>)
        print(<span class="hljs-string">f'✅ Order #<span class="hljs-subst">{order_id}</span> confirmed'</span>)

        <span class="hljs-comment"># Your business logic:</span>
        <span class="hljs-comment"># - Update database</span>
        <span class="hljs-comment"># - Send notifications</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    app.run(port=<span class="hljs-number">3000</span>)
</code></pre>
<p><a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample/blob/main/webhook_python.py"><strong>📥 Download complete example</strong></a></p>
<hr />
<h2 id="heading-security-best-practices"><strong>Security Best Practices</strong></h2>
<h3 id="heading-do-must-implement"><strong>✅ DO (Must Implement!)</strong></h3>
<p><strong>1. Always Verify Signature</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ✅ Good</span>
<span class="hljs-keyword">if</span> (!hash_equals($expected, $signature)) {
    <span class="hljs-keyword">die</span>(<span class="hljs-string">'Invalid signature'</span>);
}

<span class="hljs-comment">// ❌ Bad - DON'T USE ==</span>
<span class="hljs-keyword">if</span> ($expected == $signature) { <span class="hljs-comment">// Vulnerable to timing attacks!</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p><strong>2. Check Timestamp (Prevent Replay Attacks)</strong></p>
<pre><code class="lang-php">$tolerance = <span class="hljs-number">300</span>; <span class="hljs-comment">// 5 minutes</span>
<span class="hljs-keyword">if</span> (abs(time() - $timestamp) &gt; $tolerance) {
    <span class="hljs-keyword">die</span>(<span class="hljs-string">'Request expired'</span>);
}
</code></pre>
<p><strong>3. Use Raw Payload</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ✅ Good</span>
$payload = file_get_contents(<span class="hljs-string">'php://input'</span>);

<span class="hljs-comment">// ❌ Bad</span>
$payload = json_encode($_POST); <span class="hljs-comment">// Wrong! Signature won't match</span>
</code></pre>
<p><strong>4. Constant-Time Comparison</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ✅ Good (PHP)</span>
hash_equals($expected, $signature)

<span class="hljs-comment">// ✅ Good (Node.js)</span>
crypto.timingSafeEqual(Buffer.<span class="hljs-keyword">from</span>(expected), Buffer.<span class="hljs-keyword">from</span>(signature))

<span class="hljs-comment">// ✅ Good (Python)</span>
hmac.compare_digest(expected, signature)

<span class="hljs-comment">// ❌ Bad - ALL languages</span>
$expected == $signature  <span class="hljs-comment">// Timing attack vulnerable!</span>
</code></pre>
<p><strong>5. HTTPS Only</strong></p>
<pre><code class="lang-plaintext">✅ https://yoursite.com/webhook
❌ http://yoursite.com/webhook (INSECURE!)
</code></pre>
<p><strong>6. Store Secret Securely</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># .env file</span>
WEBHOOK_SECRET=your_64_char_secret_here

<span class="hljs-comment"># ❌ DON'T hardcode in code</span>
define(<span class="hljs-string">'SECRET'</span>, <span class="hljs-string">'abc123...'</span>); // BAD!
</code></pre>
<hr />
<h3 id="heading-dont-common-mistakes"><strong>❌ DON'T (Common Mistakes!)</strong></h3>
<p><strong>1. Skip Signature Verification</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ NEVER DO THIS!</span>
$data = json_decode(file_get_contents(<span class="hljs-string">'php://input'</span>), <span class="hljs-literal">true</span>);
processWebhook($data); <span class="hljs-comment">// 😱 No verification = DANGEROUS!</span>
</code></pre>
<p><strong>2. Use Wrong Comparison</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ Vulnerable to timing attacks</span>
<span class="hljs-keyword">if</span> ($expected == $signature) { ... }

<span class="hljs-comment">// ✅ Use constant-time</span>
<span class="hljs-keyword">if</span> (hash_equals($expected, $signature)) { ... }
</code></pre>
<p><strong>3. Log Sensitive Data</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ DON'T log secret!</span>
error_log(<span class="hljs-string">"Secret: "</span> . WEBHOOK_SECRET);

<span class="hljs-comment">// ❌ DON'T log signature</span>
error_log(<span class="hljs-string">"Signature: "</span> . $signature);

<span class="hljs-comment">// ✅ Log only webhook ID</span>
error_log(<span class="hljs-string">"Webhook received: "</span> . $webhookId);
</code></pre>
<p><strong>4. Wrong Payload</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ Wrong - signature calculated on raw bytes</span>
$payload = json_encode($_POST);

<span class="hljs-comment">// ✅ Correct - use raw input</span>
$payload = file_get_contents(<span class="hljs-string">'php://input'</span>);
</code></pre>
<hr />
<h2 id="heading-testing-amp-debugging"><strong>Testing &amp; Debugging</strong></h2>
<h3 id="heading-local-testing-dengan-curl"><strong>Local Testing dengan cURL</strong></h3>
<p>Generate signature secara manual untuk testing:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># Configuration</span>
SECRET=<span class="hljs-string">"your_webhook_secret_64_chars"</span>
TIMESTAMP=$(date +%s)
PAYLOAD=<span class="hljs-string">'{"api_key":"test","account_id":1,"data_mutasi":[{"amount":100000}]}'</span>

<span class="hljs-comment"># Calculate signature</span>
SIGNATURE=$(<span class="hljs-built_in">echo</span> -n <span class="hljs-string">"<span class="hljs-variable">$PAYLOAD</span>"</span> | openssl dgst -sha256 -hmac <span class="hljs-string">"<span class="hljs-variable">$SECRET</span>"</span> | cut -d<span class="hljs-string">' '</span> -f2)

<span class="hljs-comment"># Send test webhook</span>
curl -X POST http://localhost:8000/webhook.php \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -H <span class="hljs-string">"X-Mutasibank-Signature: <span class="hljs-variable">$SIGNATURE</span>"</span> \
  -H <span class="hljs-string">"X-Mutasibank-Timestamp: <span class="hljs-variable">$TIMESTAMP</span>"</span> \
  -H <span class="hljs-string">"X-Mutasibank-Webhook-Id: test-webhook-123"</span> \
  -d <span class="hljs-string">"<span class="hljs-variable">$PAYLOAD</span>"</span>
</code></pre>
<p><strong>Expected response:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"success"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"webhook_id"</span>: <span class="hljs-string">"test-webhook-123"</span>,
  <span class="hljs-attr">"transactions_processed"</span>: <span class="hljs-number">1</span>
}
</code></pre>
<hr />
<h3 id="heading-debug-checklist"><strong>Debug Checklist</strong></h3>
<p><strong>Webhook tidak terkirim?</strong></p>
<ol>
<li><p>✅ URL webhook publicly accessible (bukan localhost)?</p>
</li>
<li><p>✅ SSL certificate valid?</p>
</li>
<li><p>✅ Port 80/443 open (firewall)?</p>
</li>
<li><p>✅ Endpoint return 200 OK?</p>
</li>
</ol>
<p><strong>"Invalid signature" error?</strong></p>
<p>Common causes:</p>
<pre><code class="lang-php"><span class="hljs-comment">// 1. ❌ Pakai $_POST instead of raw input</span>
$payload = json_encode($_POST); <span class="hljs-comment">// WRONG!</span>
$payload = file_get_contents(<span class="hljs-string">'php://input'</span>); <span class="hljs-comment">// CORRECT!</span>

<span class="hljs-comment">// 2. ❌ Secret salah</span>
<span class="hljs-comment">// Check di Mutasibank dashboard, copy exactly!</span>

<span class="hljs-comment">// 3. ❌ Pakai == instead of hash_equals()</span>
<span class="hljs-keyword">if</span> ($a == $b) <span class="hljs-comment">// WRONG!</span>
<span class="hljs-keyword">if</span> (hash_equals($a, $b)) <span class="hljs-comment">// CORRECT!</span>
</code></pre>
<p><strong>"Request expired" error?</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// Server time tidak sync?</span>
<span class="hljs-comment">// Install NTP dan sync time</span>
sudo ntpdate -s time.nist.gov

<span class="hljs-comment">// Atau increase tolerance (production use 300 = 5 min)</span>
define(<span class="hljs-string">'TIMESTAMP_TOLERANCE'</span>, <span class="hljs-number">600</span>); <span class="hljs-comment">// 10 minutes (for testing)</span>
</code></pre>
<hr />
<h2 id="heading-common-mistakes-amp-solutions"><strong>Common Mistakes &amp; Solutions</strong></h2>
<h3 id="heading-mistake-1-using-for-comparison"><strong>Mistake #1: Using == for Comparison</strong></h3>
<p><strong>Problem:</strong></p>
<pre><code class="lang-php"><span class="hljs-keyword">if</span> ($expected == $signature) { <span class="hljs-comment">// ❌ Timing attack vulnerable!</span>
</code></pre>
<p><strong>Why bad?</strong></p>
<ul>
<li><p><code>==</code> operator return different time tergantung berapa karakter yang match</p>
</li>
<li><p>Attacker bisa brute-force signature character by character</p>
</li>
<li><p>Measuring response time → gradually discover signature</p>
</li>
</ul>
<p><strong>Solution:</strong></p>
<pre><code class="lang-php"><span class="hljs-keyword">if</span> (hash_equals($expected, $signature)) { <span class="hljs-comment">// ✅ Constant-time</span>
</code></pre>
<h3 id="heading-mistake-2-wrong-payload"><strong>Mistake #2: Wrong Payload</strong></h3>
<p><strong>Problem:</strong></p>
<pre><code class="lang-php">$payload = json_encode($_POST); <span class="hljs-comment">// ❌ Signature won't match!</span>
</code></pre>
<p><strong>Why bad?</strong></p>
<ul>
<li><p>Signature calculated on <strong>exact raw bytes</strong> dari request body</p>
</li>
<li><p><code>$_POST</code> is parsed &amp; might change formatting</p>
</li>
</ul>
<p><strong>Solution:</strong></p>
<pre><code class="lang-php">$payload = file_get_contents(<span class="hljs-string">'php://input'</span>); <span class="hljs-comment">// ✅ Raw input</span>
</code></pre>
<h3 id="heading-mistake-3-not-checking-timestamp"><strong>Mistake #3: Not Checking Timestamp</strong></h3>
<p><strong>Problem:</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ No timestamp check</span>
verifySignature($payload, $signature);
processWebhook($payload);
</code></pre>
<p><strong>Why bad?</strong></p>
<ul>
<li><p>Attacker bisa capture valid webhook</p>
</li>
<li><p>Replay it multiple times</p>
</li>
<li><p>Trigger duplicate processing</p>
</li>
</ul>
<p><strong>Solution:</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ✅ Check timestamp first</span>
<span class="hljs-keyword">if</span> (abs(time() - $timestamp) &gt; <span class="hljs-number">300</span>) {
    <span class="hljs-keyword">die</span>(<span class="hljs-string">'Expired'</span>);
}
</code></pre>
<h3 id="heading-mistake-4-logging-sensitive-data"><strong>Mistake #4: Logging Sensitive Data</strong></h3>
<p><strong>Problem:</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ❌ DON'T DO THIS!</span>
error_log(<span class="hljs-string">"Webhook secret: "</span> . WEBHOOK_SECRET);
error_log(<span class="hljs-string">"Full payload: "</span> . $payload); <span class="hljs-comment">// Might contain sensitive data</span>
</code></pre>
<p><strong>Solution:</strong></p>
<pre><code class="lang-php"><span class="hljs-comment">// ✅ Log only non-sensitive info</span>
error_log(<span class="hljs-string">"Webhook ID: <span class="hljs-subst">{$webhookId}</span>"</span>);
error_log(<span class="hljs-string">"Transactions count: "</span> . count($data[<span class="hljs-string">'data_mutasi'</span>]));
</code></pre>
<hr />
<h2 id="heading-production-deployment"><strong>Production Deployment</strong></h2>
<h3 id="heading-environment-variables"><strong>Environment Variables</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># .env</span>
WEBHOOK_SECRET=your_64_character_secret_from_dashboard
API_TOKEN=your_api_token
TIMESTAMP_TOLERANCE=300
ENABLE_LOGGING=<span class="hljs-literal">true</span>
</code></pre>
<h3 id="heading-server-requirements"><strong>Server Requirements</strong></h3>
<ul>
<li><p>✅ PHP 7.4+ / Node.js 14+ / Python 3.8+</p>
</li>
<li><p>✅ SSL certificate (HTTPS required)</p>
</li>
<li><p>✅ Port 443 open</p>
</li>
<li><p>✅ Firewall configured</p>
</li>
<li><p>✅ NTP time sync enabled</p>
</li>
</ul>
<h3 id="heading-monitoring"><strong>Monitoring</strong></h3>
<p><strong>Log these events:</strong></p>
<ul>
<li><p>✅ Webhook received (dengan webhook_id)</p>
</li>
<li><p>✅ Signature verification success/failure</p>
</li>
<li><p>✅ Transactions processed count</p>
</li>
<li><p>❌ DON'T log: secrets, signatures, sensitive data</p>
</li>
</ul>
<p><strong>Alert on:</strong></p>
<ul>
<li><p>⚠️ Multiple signature failures (potential attack)</p>
</li>
<li><p>⚠️ Webhook endpoint down</p>
</li>
<li><p>⚠️ Processing errors</p>
</li>
</ul>
<hr />
<h2 id="heading-faq"><strong>FAQ</strong></h2>
<h3 id="heading-q-apakah-wajib-implement-signature-verification"><strong>Q: Apakah wajib implement signature verification?</strong></h3>
<p><strong>A:</strong> <strong>YA, WAJIB!</strong> Tanpa ini, sistem Anda vulnerable terhadap fraud. Attacker bisa kirim fake webhooks dan manipulasi sistem Anda.</p>
<h3 id="heading-q-berapa-tolerance-timestamp-yang-aman"><strong>Q: Berapa tolerance timestamp yang aman?</strong></h3>
<p><strong>A:</strong> <strong>300 seconds (5 menit)</strong> adalah balance yang baik antara security dan tolerance untuk network delay. Jangan set terlalu besar (risiko replay attack).</p>
<h3 id="heading-q-dimana-dapat-webhook-secret"><strong>Q: Dimana dapat webhook secret?</strong></h3>
<p><strong>A:</strong> Di Mutasibank Dashboard:</p>
<ol>
<li><p>Login → Webhook Settings</p>
</li>
<li><p>Create webhook atau view existing</p>
</li>
<li><p>Secret akan ditampilkan (64 characters)</p>
</li>
<li><p><strong>Copy &amp; simpan di .env</strong> - Tidak bisa lihat lagi setelah close!</p>
</li>
</ol>
<h3 id="heading-q-webhook-secret-sama-untuk-semua-webhook"><strong>Q: Webhook secret sama untuk semua webhook?</strong></h3>
<p><strong>A:</strong> <strong>Tidak.</strong> Setiap webhook punya secret sendiri (auto-generated saat create). Jika punya multiple webhooks, simpan secret masing-masing.</p>
<h3 id="heading-q-bisa-regenerate-secret"><strong>Q: Bisa regenerate secret?</strong></h3>
<p><strong>A:</strong> Ya, tapi:</p>
<ol>
<li><p>Old secret langsung invalid</p>
</li>
<li><p>Webhook existing akan gagal verification</p>
</li>
<li><p>Harus update secret di server Anda</p>
</li>
<li><p><strong>Coordination needed</strong> untuk zero-downtime</p>
</li>
</ol>
<h3 id="heading-q-apa-itu-timing-attack"><strong>Q: Apa itu timing attack?</strong></h3>
<p><strong>A:</strong> Timing attack adalah teknik hacking yang measure response time untuk discover secret:</p>
<pre><code class="lang-php"><span class="hljs-comment">// Vulnerable code:</span>
<span class="hljs-keyword">if</span> ($a == $b) {
    <span class="hljs-comment">// Comparison stops at first different character</span>
    <span class="hljs-comment">// Different timing for different positions!</span>
}

<span class="hljs-comment">// Safe code:</span>
<span class="hljs-keyword">if</span> (hash_equals($a, $b)) {
    <span class="hljs-comment">// Always takes same time regardless of input</span>
    <span class="hljs-comment">// ✅ Secure!</span>
}
</code></pre>
<hr />
<h2 id="heading-kesimpulan"><strong>Kesimpulan</strong></h2>
<p>Webhook signature verification adalah <strong>critical security measure</strong> untuk protect sistem Anda dari fraud dan spoofing attacks.</p>
<p><strong>Key Takeaways:</strong></p>
<p>✅ <strong>Always verify</strong> HMAC-SHA256 signatures</p>
<p>✅ <strong>Check timestamp</strong> untuk prevent replay attacks</p>
<p>✅ <strong>Use constant-time comparison</strong> (hash_equals, timingSafeEqual)</p>
<p>✅ <strong>HTTPS only</strong> untuk webhook URLs ✅ <strong>Store secrets securely</strong> di environment variables</p>
<p>✅ <strong>Test thoroughly</strong> sebelum production</p>
<p><strong>Implementation time:</strong> 30-60 menit untuk basic setup</p>
<p><strong>Security impact:</strong> Protect dari potential fraud senilai jutaan rupiah! 🔒</p>
<hr />
<h2 id="heading-start-building-secure-webhooks"><strong>🚀 Start Building Secure Webhooks</strong></h2>
<p>Sudah punya account Mutasibank?</p>
<ul>
<li><p>✓ Login → Create webhook → Get secret</p>
</li>
<li><p>✓ Download sample code (PHP/Node.js/Python)</p>
</li>
<li><p>✓ Implement verification</p>
</li>
<li><p>✓ Test locally</p>
</li>
<li><p>✓ Deploy to production</p>
</li>
</ul>
<p><a target="_blank" href="https://mutasibank.co.id/login?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=webhook_security"><strong>👉 Daftar Gratis 7 Hari</strong></a></p>
<hr />
<h2 id="heading-resources"><strong>📚 Resources</strong></h2>
<p><strong>Code Examples:</strong></p>
<ul>
<li><p>💻 <a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample">GitHub Repository</a></p>
</li>
<li><p>📖 <a target="_blank" href="https://mutasibank.co.id/api/docs">API Documentation</a></p>
</li>
<li><p>📘 <a target="_blank" href="https://mutasibank.co.id/api/docs#security">Security Guide</a></p>
</li>
</ul>
<p><strong>Support:</strong></p>
<ul>
<li><p>💬 <a target="_blank" href="https://wa.me/628561205976">WhatsApp</a></p>
</li>
<li><p>📧 <a target="_blank" href="mailto:support@mutasibank.co.id">support@mutasibank.co.id</a></p>
</li>
</ul>
<p><strong>Related Articles:</strong></p>
<ul>
<li><p><a target="_blank" href="https://blog.mutasibank.co.id/rest-api-mutasi-bank-dokumentasi-lengkap-dengan-contoh-code">REST API Mutasi Bank - Dokumentasi Lengkap</a></p>
</li>
<li><p>Coming soon: Best Practices Security untuk Banking API</p>
</li>
<li><p>Coming soon: Error Handling &amp; Retry Logic</p>
</li>
</ul>
<hr />
<p><strong>Questions tentang webhook security?</strong> Drop a comment below! 👇</p>
<p><strong>Found this helpful?</strong> Share dengan developer friends! 🙏</p>
<hr />
<p><strong>Tags:</strong> #webhook #security #hmac #php #nodejs #python #api #banking #tutorial</p>
]]></content:encoded></item><item><title><![CDATA[REST API Mutasi Bank: Dokumentasi Lengkap dengan Contoh Code]]></title><description><![CDATA[📌 TL;DR (Quick Summary)
Butuh otomasi monitoring transaksi bank untuk sistem Anda? API Mutasibank menyediakan:

✅ 25+ REST API endpoints untuk complete automation

✅ 34 bank didukung (BCA, BRI, Mandiri, BNI, BSI, dll)

✅ Webhook callbacks dengan HMA...]]></description><link>https://blog.mutasibank.co.id/rest-api-mutasi-bank-dokumentasi-lengkap-dengan-contoh-code</link><guid isPermaLink="true">https://blog.mutasibank.co.id/rest-api-mutasi-bank-dokumentasi-lengkap-dengan-contoh-code</guid><category><![CDATA[api]]></category><category><![CDATA[REST]]></category><category><![CDATA[banking]]></category><category><![CDATA[indonesia]]></category><category><![CDATA[webhooks]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Python]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Mutasibank Tech Blog]]></dc:creator><pubDate>Thu, 02 Oct 2025 06:16:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759385692679/43bbf0ac-fe73-4b39-835f-5ebfd95b09fe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-tldr-quick-summary">📌 TL;DR (Quick Summary)</h2>
<p>Butuh otomasi monitoring transaksi bank untuk sistem Anda? API Mutasibank menyediakan:</p>
<ul>
<li><p>✅ <strong>25+ REST API endpoints</strong> untuk complete automation</p>
</li>
<li><p>✅ <strong>34 bank didukung</strong> (BCA, BRI, Mandiri, BNI, BSI, dll)</p>
</li>
<li><p>✅ <strong>Webhook callbacks</strong> dengan HMAC-SHA256 signature security</p>
</li>
<li><p>✅ <strong>Code examples</strong> siap pakai: PHP, Node.js, Python</p>
</li>
<li><p>✅ <strong>Gratis 7 hari trial</strong> untuk testing tanpa biaya</p>
</li>
</ul>
<p><strong>Reading time:</strong> 15 menit • <strong>Level:</strong> Intermediate</p>
<hr />
<h2 id="heading-daftar-isi">Daftar Isi</h2>
<ol>
<li><p><a class="post-section-overview" href="#apa-itu-api-mutasi-bank">Apa itu API Mutasi Bank?</a></p>
</li>
<li><p><a class="post-section-overview" href="#getting-started">Getting Started</a></p>
</li>
<li><p><a class="post-section-overview" href="#authentication">Authentication</a></p>
</li>
<li><p><a class="post-section-overview" href="#core-endpoints">Core Endpoints</a></p>
</li>
<li><p><a class="post-section-overview" href="#code-examples">Code Examples</a></p>
</li>
<li><p><a class="post-section-overview" href="#webhook-integration">Webhook Integration</a></p>
</li>
<li><p><a class="post-section-overview" href="#best-practices">Best Practices</a></p>
</li>
<li><p><a class="post-section-overview" href="#faq">FAQ</a></p>
</li>
</ol>
<hr />
<h2 id="heading-apa-itu-api-mutasi-bank">Apa itu API Mutasi Bank?</h2>
<p>Pernahkah Anda menghabiskan berjam-jam untuk <strong>manual checking transfer bank</strong> setiap hari? Atau kehilangan customer karena <strong>verifikasi pembayaran terlalu lama</strong>?</p>
<p>REST API Mutasi Bank adalah solusi untuk <strong>mengotomasi 100% proses monitoring transaksi bank</strong>. Dengan API ini, sistem Anda bisa:</p>
<h3 id="heading-use-cases-umum">🎯 Use Cases Umum:</h3>
<p><strong>E-Commerce &amp; Marketplace:</strong></p>
<ul>
<li><p>Auto-confirm order saat customer transfer</p>
</li>
<li><p>Match nominal dengan order ID</p>
</li>
<li><p>Kirim konfirmasi instant ke customer</p>
</li>
</ul>
<p><strong>Gaming &amp; Top-Up:</strong></p>
<ul>
<li><p>Deteksi pembayaran otomatis (5-10 menit)</p>
</li>
<li><p>Auto-process top-up saldo</p>
</li>
<li><p>Hemat waktu operasional hingga 80%</p>
</li>
</ul>
<p><strong>Fintech &amp; Payment Gateway:</strong></p>
<ul>
<li><p>Build payment verification system</p>
</li>
<li><p>Multi-bank support dalam 1 API</p>
</li>
<li><p>Real-time balance monitoring</p>
</li>
</ul>
<p><strong>UMKM &amp; Retail:</strong></p>
<ul>
<li><p>Monitor transaksi multi-cabang</p>
</li>
<li><p>Deteksi anomali transaksi</p>
</li>
<li><p>Laporan keuangan otomatis</p>
</li>
</ul>
<h3 id="heading-supported-banks-34-total">🏦 Supported Banks (34 Total):</h3>
<ul>
<li><p><strong>BCA</strong> (5 variants): KlikBCA, BCA API, BCA SNAP, BCA Giro, BCA Syariah</p>
</li>
<li><p><strong>BRI</strong> (6 variants): BRI IB, BRI Giro, BRI CMS, IBBIZ, Qlola</p>
</li>
<li><p><strong>Mandiri</strong> (6 variants): Livin', Mandiri IB, Mandiri Giro, MCM Corporate</p>
</li>
<li><p><strong>BNI</strong> (4 variants): BNI IB, BNI Mobile, BNI Giro, BNI Direct NG</p>
</li>
<li><p><strong>BSI</strong> (4 variants): BSI IB, BSI CMS, BSI Remittance, BSI CUZ</p>
</li>
<li><p><strong>Others</strong> (9 banks): Muamalat, CIMB, Sinarmas, Permata, Jago, dll</p>
</li>
</ul>
<p><a target="_blank" href="https://mutasibank.co.id/mutasi-bca-otomatis#all-banks">👉 Lihat daftar lengkap bank yang didukung</a></p>
<hr />
<h2 id="heading-getting-started">Getting Started</h2>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Sebelum mulai, pastikan Anda punya:</p>
<ul>
<li><p>✅ PHP 7.4+ / Node.js 14+ / Python 3.8+</p>
</li>
<li><p>✅ Account Mutasibank (<a target="_blank" href="https://mutasibank.co.id/login">daftar gratis 7 hari</a>)</p>
</li>
<li><p>✅ API Token (dari dashboard Mutasibank)</p>
</li>
<li><p>✅ Basic knowledge: HTTP, JSON, REST API</p>
</li>
</ul>
<h3 id="heading-step-1-get-your-api-token">Step 1: Get Your API Token</h3>
<ol>
<li><p>Login ke <a target="_blank" href="http://mutasibank.co.id">mutasibank.co.id</a></p>
</li>
<li><p>Go to <strong>Dashboard → API Key</strong></p>
</li>
<li><p>Copy your API token</p>
</li>
<li><p>Simpan di environment variable (jangan hardcode!)</p>
</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-comment"># .env</span>
MUTASIBANK_API_TOKEN=your_api_token_here
</code></pre>
<h3 id="heading-step-2-quick-test">Step 2: Quick Test</h3>
<p>Test API dengan cURL untuk memastikan token valid:</p>
<pre><code class="lang-bash">curl -X GET <span class="hljs-string">"https://mutasibank.co.id/api/v1/user"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_API_TOKEN"</span>
</code></pre>
<p><strong>Expected response:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"data"</span>: {
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"John Doe"</span>,
    <span class="hljs-attr">"email"</span>: <span class="hljs-string">"john@example.com"</span>,
    <span class="hljs-attr">"saldo"</span>: <span class="hljs-number">100000</span>,
    <span class="hljs-attr">"jenis_akun"</span>: <span class="hljs-string">"advance"</span>
  }
}
</code></pre>
<p>✅ <strong>Jika dapat response seperti ini, token Anda valid!</strong></p>
<hr />
<h2 id="heading-authentication">Authentication</h2>
<h3 id="heading-bearer-token-authentication">Bearer Token Authentication</h3>
<p>Semua API request memerlukan Bearer token di header:</p>
<pre><code class="lang-plaintext">Authorization: Bearer YOUR_API_TOKEN
</code></pre>
<h3 id="heading-security-best-practices">Security Best Practices</h3>
<p><strong>❌ Bad Practice (DON'T DO THIS):</strong></p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// Hardcoded token - DANGEROUS!</span>
$token = <span class="hljs-string">"abc123def456ghi789"</span>;
</code></pre>
<p><strong>✅ Good Practice (DO THIS):</strong></p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// Store in environment variable</span>
$token = getenv(<span class="hljs-string">'MUTASIBANK_API_TOKEN'</span>);

<span class="hljs-comment">// Or with Laravel</span>
$token = env(<span class="hljs-string">'MUTASIBANK_API_TOKEN'</span>);
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">// Node.js with dotenv</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();
<span class="hljs-keyword">const</span> token = process.env.MUTASIBANK_API_TOKEN;
</code></pre>
<pre><code class="lang-python"><span class="hljs-comment"># Python with python-dotenv</span>
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv

load_dotenv()
token = os.getenv(<span class="hljs-string">'MUTASIBANK_API_TOKEN'</span>)
</code></pre>
<h3 id="heading-error-responses">Error Responses</h3>
<p><strong>401 Unauthorized:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"User not found"</span>
}
</code></pre>
<p><strong>Solusi:</strong> Check token Anda di dashboard, pastikan masih valid.</p>
<hr />
<h2 id="heading-core-endpoints">Core Endpoints</h2>
<p>API Mutasibank menyediakan <strong>25+ endpoints</strong> untuk complete automation. Berikut endpoint-endpoint utama:</p>
<h3 id="heading-1-user-management">1. User Management</h3>
<h4 id="heading-get-apiv1user">GET /api/v1/user</h4>
<p>Get informasi user yang sedang login (balance, package, dll).</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-bash">curl -X GET <span class="hljs-string">"https://mutasibank.co.id/api/v1/user"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_TOKEN"</span>
</code></pre>
<p><strong>Response:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"data"</span>: {
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"John Doe"</span>,
    <span class="hljs-attr">"email"</span>: <span class="hljs-string">"john@example.com"</span>,
    <span class="hljs-attr">"saldo"</span>: <span class="hljs-number">100000</span>,
    <span class="hljs-attr">"jenis_akun"</span>: <span class="hljs-string">"advance"</span>
  }
}
</code></pre>
<hr />
<h3 id="heading-2-bank-accounts">2. Bank Accounts</h3>
<h4 id="heading-get-apiv1accounts">GET /api/v1/accounts</h4>
<p>Get semua bank account yang terdaftar.</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-bash">curl -X GET <span class="hljs-string">"https://mutasibank.co.id/api/v1/accounts"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_TOKEN"</span>
</code></pre>
<p><strong>Response:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"data"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-number">1</span>,
      <span class="hljs-attr">"unique_id"</span>: <span class="hljs-string">"abc123def"</span>,
      <span class="hljs-attr">"bank_name"</span>: <span class="hljs-string">"Bank BCA Personal"</span>,
      <span class="hljs-attr">"account_name"</span>: <span class="hljs-string">"PT Example Indonesia"</span>,
      <span class="hljs-attr">"account_number"</span>: <span class="hljs-string">"1234567890"</span>,
      <span class="hljs-attr">"is_active"</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">"last_check"</span>: <span class="hljs-string">"2025-10-02 10:30:00"</span>,
      <span class="hljs-attr">"last_balance"</span>: <span class="hljs-number">5000000</span>,
      <span class="hljs-attr">"status"</span>: <span class="hljs-string">"online"</span>
    }
  ]
}
</code></pre>
<h4 id="heading-post-apiv1accountcreate">POST /api/v1/account/create</h4>
<p>Daftarkan bank account baru untuk di-monitor.</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-bash">curl -X POST <span class="hljs-string">"https://mutasibank.co.id/api/v1/account/create"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_TOKEN"</span> \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{
    "bank_id": 1,
    "account_name": "PT Example",
    "account_no": "1234567890",
    "user_banking": "your_username",
    "password_banking": "your_password",
    "schedule_minutes": 10
  }'</span>
</code></pre>
<p><strong>Response:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Account created successfully"</span>,
  <span class="hljs-attr">"data"</span>: {
    <span class="hljs-attr">"id"</span>: <span class="hljs-number">123</span>,
    <span class="hljs-attr">"unique_id"</span>: <span class="hljs-string">"xyz789"</span>,
    <span class="hljs-attr">"bank_name"</span>: <span class="hljs-string">"Bank BCA Personal"</span>
  }
}
</code></pre>
<h4 id="heading-other-account-endpoints">Other Account Endpoints:</h4>
<ul>
<li><p><code>GET /api/v1/account/{id}</code> - Get single account</p>
</li>
<li><p><code>PUT /api/v1/account/{id}</code> - Update account</p>
</li>
<li><p><code>DELETE /api/v1/account/{id}</code> - Delete account</p>
</li>
<li><p><code>POST /api/v1/account/{id}/toggle</code> - Enable/disable monitoring</p>
</li>
<li><p><code>POST /api/v1/account/{id}/input_token</code> - Input BCA token (untuk BCA)</p>
</li>
<li><p><code>POST /api/v1/account/{id}/rerun</code> - Manually trigger check</p>
</li>
<li><p><code>GET /api/v1/account/{id}/log_bot</code> - Get bot activity logs</p>
</li>
</ul>
<hr />
<h3 id="heading-3-transactions-bank-statements">3. Transactions (Bank Statements)</h3>
<h4 id="heading-get-apiv1accountidstatements">GET /api/v1/account/{id}/statements</h4>
<p>Get transaction history dari bank account.</p>
<p><strong>Parameters:</strong></p>
<ul>
<li><p><code>start_date</code> (required): Format <code>Y-m-d</code> (e.g., <code>2025-01-01</code>)</p>
</li>
<li><p><code>end_date</code> (required): Format <code>Y-m-d</code></p>
</li>
</ul>
<p><strong>Request:</strong></p>
<pre><code class="lang-bash">curl -X GET <span class="hljs-string">"https://mutasibank.co.id/api/v1/account/1/statements?start_date=2025-10-01&amp;end_date=2025-10-02"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_TOKEN"</span>
</code></pre>
<p><strong>Response:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"error"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"data"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"uuid-transaction-id"</span>,
      <span class="hljs-attr">"unique_id"</span>: <span class="hljs-string">"TRX20251002001"</span>,
      <span class="hljs-attr">"transaction_date"</span>: <span class="hljs-string">"2025-10-02"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"TRANSFER FROM JOHN DOE ORDER-12345"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"CR"</span>,
      <span class="hljs-attr">"amount"</span>: <span class="hljs-number">150000</span>,
      <span class="hljs-attr">"balance"</span>: <span class="hljs-number">5150000</span>
    }
  ]
}
</code></pre>
<p><strong>Field explanation:</strong></p>
<ul>
<li><p><code>type</code>: <code>CR</code> = Credit (uang masuk), <code>DB</code> = Debit (uang keluar)</p>
</li>
<li><p><code>amount</code>: Nominal transaksi</p>
</li>
<li><p><code>balance</code>: Saldo setelah transaksi</p>
</li>
<li><p><code>description</code>: Keterangan dari bank</p>
</li>
</ul>
<h4 id="heading-post-apiv1accountidmatch">POST /api/v1/account/{id}/match</h4>
<p>Find transaction by amount (single result).</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-bash">curl -X POST <span class="hljs-string">"https://mutasibank.co.id/api/v1/account/1/match"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_TOKEN"</span> \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{"amount": 150000}'</span>
</code></pre>
<p><strong>Use case:</strong> Match pembayaran customer dengan order amount.</p>
<h4 id="heading-post-apiv1accountidmatchmultiple">POST /api/v1/account/{id}/match_multiple</h4>
<p>Find ALL transactions yang match dengan amount (multiple results).</p>
<p><strong>Use case:</strong> Untuk amount yang mungkin muncul berkali-kali (e.g., harga produk yang sama).</p>
<h4 id="heading-post-apiv1transactionidvalidate">POST /api/v1/transaction/{id}/validate</h4>
<p>Validate specific transaction by ID.</p>
<pre><code class="lang-bash">curl -X POST <span class="hljs-string">"https://mutasibank.co.id/api/v1/transaction/uuid-123/validate"</span> \
  -H <span class="hljs-string">"Authorization: Bearer YOUR_TOKEN"</span>
</code></pre>
<hr />
<h3 id="heading-4-webhooks">4. Webhooks</h3>
<h4 id="heading-get-apiv1webhooks">GET /api/v1/webhooks</h4>
<p>List all registered webhooks.</p>
<h4 id="heading-post-apiv1webhookcreate">POST /api/v1/webhook/create</h4>
<p>Create webhook untuk receive real-time notifications.</p>
<p><strong>Request:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://yoursite.com/webhook/mutasibank"</span>,
  <span class="hljs-attr">"bank_account_id"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Production webhook"</span>
}
</code></pre>
<p><strong>Response includes:</strong></p>
<ul>
<li><p>Webhook ID</p>
</li>
<li><p><strong>Webhook Secret</strong> (64 chars) - SIMPAN INI untuk signature verification!</p>
</li>
</ul>
<h4 id="heading-other-webhook-endpoints">Other Webhook Endpoints:</h4>
<ul>
<li><p><code>PUT /api/v1/webhook/{id}</code> - Update webhook</p>
</li>
<li><p><code>DELETE /api/v1/webhook/{id}</code> - Delete webhook</p>
</li>
<li><p><code>GET /api/v1/webhook/{id}</code> - Get webhook details</p>
</li>
</ul>
<hr />
<h3 id="heading-5-other-endpoints">5. Other Endpoints</h3>
<p><strong>Categories:</strong></p>
<ul>
<li><p><code>GET /api/v1/categories</code> - Transaction categories</p>
</li>
<li><p><code>POST /api/v1/category</code> - Create category</p>
</li>
<li><p><code>PUT /api/v1/category/{id}</code> - Update category</p>
</li>
<li><p><code>DELETE /api/v1/category/{id}</code> - Delete category</p>
</li>
</ul>
<p><strong>Billing:</strong></p>
<ul>
<li><code>POST /api/v1/topup</code> - Create top-up request</li>
</ul>
<p><a target="_blank" href="https://mutasibank.co.id/api/docs"><strong>📖 Lihat semua 25+ endpoints di Swagger UI</strong></a></p>
<hr />
<h2 id="heading-code-examples">Code Examples</h2>
<h3 id="heading-php-implementation">PHP Implementation</h3>
<p><strong>Install helper class:</strong></p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/daffigusti/mutasi_api_sample
<span class="hljs-built_in">cd</span> mutasi_api_sample
</code></pre>
<p><strong>Basic usage:</strong></p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-keyword">require_once</span> <span class="hljs-string">'Helper.php'</span>;

<span class="hljs-comment">// Initialize</span>
$api = <span class="hljs-keyword">new</span> MutasibankAPI(<span class="hljs-string">'your-api-token'</span>);

<span class="hljs-comment">// Get user info</span>
$user = $api-&gt;getUser();
<span class="hljs-keyword">echo</span> <span class="hljs-string">"Balance: Rp "</span> . number_format($user[<span class="hljs-string">'data'</span>][<span class="hljs-string">'saldo'</span>]) . <span class="hljs-string">"\n"</span>;

<span class="hljs-comment">// Get all accounts</span>
$accounts = $api-&gt;getAccounts();
<span class="hljs-keyword">foreach</span> ($accounts[<span class="hljs-string">'data'</span>] <span class="hljs-keyword">as</span> $account) {
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"<span class="hljs-subst">{$account['bank_name']}</span>: <span class="hljs-subst">{$account['account_number']}</span>\n"</span>;
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Balance: Rp "</span> . number_format($account[<span class="hljs-string">'last_balance'</span>]) . <span class="hljs-string">"\n"</span>;
}

<span class="hljs-comment">// Get transactions (last 7 days)</span>
$accountId = <span class="hljs-number">1</span>;
$transactions = $api-&gt;getStatements(
    $accountId,
    date(<span class="hljs-string">'Y-m-d'</span>, strtotime(<span class="hljs-string">'-7 days'</span>)),
    date(<span class="hljs-string">'Y-m-d'</span>)
);

<span class="hljs-keyword">foreach</span> ($transactions[<span class="hljs-string">'data'</span>] <span class="hljs-keyword">as</span> $tx) {
    $type = $tx[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span> ? <span class="hljs-string">'MASUK'</span> : <span class="hljs-string">'KELUAR'</span>;
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"[<span class="hljs-subst">{$type}</span>] <span class="hljs-subst">{$tx['description']}</span>: Rp "</span> . number_format($tx[<span class="hljs-string">'amount'</span>]) . <span class="hljs-string">"\n"</span>;
}

<span class="hljs-comment">// Match transaction by amount</span>
$amount = <span class="hljs-number">150000</span>;
$match = $api-&gt;matchTransaction($accountId, $amount);

<span class="hljs-keyword">if</span> (!$match[<span class="hljs-string">'error'</span>]) {
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Found transaction:\n"</span>;
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Description: <span class="hljs-subst">{$match['data']['description']}</span>\n"</span>;
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Date: <span class="hljs-subst">{$match['data']['transaction_date']}</span>\n"</span>;
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Transaction not found\n"</span>;
}

<span class="hljs-comment">// Create new account</span>
$newAccount = $api-&gt;createAccount([
    <span class="hljs-string">'bank_id'</span> =&gt; <span class="hljs-number">1</span>, <span class="hljs-comment">// BCA</span>
    <span class="hljs-string">'account_name'</span> =&gt; <span class="hljs-string">'PT Example'</span>,
    <span class="hljs-string">'account_no'</span> =&gt; <span class="hljs-string">'1234567890'</span>,
    <span class="hljs-string">'user_banking'</span> =&gt; <span class="hljs-string">'username_klikbca'</span>,
    <span class="hljs-string">'password_banking'</span> =&gt; <span class="hljs-string">'password_klikbca'</span>,
    <span class="hljs-string">'schedule_minutes'</span> =&gt; <span class="hljs-number">10</span>,
    <span class="hljs-string">'url_callback'</span> =&gt; <span class="hljs-string">'https://yoursite.com/webhook'</span>
]);

print_r($newAccount);
</code></pre>
<p><a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample/blob/main/Helper.php"><strong>📥 Download complete Helper.php</strong></a></p>
<hr />
<h3 id="heading-nodejs-implementation">Node.js Implementation</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> axios = <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>);

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MutasibankAPI</span> </span>{
    <span class="hljs-keyword">constructor</span>(apiToken) {
        <span class="hljs-built_in">this</span>.baseURL = <span class="hljs-string">'https://mutasibank.co.id/api/v1'</span>;
        <span class="hljs-built_in">this</span>.token = apiToken;
        <span class="hljs-built_in">this</span>.headers = {
            <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${apiToken}</span>`</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        };
    }

    <span class="hljs-comment">// Get user info</span>
    <span class="hljs-keyword">async</span> getUser() {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.baseURL}</span>/user`</span>, {
            <span class="hljs-attr">headers</span>: <span class="hljs-built_in">this</span>.headers
        });
        <span class="hljs-keyword">return</span> response.data;
    }

    <span class="hljs-comment">// Get all accounts</span>
    <span class="hljs-keyword">async</span> getAccounts() {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.baseURL}</span>/accounts`</span>, {
            <span class="hljs-attr">headers</span>: <span class="hljs-built_in">this</span>.headers
        });
        <span class="hljs-keyword">return</span> response.data;
    }

    <span class="hljs-comment">// Get transactions</span>
    <span class="hljs-keyword">async</span> getStatements(accountId, startDate, endDate) {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.get(
            <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.baseURL}</span>/account/<span class="hljs-subst">${accountId}</span>/statements`</span>,
            {
                <span class="hljs-attr">headers</span>: <span class="hljs-built_in">this</span>.headers,
                <span class="hljs-attr">params</span>: {
                    <span class="hljs-attr">start_date</span>: startDate,
                    <span class="hljs-attr">end_date</span>: endDate
                }
            }
        );
        <span class="hljs-keyword">return</span> response.data;
    }

    <span class="hljs-comment">// Match transaction</span>
    <span class="hljs-keyword">async</span> matchTransaction(accountId, amount) {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.post(
            <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">this</span>.baseURL}</span>/account/<span class="hljs-subst">${accountId}</span>/match`</span>,
            { amount },
            { <span class="hljs-attr">headers</span>: <span class="hljs-built_in">this</span>.headers }
        );
        <span class="hljs-keyword">return</span> response.data;
    }
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> MutasibankAPI(process.env.MUTASIBANK_TOKEN);

(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-comment">// Get accounts</span>
    <span class="hljs-keyword">const</span> accounts = <span class="hljs-keyword">await</span> api.getAccounts();
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Accounts:'</span>, accounts.data);

    <span class="hljs-comment">// Get today's transactions</span>
    <span class="hljs-keyword">const</span> today = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString().split(<span class="hljs-string">'T'</span>)[<span class="hljs-number">0</span>];
    <span class="hljs-keyword">const</span> transactions = <span class="hljs-keyword">await</span> api.getStatements(<span class="hljs-number">1</span>, today, today);
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Transactions:'</span>, transactions.data);

    <span class="hljs-comment">// Match transaction</span>
    <span class="hljs-keyword">const</span> match = <span class="hljs-keyword">await</span> api.matchTransaction(<span class="hljs-number">1</span>, <span class="hljs-number">150000</span>);
    <span class="hljs-keyword">if</span> (!match.error) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Found:'</span>, match.data);
    }
})();
</code></pre>
<hr />
<h3 id="heading-python-implementation">Python Implementation</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> date, timedelta
<span class="hljs-keyword">import</span> os

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MutasibankAPI</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, api_token</span>):</span>
        self.base_url = <span class="hljs-string">'https://mutasibank.co.id/api/v1'</span>
        self.token = api_token
        self.headers = {
            <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">f'Bearer <span class="hljs-subst">{api_token}</span>'</span>,
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
        }

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_user</span>(<span class="hljs-params">self</span>):</span>
        response = requests.get(
            <span class="hljs-string">f'<span class="hljs-subst">{self.base_url}</span>/user'</span>,
            headers=self.headers
        )
        <span class="hljs-keyword">return</span> response.json()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_accounts</span>(<span class="hljs-params">self</span>):</span>
        response = requests.get(
            <span class="hljs-string">f'<span class="hljs-subst">{self.base_url}</span>/accounts'</span>,
            headers=self.headers
        )
        <span class="hljs-keyword">return</span> response.json()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_statements</span>(<span class="hljs-params">self, account_id, start_date, end_date</span>):</span>
        response = requests.get(
            <span class="hljs-string">f'<span class="hljs-subst">{self.base_url}</span>/account/<span class="hljs-subst">{account_id}</span>/statements'</span>,
            headers=self.headers,
            params={
                <span class="hljs-string">'start_date'</span>: start_date,
                <span class="hljs-string">'end_date'</span>: end_date
            }
        )
        <span class="hljs-keyword">return</span> response.json()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">match_transaction</span>(<span class="hljs-params">self, account_id, amount</span>):</span>
        response = requests.post(
            <span class="hljs-string">f'<span class="hljs-subst">{self.base_url}</span>/account/<span class="hljs-subst">{account_id}</span>/match'</span>,
            headers=self.headers,
            json={<span class="hljs-string">'amount'</span>: amount}
        )
        <span class="hljs-keyword">return</span> response.json()

<span class="hljs-comment"># Usage</span>
api = MutasibankAPI(os.getenv(<span class="hljs-string">'MUTASIBANK_TOKEN'</span>))

<span class="hljs-comment"># Get user</span>
user = api.get_user()
print(<span class="hljs-string">f"Balance: Rp <span class="hljs-subst">{user[<span class="hljs-string">'data'</span>][<span class="hljs-string">'saldo'</span>]:,}</span>"</span>)

<span class="hljs-comment"># Get accounts</span>
accounts = api.get_accounts()
<span class="hljs-keyword">for</span> account <span class="hljs-keyword">in</span> accounts[<span class="hljs-string">'data'</span>]:
    print(<span class="hljs-string">f"<span class="hljs-subst">{account[<span class="hljs-string">'bank_name'</span>]}</span>: <span class="hljs-subst">{account[<span class="hljs-string">'account_number'</span>]}</span>"</span>)

<span class="hljs-comment"># Get last 7 days transactions</span>
today = date.today()
last_week = today - timedelta(days=<span class="hljs-number">7</span>)

transactions = api.get_statements(
    account_id=<span class="hljs-number">1</span>,
    start_date=last_week.strftime(<span class="hljs-string">'%Y-%m-%d'</span>),
    end_date=today.strftime(<span class="hljs-string">'%Y-%m-%d'</span>)
)

<span class="hljs-keyword">for</span> tx <span class="hljs-keyword">in</span> transactions[<span class="hljs-string">'data'</span>]:
    tx_type = <span class="hljs-string">'MASUK'</span> <span class="hljs-keyword">if</span> tx[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span> <span class="hljs-keyword">else</span> <span class="hljs-string">'KELUAR'</span>
    print(<span class="hljs-string">f"[<span class="hljs-subst">{tx_type}</span>] <span class="hljs-subst">{tx[<span class="hljs-string">'description'</span>]}</span>: Rp <span class="hljs-subst">{tx[<span class="hljs-string">'amount'</span>]:,}</span>"</span>)
</code></pre>
<hr />
<h2 id="heading-webhook-integration">Webhook Integration</h2>
<p>Webhook adalah cara paling efisien untuk menerima notifikasi transaksi <strong>tanpa perlu polling</strong>.</p>
<h3 id="heading-how-webhooks-work">How Webhooks Work</h3>
<pre><code class="lang-plaintext">Bank Transaction Occurs
         ↓
Mutasibank detects (5-10 min)
         ↓
Mutasibank sends POST to your URL
         ↓
Your server processes instantly
</code></pre>
<h3 id="heading-create-webhook">Create Webhook</h3>
<pre><code class="lang-php">$webhook = $api-&gt;createWebhook([
    <span class="hljs-string">'url'</span> =&gt; <span class="hljs-string">'https://yoursite.com/webhook/mutasibank'</span>,
    <span class="hljs-string">'bank_account_id'</span> =&gt; <span class="hljs-number">1</span>,
    <span class="hljs-string">'description'</span> =&gt; <span class="hljs-string">'Production webhook for account BCA'</span>
]);

<span class="hljs-comment">// IMPORTANT: Save webhook secret!</span>
$secret = $webhook[<span class="hljs-string">'data'</span>][<span class="hljs-string">'secret'</span>]; <span class="hljs-comment">// 64 characters</span>
<span class="hljs-comment">// Store this in environment variable</span>
</code></pre>
<h3 id="heading-webhook-payload">Webhook Payload</h3>
<p>Setiap kali ada transaksi baru, Mutasibank akan POST ke URL Anda:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"api_key"</span>: <span class="hljs-string">"your_api_token"</span>,
  <span class="hljs-attr">"account_id"</span>: <span class="hljs-number">123</span>,
  <span class="hljs-attr">"module"</span>: <span class="hljs-string">"bca"</span>,
  <span class="hljs-attr">"account_name"</span>: <span class="hljs-string">"PT Example"</span>,
  <span class="hljs-attr">"account_number"</span>: <span class="hljs-string">"1234567890"</span>,
  <span class="hljs-attr">"balance"</span>: <span class="hljs-number">5150000</span>,
  <span class="hljs-attr">"data_mutasi"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"uuid-tx-id"</span>,
      <span class="hljs-attr">"transaction_date"</span>: <span class="hljs-string">"2025-10-02 14:30:00"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"TRANSFER FROM CUSTOMER ORDER-12345"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"CR"</span>,
      <span class="hljs-attr">"amount"</span>: <span class="hljs-number">150000</span>,
      <span class="hljs-attr">"balance"</span>: <span class="hljs-number">5150000</span>
    }
  ]
}
</code></pre>
<h3 id="heading-webhook-security-critical">Webhook Security (CRITICAL!)</h3>
<p>🔒 <strong>Selalu verifikasi signature untuk prevent fraud!</strong></p>
<p><strong>Headers yang dikirim:</strong></p>
<pre><code class="lang-plaintext">X-Mutasibank-Signature: abc123...  (HMAC-SHA256 hash)
X-Mutasibank-Timestamp: 1696234567 (Unix timestamp)
X-Mutasibank-Webhook-Id: uuid-webhook-id
</code></pre>
<p><strong>PHP Verification:</strong></p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// Get headers</span>
$signature = $_SERVER[<span class="hljs-string">'HTTP_X_MUTASIBANK_SIGNATURE'</span>] ?? <span class="hljs-string">''</span>;
$timestamp = $_SERVER[<span class="hljs-string">'HTTP_X_MUTASIBANK_TIMESTAMP'</span>] ?? <span class="hljs-string">''</span>;

<span class="hljs-comment">// Get raw payload</span>
$payload = file_get_contents(<span class="hljs-string">'php://input'</span>);

<span class="hljs-comment">// Get webhook secret (from dashboard when creating webhook)</span>
$secret = getenv(<span class="hljs-string">'WEBHOOK_SECRET'</span>); <span class="hljs-comment">// 64 chars</span>

<span class="hljs-comment">// Verify timestamp (prevent replay attacks)</span>
<span class="hljs-keyword">if</span> (abs(time() - $timestamp) &gt; <span class="hljs-number">300</span>) { <span class="hljs-comment">// 5 minutes tolerance</span>
    http_response_code(<span class="hljs-number">401</span>);
    <span class="hljs-keyword">die</span>(json_encode([<span class="hljs-string">'error'</span> =&gt; <span class="hljs-string">'Request expired'</span>]));
}

<span class="hljs-comment">// Calculate expected signature</span>
$expectedSignature = hash_hmac(<span class="hljs-string">'sha256'</span>, $payload, $secret);

<span class="hljs-comment">// Verify signature (use constant-time comparison!)</span>
<span class="hljs-keyword">if</span> (!hash_equals($expectedSignature, $signature)) {
    http_response_code(<span class="hljs-number">401</span>);
    <span class="hljs-keyword">die</span>(json_encode([<span class="hljs-string">'error'</span> =&gt; <span class="hljs-string">'Invalid signature'</span>]));
}

<span class="hljs-comment">// ✅ Signature valid - process webhook</span>
$data = json_decode($payload, <span class="hljs-literal">true</span>);

<span class="hljs-keyword">foreach</span> ($data[<span class="hljs-string">'data_mutasi'</span>] <span class="hljs-keyword">as</span> $transaction) {
    <span class="hljs-comment">// Your business logic here</span>
    <span class="hljs-keyword">if</span> ($transaction[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span>) { <span class="hljs-comment">// Credit (uang masuk)</span>
        <span class="hljs-comment">// Auto-confirm order</span>
        $orderId = extractOrderId($transaction[<span class="hljs-string">'description'</span>]);
        confirmOrder($orderId, $transaction[<span class="hljs-string">'amount'</span>]);
    }
}

http_response_code(<span class="hljs-number">200</span>);
<span class="hljs-keyword">echo</span> json_encode([<span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>]);
</code></pre>
<p><a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample"><strong>📥 Download complete webhook examples (PHP, Node.js, Python)</strong></a></p>
<hr />
<h2 id="heading-best-practices">Best Practices</h2>
<h3 id="heading-1-security">1. Security</h3>
<p>✅ <strong>DO:</strong></p>
<ul>
<li><p>Always use HTTPS for webhook URLs</p>
</li>
<li><p>Verify HMAC signatures (use <code>hash_equals()</code>)</p>
</li>
<li><p>Check timestamp to prevent replay attacks</p>
</li>
<li><p>Store secrets in environment variables</p>
</li>
<li><p>Enable SSL verification in cURL</p>
</li>
<li><p>Log webhook activity (without sensitive data)</p>
</li>
</ul>
<p>❌ <strong>DON'T:</strong></p>
<ul>
<li><p>Use HTTP for webhooks (insecure!)</p>
</li>
<li><p>Skip signature verification (risk of fraud!)</p>
</li>
<li><p>Use <code>==</code> for signature comparison (timing attack vulnerable)</p>
</li>
<li><p>Hardcode API tokens in code</p>
</li>
<li><p>Log sensitive data (passwords, tokens)</p>
</li>
</ul>
<h3 id="heading-2-performance">2. Performance</h3>
<p>✅ <strong>Optimize dengan:</strong></p>
<ul>
<li><p>Cache API responses (5-10 min untuk balance)</p>
</li>
<li><p>Use webhooks instead of constant polling</p>
</li>
<li><p>Batch operations when possible</p>
</li>
<li><p>Implement queue for webhook processing</p>
</li>
<li><p>Set reasonable timeouts (30 seconds)</p>
</li>
</ul>
<h3 id="heading-3-error-handling">3. Error Handling</h3>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">callAPIWithRetry</span>(<span class="hljs-params">$callback, $maxRetries = <span class="hljs-number">3</span></span>) </span>{
    $attempt = <span class="hljs-number">0</span>;
    $delay = <span class="hljs-number">1</span>; <span class="hljs-comment">// seconds</span>

    <span class="hljs-keyword">while</span> ($attempt &lt; $maxRetries) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> $callback();
        } <span class="hljs-keyword">catch</span> (<span class="hljs-built_in">Exception</span> $e) {
            $attempt++;

            <span class="hljs-keyword">if</span> ($attempt &gt;= $maxRetries) {
                <span class="hljs-comment">// Log error</span>
                error_log(<span class="hljs-string">"API Error after <span class="hljs-subst">{$maxRetries}</span> retries: "</span> . $e-&gt;getMessage());
                <span class="hljs-keyword">throw</span> $e;
            }

            <span class="hljs-comment">// Exponential backoff: 1s, 2s, 4s</span>
            sleep($delay);
            $delay *= <span class="hljs-number">2</span>;
        }
    }
}

<span class="hljs-comment">// Usage</span>
$accounts = callAPIWithRetry(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) <span class="hljs-title">use</span> (<span class="hljs-params">$api</span>) </span>{
    <span class="hljs-keyword">return</span> $api-&gt;getAccounts();
});
</code></pre>
<h3 id="heading-4-production-checklist">4. Production Checklist</h3>
<p>Sebelum production, pastikan:</p>
<ul>
<li><p>[ ] API token disimpan di environment variable</p>
</li>
<li><p>[ ] Webhook signature verification implemented</p>
</li>
<li><p>[ ] SSL certificate valid untuk webhook URL</p>
</li>
<li><p>[ ] Error logging &amp; monitoring setup</p>
</li>
<li><p>[ ] Retry logic untuk API calls</p>
</li>
<li><p>[ ] Rate limiting awareness (60 req/min recommended)</p>
</li>
<li><p>[ ] Queue workers running (untuk webhook processing)</p>
</li>
<li><p>[ ] Alerting untuk critical errors</p>
</li>
</ul>
<hr />
<h2 id="heading-real-world-use-case-e-commerce-auto-confirm">Real-World Use Case: E-Commerce Auto-Confirm</h2>
<p>Contoh complete implementation untuk auto-confirm order:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// webhook_handler.php</span>

<span class="hljs-comment">// 1. Verify signature (lihat code di atas)</span>
verifyWebhookSignature();

<span class="hljs-comment">// 2. Parse payload</span>
$data = json_decode(file_get_contents(<span class="hljs-string">'php://input'</span>), <span class="hljs-literal">true</span>);

<span class="hljs-comment">// 3. Process each transaction</span>
<span class="hljs-keyword">foreach</span> ($data[<span class="hljs-string">'data_mutasi'</span>] <span class="hljs-keyword">as</span> $transaction) {
    <span class="hljs-keyword">if</span> ($transaction[<span class="hljs-string">'type'</span>] == <span class="hljs-string">'CR'</span>) { <span class="hljs-comment">// Uang masuk</span>
        <span class="hljs-comment">// Extract order ID from description</span>
        <span class="hljs-comment">// Example: "TRANSFER FROM CUSTOMER ORDER-12345"</span>
        preg_match(<span class="hljs-string">'/ORDER-(\d+)/'</span>, $transaction[<span class="hljs-string">'description'</span>], $matches);

        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">isset</span>($matches[<span class="hljs-number">1</span>])) {
            $orderId = $matches[<span class="hljs-number">1</span>];

            <span class="hljs-comment">// Find order in database</span>
            $order = DB::table(<span class="hljs-string">'orders'</span>)
                -&gt;where(<span class="hljs-string">'id'</span>, $orderId)
                -&gt;where(<span class="hljs-string">'total'</span>, $transaction[<span class="hljs-string">'amount'</span>])
                -&gt;where(<span class="hljs-string">'status'</span>, <span class="hljs-string">'pending'</span>)
                -&gt;first();

            <span class="hljs-keyword">if</span> ($order) {
                <span class="hljs-comment">// Update order status</span>
                DB::table(<span class="hljs-string">'orders'</span>)-&gt;where(<span class="hljs-string">'id'</span>, $orderId)-&gt;update([
                    <span class="hljs-string">'status'</span> =&gt; <span class="hljs-string">'paid'</span>,
                    <span class="hljs-string">'payment_proof'</span> =&gt; $transaction[<span class="hljs-string">'id'</span>],
                    <span class="hljs-string">'paid_at'</span> =&gt; $transaction[<span class="hljs-string">'transaction_date'</span>]
                ]);

                <span class="hljs-comment">// Send confirmation email</span>
                Mail::to($order-&gt;customer_email)-&gt;send(
                    <span class="hljs-keyword">new</span> OrderConfirmedEmail($order)
                );

                <span class="hljs-comment">// Send WhatsApp notification</span>
                WhatsApp::send(
                    $order-&gt;customer_phone,
                    <span class="hljs-string">"✅ Pembayaran order #<span class="hljs-subst">{$orderId}</span> telah dikonfirmasi! Terima kasih."</span>
                );

                <span class="hljs-comment">// Log success</span>
                Log::info(<span class="hljs-string">'Order auto-confirmed'</span>, [
                    <span class="hljs-string">'order_id'</span> =&gt; $orderId,
                    <span class="hljs-string">'amount'</span> =&gt; $transaction[<span class="hljs-string">'amount'</span>],
                    <span class="hljs-string">'tx_id'</span> =&gt; $transaction[<span class="hljs-string">'id'</span>]
                ]);
            }
        }
    }
}

http_response_code(<span class="hljs-number">200</span>);
<span class="hljs-keyword">echo</span> json_encode([<span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>]);
</code></pre>
<p><strong>Benefit:</strong></p>
<ul>
<li><p>⚡ Auto-confirm dalam 5-10 menit (sesuai schedule)</p>
</li>
<li><p>🤖 Zero manual work</p>
</li>
<li><p>📉 Reduce customer complaints</p>
</li>
<li><p>📈 Increase customer satisfaction</p>
</li>
</ul>
<hr />
<h2 id="heading-faq">FAQ</h2>
<h3 id="heading-q-berapa-rate-limit-api">Q: Berapa rate limit API?</h3>
<p><strong>A:</strong> Currently tidak ada hard limit, tapi kami recommend maksimal <strong>60 requests/minute</strong> untuk optimal performance.</p>
<h3 id="heading-q-apakah-ada-sandboxtesting-environment">Q: Apakah ada sandbox/testing environment?</h3>
<p><strong>A:</strong> Ya! Gunakan <strong>free 7-day trial</strong> untuk testing dengan real bank accounts. Atau bisa create test account dengan minimal balance.</p>
<h3 id="heading-q-bagaimana-billing-calculation">Q: Bagaimana billing calculation?</h3>
<p><strong>A:</strong> Harga per rekening per hari:</p>
<ul>
<li><p><strong>Standard:</strong> Rp 2.000/hari (untuk 10 bank tertentu)</p>
</li>
<li><p><strong>Advanced:</strong> Rp 3.500/hari (untuk 24 bank)</p>
</li>
</ul>
<p>Monitor 10 rekening = Rp 20.000-35.000/hari.</p>
<p><a target="_blank" href="https://mutasibank.co.id/#pricing">Lihat detail pricing</a></p>
<h3 id="heading-q-support-bank-apa-saja">Q: Support bank apa saja?</h3>
<p><strong>A:</strong> Total <strong>34 bank</strong> didukung, termasuk:</p>
<ul>
<li><p>BCA (5 variants)</p>
</li>
<li><p>BRI (6 variants)</p>
</li>
<li><p>Mandiri (6 variants)</p>
</li>
<li><p>BNI (4 variants)</p>
</li>
<li><p>BSI, CIMB, Permata, Muamalat, dll</p>
</li>
</ul>
<p><a target="_blank" href="https://mutasibank.co.id/#all-banks">Lihat daftar lengkap</a></p>
<h3 id="heading-q-webhook-retry-policy">Q: Webhook retry policy?</h3>
<p><strong>A:</strong> Jika webhook gagal terkirim, sistem auto-retry <strong>3 kali</strong> dengan exponential backoff:</p>
<ul>
<li><p>Retry 1: 1 second</p>
</li>
<li><p>Retry 2: 5 seconds</p>
</li>
<li><p>Retry 3: 25 seconds</p>
</li>
</ul>
<p>Jika tetap gagal, akan di-queue untuk manual retry.</p>
<h3 id="heading-q-maximum-transaction-history">Q: Maximum transaction history?</h3>
<p><strong>A:</strong> <strong>Unlimited</strong>. Semua data transaksi disimpan permanent untuk keperluan audit dan reporting.</p>
<h3 id="heading-q-apakah-bisa-real-time">Q: Apakah bisa real-time?</h3>
<p><strong>A:</strong> Tergantung bank dan paket:</p>
<ul>
<li><p><strong>Web scraping:</strong> 5-10 menit (sesuai schedule)</p>
</li>
<li><p><strong>BCA API/SNAP:</strong> Near real-time (1-2 menit)</p>
</li>
<li><p><strong>Webhook:</strong> Notifikasi instant setelah deteksi</p>
</li>
</ul>
<hr />
<h2 id="heading-kesimpulan">Kesimpulan</h2>
<p>REST API Mutasi Bank menyediakan solusi complete untuk otomasi monitoring transaksi bank:</p>
<p>✅ <strong>25+ endpoints</strong> untuk semua kebutuhan</p>
<p>✅ <strong>34 bank</strong> didukung dengan berbagai jenis rekening</p>
<p>✅ <strong>Webhook</strong> untuk real-time notifications</p>
<p>✅ <strong>Security</strong> dengan HMAC-SHA256 signatures</p>
<p>✅ <strong>Code examples</strong> ready-to-use untuk 3 bahasa</p>
<p>✅ <strong>Production-ready</strong> dengan retry logic &amp; error handling</p>
<hr />
<h2 id="heading-ready-to-automate-your-bank-monitoring">🚀 Ready to Automate Your Bank Monitoring?</h2>
<p>Daftar sekarang dan dapatkan:</p>
<ul>
<li><p>✓ <strong>7 hari trial GRATIS</strong> tanpa biaya di awal</p>
</li>
<li><p>✓ <strong>Full API access</strong> dengan 25+ endpoints</p>
</li>
<li><p>✓ <strong>34 banks supported</strong> (BCA, BRI, Mandiri, BNI, dll)</p>
</li>
<li><p>✓ <strong>Webhook integration</strong> dengan signature security</p>
</li>
<li><p>✓ <strong>Priority support</strong> via WhatsApp</p>
</li>
</ul>
<p><a target="_blank" href="https://mutasibank.co.id/login?utm_source=blog&amp;utm_medium=article&amp;utm_campaign=api_tutorial"><strong>👉 Daftar Gratis Sekarang</strong></a></p>
<hr />
<h2 id="heading-resources">📚 Resources</h2>
<p><strong>Documentation:</strong></p>
<ul>
<li><p>📖 <a target="_blank" href="https://mutasibank.co.id/api/docs">Interactive API Docs (Swagger UI)</a></p>
</li>
<li><p>💻 <a target="_blank" href="https://github.com/daffigusti/mutasi_api_sample">Sample Code Repository (GitHub)</a></p>
</li>
<li><p>📘 <a target="_blank" href="https://mutasibank.co.id/api/docs">Postman Collection</a></p>
</li>
</ul>
<p><strong>Support:</strong></p>
<ul>
<li><p>💬 <a target="_blank" href="https://wa.me/628561205976">WhatsApp Support</a></p>
</li>
<li><p>📧 Email: <a target="_blank" href="mailto:support@mutasibank.co.id">support@mutasibank.co.id</a></p>
</li>
<li><p>🌐 Website: <a target="_blank" href="http://mutasibank.co.id">mutasibank.co.id</a></p>
</li>
</ul>
<p><strong>Related Articles:</strong></p>
<ul>
<li><p>Coming soon: Webhook Signature Verification Tutorial</p>
</li>
<li><p>Coming soon: Best Practices Security untuk Banking API</p>
</li>
<li><p>Coming soon: Cara Integrasi dengan Laravel</p>
</li>
</ul>
<hr />
<p><strong>Questions?</strong> Drop a comment below atau hubungi kami via WhatsApp! 👇</p>
<p><strong>Found this helpful?</strong> Jangan lupa like dan share! 🙏</p>
<hr />
<p><strong>Tags:</strong> #api #rest #banking #indonesia #webhook #php #nodejs #python #tutorial #developer</p>
]]></content:encoded></item></channel></rss>