Blog · 2 September 2026
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. Ia 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:
- API key live, berawalan
kp_live_ - API key tes, berawalan
kp_test_ - Signing secret untuk endpoint webhook live
- Signing secret untuk endpoint webhook tes
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 hanya ditampilkan sekali, saat dibuat. Kalau hilang, tidak ada cara membacanya kembali; yang ada hanyalah membuat yang baru. Signing secret sebaliknya bisa dilihat kapan saja di dashboard. Artinya API key wajib masuk ke penyimpanan yang bisa dibaca ulang oleh mesin sejak detik pertama, sedangkan signing secret masih bisa diambil ulang bila konfigurasi server hilang.
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:
- Kode frontend. Segala sesuatu yang dikirim ke browser bisa dibaca siapa pun yang membuka halaman itu, termasuk nilai yang tersembunyi di dalam bundel JavaScript. Pembuatan pembayaran karena itu dipanggil dari server sendiri, bukan dari halaman pembeli.
- Repositori, termasuk yang privat. Repositori privat berubah menjadi publik karena kekeliruan konfigurasi, dan riwayat commit ikut terbawa saat repositori di-fork atau di-clone ke laptop yang kemudian hilang.
- Berkas contoh dan log.
.env.exampleseharusnya berisi nama variabel dengan nilai kosong, dan headerAuthorizationikut tercetak bila seluruh objek request di-log saat terjadi kegagalan, ke layanan pihak ketiga pula.
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.
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:
- Pastikan key dibaca saat runtime, bukan saat build. Nilai yang ikut terkompilasi ke dalam artefak build hanya berubah setelah build dan deploy ulang, sehingga jeda 401 berubah dari hitungan detik menjadi hitungan menit. Baca dari environment variable pada saat permintaan dibuat.
- Pilih jam sepi. Jeda yang sama menjatuhkan lima permintaan atau lima ratus, tergantung jam berapa dijalankan.
- Rotasi, lalu segera perbarui secret store dan muat ulang. Bukan sebaliknya. Nilai baru belum ada sebelum rotasi dijalankan.
- Verifikasi dengan panggilan baca, bukan dengan membuat pembayaran.
GET /v1/payment_methodsmembuktikan key baru diterima tanpa meninggalkan baris apa pun di riwayat transaksi.
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 tanda tangannya dibuat sebelum rotasi bisa mengetuk endpoint satu hari penuh setelah rotasi dijalankan.
Handler yang langsung membuang secret lama akan menolak event-event itu sebagai tanda tangan tidak sah. Yang ditolak bukan serangan, melainkan pemberitahuan bahwa sebuah pembayaran sudah lunas, dan setelah percobaan ketujuh pemberitahuan itu tidak datang lagi. Urutan yang benar karena itu kebalikan dari rotasi API key: secret baru dipasang berdampingan dengan yang lama, bukan menggantikannya.
- Buat handler membaca dua secret, misalnya
KASERA_WEBHOOK_SECRETdanKASERA_WEBHOOK_SECRET_PREV, dan deploy dulu perubahan itu sebelum apa pun dirotasi. - Verifikasi terhadap secret baru lebih dulu, dan hanya bila gagal, coba secret lama. Kedua perbandingan tetap harus memakai perbandingan yang waktunya konstan.
- Rotasi secret di dashboard, lalu isi nilai barunya dan pindahkan nilai lama ke variabel kedua.
- Kosongkan variabel kedua setelah lewat dua hari penuh sejak rotasi, yang memberi jarak aman terhadap rentang 33 jam. Menghapusnya adalah langkah tersendiri yang mudah terlupakan, jadi sebaiknya dijadwalkan saat itu juga.
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.
Dua hal yang bisa dilakukan pemegang key live, dan keduanya perlu diperiksa. Yang pertama 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 kedua lebih langsung berhubungan dengan uang: POST /v1/refunds. Refund lewat API hanya berlaku untuk pembayaran kartu yang sudah succeeded, dan metode lain ditolak 422 refund_not_supported, jadi paparannya terbatas pada pembayaran kartu yang belum dicairkan. Bagi merchant yang belum menyalakan kartu, jalur ini tidak ada sama sekali. Bagi yang sudah, daftar refund adalah hal kedua yang diperiksa setelah daftar transaksi.
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.
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.