Blog · Terbit · Diperbarui

Menyimpan dan merotasi kunci API: dua rahasia yang arahnya berlawanan

Sebuah integrasi pembayaran memegang dua rahasia, dan keduanya sering disimpan di tempat yang sama dengan nama variabel yang mirip. Padahal arahnya berlawanan. API key adalah rahasia keluar: dikirim ke Kasera Pay untuk membuktikan siapa yang memanggil. Signing secret webhook adalah rahasia masuk: dipakai untuk memeriksa bahwa yang mengetuk endpoint memang Kasera Pay dan bukan orang lain yang menebak alamatnya.

Perbedaan arah itu bukan soal istilah. Dia menentukan di mana masing-masing disimpan, dan menentukan sesuatu yang lebih tajam lagi: cara merotasi keduanya bertolak belakang. Merotasi API key tidak punya masa tumpang tindih sama sekali, sehingga urutan langkahnya menentukan. Merotasi signing secret justru punya ekor sampai sekitar 33 jam, sehingga mematikan secret lama terlalu cepat adalah cara kehilangan pembayaran yang sudah lunas.

Empat nilai, bukan dua

Mode live dan mode tes sepenuhnya terpisah, jadi yang disimpan sebenarnya empat nilai:

Keempatnya tidak pernah saling menggantikan. Key tes tidak bisa mengambil pembayaran live dan sebaliknya, event mode tes hanya dikirim ke endpoint tes dan membawa livemode: false, dan secret tes tidak pernah bisa memverifikasi payload live. Kesalahan yang paling sering terjadi karena itu bukan lupa memverifikasi tanda tangan, melainkan memverifikasi dengan secret mode yang salah, lalu menyimpulkan bahwa kode verifikasinya yang rusak. Kesalahan lain yang membatalkan verifikasi dibahas di artikel webhook pembayaran yang aman.

Satu perbedaan praktis lagi, dan ini yang menentukan tempat penyimpanannya. API key ditampilkan penuh saat dibuat, lalu disembunyikan. Menampilkannya lagi perlu satu langkah tambahan yang tidak berlaku untuk signing secret, yang bisa dilihat kapan saja di dashboard. Artinya API key tetap wajib masuk ke penyimpanan yang bisa dibaca ulang oleh mesin sejak detik pertama. Yang berubah adalah akibat kalau itu tidak dilakukan: key yang hilang tidak lagi otomatis berarti rotasi.

Kalau API key hilang: tiga keadaan, tiga jawaban

Ini bagian yang paling sering diselesaikan dengan langkah yang terlalu besar. Menutup tab sebelum secret disalin bukan insiden keamanan, dan merotasi key yang sehat karena itu mematikan integrasi yang sedang berjalan. Bedakan dulu keadaannya.

Batas kewenangannya: tombolnya ada pada peran yang memang memegang API key, yaitu pemilik dan Developer, dan berlaku per mode, jadi key live dan key tes ditampilkan terpisah. Staf tidak melihat halaman Developer sama sekali. Pembagian perannya dibahas di panduan keamanan akun dan akses tim.

Yang tidak berubah: kemampuan menampilkan ulang bukan alasan menyimpan key dengan sembarangan. Dia memperbaiki satu kecelakaan kecil, yaitu secret yang tidak sempat disalin, dan tidak menggantikan secret manager, pembatasan akses, maupun langkah penanganan saat sebuah kunci benar-benar bocor.

Tempat menyimpannya

Aturannya satu kalimat: keempat nilai hanya boleh ada di sisi server, dan tidak satu pun boleh masuk ke repositori. Yang layak dipakai adalah environment variable yang diisi dari secret manager penyedia hosting, atau file konfigurasi di luar repositori yang izin bacanya dibatasi. Yang tidak layak, dan tetap sering terjadi:

Key tes tidak memindahkan uang, tetapi tetap sebuah rahasia. Key tes yang bocor membuka seluruh data uji, yang biasanya berisi nama dan email nyata dari pengujian internal.

Rotasi API key: tidak ada masa tumpang tindih

Ini bagian yang paling sering salah dijalankan, karena kebiasaan dari layanan lain tidak berlaku di sini. Sebagian penyedia mengizinkan dua key aktif bersamaan sehingga key baru bisa dipasang lebih dulu dan key lama dimatikan setelah semuanya dipastikan jalan. Di Kasera Pay hanya ada satu key aktif per mode, dan rotasi menerbitkan secret baru sekaligus mematikan yang lama seketika. Key yang dicabut berhenti bekerja saat itu juga, bukan setelah masa tenggang. Karena itu rotasi dijalankan saat memang diperlukan, yaitu ketika sebuah key bocor atau memang waktunya diputar, bukan setiap kali secretnya tidak sempat disalin.

Konsekuensinya, selalu ada jeda antara rotasi dan saat aplikasi benar-benar memakai nilai baru. Selama jeda itu setiap panggilan dijawab 401. Panjang jeda itulah yang harus ditekan, dan caranya bukan dengan bergerak cepat melainkan dengan menyiapkan urutannya lebih dulu:

Ada satu hal yang membuat jeda ini jauh lebih kecil risikonya daripada kelihatannya. Permintaan pembuatan pembayaran yang gagal karena 401 tidak pernah sampai ke tahap membuat apa pun, dan bila permintaan itu membawa header Idempotency-Key, mengulanginya dengan key API yang baru dan Idempotency-Key yang sama tetap menghasilkan satu pembayaran. Artinya biaya jeda rotasi adalah permintaan yang gagal dan perlu diulang, bukan tagihan ganda. Antrean retry yang benar akan menyelesaikannya sendiri beberapa detik kemudian. Cara memilih nilai key yang tahan retry dibahas di artikel tagihan ganda dan Idempotency-Key.

Rotasi signing secret: ada ekor sampai 33 jam

Arah masuk membuat masalahnya terbalik. Saat signing secret dirotasi, tidak semua pengiriman webhook yang beredar sudah selesai. Pengiriman yang belum dijawab 2xx dicoba ulang dengan jeda yang membesar, sampai 7 percobaan dalam rentang sekitar 33 jam. Sebuah event yang lahir sebelum rotasi karena itu bisa mengetuk endpoint satu hari penuh setelah rotasi dijalankan.

Yang menanggung selisih itu bukan kode di sisi penerima, melainkan sisi pengirim. Selama 24 jam setelah rotasi, header Kasera-Signature-V1 membawa dua entri v1 sekaligus, satu per secret, dan pengiriman diterima kalau salah satu entri cocok. Jadi tidak ada variabel kedua yang perlu dipasang berdampingan: handler yang masih memegang secret lama tetap lolos sampai jendela itu habis, dan handler yang sudah memegang secret baru lolos sejak detik pertama.

Jendelanya 24 jam sementara ekor percobaan ulangnya sekitar 33 jam, dan selisih itu satu-satunya jebakan yang tersisa: lewat jam ke-24 hanya secret baru yang menandatangani, termasuk untuk event lama yang masih diulang. Seluruh urutannya, beserta tiga bentuk verifikasi yang patah pada rotasi, ada di tulisan tentang merotasi signing secret tanpa kehilangan event.

Satu hal yang justru bukan alasan untuk merotasi: memindahkan URL webhook ke domain baru tidak mengganti secret. Migrasi domain karena itu tidak menuntut deploy ulang kode verifikasi. Sebaliknya, secret bisa dibuat sebelum URL apa pun diatur, sehingga handler bisa dibangun dan di-deploy lebih dulu, lalu domainnya diarahkan belakangan.

Kapan sebuah kunci perlu dirotasi

Rotasi berkala tanpa alasan lebih sering menciptakan insiden daripada mencegahnya, karena setiap rotasi API key membawa jeda 401 yang nyata. Yang menuntut rotasi adalah peristiwa, bukan tanggal: nilai key pernah muncul di repositori, riwayat commit, isu, tangkapan layar, chat, atau email; orang yang pernah memegangnya berhenti mengerjakan proyek itu; perangkat yang menyimpan konfigurasi produksi hilang; atau ada permintaan pada log yang tidak berasal dari sistem sendiri.

Kasus pertama layak ditekankan karena penanganannya sering keliru. Membersihkan riwayat commit bukan pencabutan. Selama key yang sama masih aktif, salinan yang sudah terlanjur di-clone tetap berfungsi. Urutannya selalu rotasi dulu, pembersihan riwayat kemudian, dan bukan sebaliknya.

Yang harus dilakukan saat kunci live bocor

Rotasi lebih dulu, sebelum menyelidiki apa pun. Pencabutan berlaku seketika, jadi ini langkah yang menghentikan kerusakan, dan penyelidikan bisa dilakukan setelahnya dengan tenang. Setelah itu, periksa apa yang sempat dilakukan dengan key tersebut.

Yang bisa dilakukan pemegang key live lebih sempit daripada yang dikhawatirkan, dan tetap perlu diperiksa. Yang bisa dilakukannya adalah membuat permintaan pembayaran atas nama merchant. Uangnya tetap mengalir ke saldo merchant dan tidak bisa dialihkan, karena pencairan hanya berjalan ke rekening bank terverifikasi milik merchant; kerugiannya reputasi, yaitu tautan pembayaran yang membawa nama toko dipakai untuk menagih orang lain. Riwayat transaksi adalah tempat memeriksanya, dan permintaan yang tidak dikenali sebaiknya dibatalkan.

Yang tidak bisa dilakukannya adalah mengeluarkan uang. Tidak ada endpoint pengembalian dana untuk QRIS maupun Virtual Account, jadi tidak ada jalur API yang memindahkan dana keluar dari saldo. Daftar transaksi karena itu satu-satunya yang perlu diperiksa setelah key diganti.

Terakhir, sebutkan request_id saat menghubungi dukungan. Setiap respons error membawa error.request_id yang mengulang header X-Request-Id, dan nilai itu yang membuat sebuah permintaan bisa ditemukan persis di log. Apa lagi yang layak menyertainya, dan apa yang tidak boleh ikut terkirim, dirangkum di tulisan tentang melaporkan masalah pembayaran ke dukungan.

Tiga hal yang disiapkan sebelum dibutuhkan

Rotasi hampir selalu dijalankan pada hari yang sedang buruk, jadi tiga hal ini disiapkan jauh sebelumnya: API key dibaca dari environment saat runtime sehingga rotasi tidak menunggu deploy, handler webhook sudah bisa membaca dua secret sebelum rotasi pertama dibutuhkan, dan setiap pembuatan pembayaran sudah membawa Idempotency-Key sehingga permintaan yang gagal saat rotasi aman diulang. Ketiganya adalah pekerjaan satu sore, dan ketiganya mustahil dikerjakan saat sebuah kunci sedang diketahui bocor.

Rinciannya ada di dokumentasi autentikasi untuk API key dan di dokumentasi webhook untuk signing secret. Urutan pemasangan integrasi dari awal, termasuk tempat kedua rahasia ini masuk, ada di panduan integrasi QRIS di website.

Mulai sekarang

Terima pembayaran pertama minggu ini.

Daftar gratis, buat payment link atau pakai API, dan lihat statusnya berubah otomatis. Tanpa biaya bulanan, biaya transaksi hanya untuk pembayaran yang berhasil.

Dapatkan di Google PlayDownload di App Store