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:
- 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 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.
- Key hilang, tidak bocor, dan masih bisa ditampilkan. Di Pengaturan → Developer ada tombol Lihat key. Kasera Pay mengirim kode sekali pakai ke email akun yang sedang masuk, kode itu dimasukkan, lalu secret yang sama muncul kembali. Integrasi yang sedang jalan tidak tersentuh sama sekali. Pemberitahuan bahwa key dibaca dikirim ke email pemilik, jadi pembacaan yang tidak dilakukan siapa pun di tim akan terlihat.
- Key hilang, tetapi dibuat sebelum fitur ini ada. Key lama hanya tersimpan sebagai hash, sehingga tidak ada yang bisa dibuka. Tombolnya menolak dengan penjelasan itu. Jalan keluarnya rotasi satu kali: setelah key baru terbit, semua pembacaan berikutnya bisa dilayani.
- Key bocor, atau diduga bocor. Rotasi, sekarang juga. Menampilkan ulang bukan penanganan insiden: yang perlu dimatikan adalah nilai yang sudah ada di tangan orang lain, dan hanya rotasi yang melakukannya.
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:
- 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. 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:
- 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 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.
- Pastikan verifikasi memeriksa setiap entri
v1, bukan entri pertama saja, dan sudah memakaiKasera-Signature-V1dan bukan header lama. Ini dikerjakan sebelum rotasi, dan ini satu-satunya langkah yang urutannya tidak boleh ditukar. - Rotasi secret di dashboard. Jendela 24 jam mulai berjalan sejak detik itu.
- Ganti nilai secret di variabel lingkungan lalu deploy, di dalam 24 jam itu. Satu variabel, bukan dua.
- Buktikan dengan pengiriman sungguhan sebelum jendelanya habis: kirim ulang satu event dari dashboard dan pastikan jawabannya 2xx.
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.