Cara mengamankan webhook pembayaran kripto
Apa yang melindungi endpoint webhook pembayaran: memeriksa tanda tangan webhook, penanganan idempoten, kode status yang tepat, percobaan ulang, dan pencatatan. Berdasarkan cara webhook Qvard sebenarnya bekerja.

Webhook adalah saat toko Anda memutuskan bahwa sebuah pesanan sudah dibayar. Siapa pun yang bisa memanggil endpoint itu dengan isi yang benar dapat mencoba mendapatkan barang gratis, jadi endpoint ini layak mendapat perhatian lebih daripada kebanyakan rute. Panduan ini menjelaskan apa yang melindungi webhook pembayaran, berdasarkan cara webhook Qvard bekerja saat ini, termasuk header X-Qvard-Signature, yang memungkinkan Anda memeriksa bahwa notifikasi berasal dari Qvard.
Apa itu webhook Qvard, dan apa yang bukan
Saat pembayaran selesai, Qvard mengirim permintaan POST ke callback URL yang tersimpan di proyek Anda. Isinya kecil: id yang Anda berikan saat membuat pembayaran dan status pembayaran. Webhook dikirim ketika pembayaran selesai, yaitu sinyal untuk menandai pesanan sebagai dibayar.
Permintaan membawa header X-Qvard-Signature dengan format t=<unix>,v1=<hex>: HMAC-SHA256 dari timestamp dan body mentah, dihitung dengan kunci rahasia Anda. Dua nilai v1 dikirim selama 24 jam setelah rotasi kunci, dan salah satunya valid. Merchant dengan kunci lama (tanpa prefiks qvard_sk_) juga menerima header x-api-key pada webhook; tetap verifikasi tanda tangannya. Verifikasi dilakukan atas body mentah, sebelum JSON diurai. Panduan ini menjelaskan verifikasi tanda tangan seperti yang dikirim Qvard hari ini.
Dua sifat desain ini membentuk semua hal lain: endpoint Anda adalah URL publik, dan satu-satunya rahasia yang melindunginya adalah kunci di header itu.
Ancaman yang benar-benar penting
Daftar pendek lebih berguna daripada daftar panjang:
- Permintaan palsu. Seseorang yang menemukan endpoint Anda mengirim
{ "id": "1234", "status": "completed" }agar pesanan ditandai dibayar. Pemeriksaan tanda tangan yang menghentikannya. - Kunci bocor. Kunci yang sama menandatangani webhook dan mengautentikasi panggilan API Anda. Jika muncul di repositori, log, atau kode front-end, keduanya terbuka.
- Pengiriman berulang. Bukan serangan, tetapi penyebab kerugian nyata yang paling umum: peristiwa yang sama tiba dua kali dan pesanan dipenuhi dua kali.
- Endpoint lambat atau gagal. Pengiriman yang terlewat membuat pesanan yang sudah dibayar tidak terpenuhi.
- Pencatatan ceroboh. Mencatat seluruh permintaan menaruh kunci di file log Anda.
Setiap bagian berikut menutup salah satu ancaman ini.
Periksa kunci dan bandingkan dengan aman
Baca header X-Qvard-Signature pada setiap permintaan dan verifikasi terhadap body mentah sebelum melakukan hal lain. Dua detail penting:
- Perbandingan waktu konstan.
===biasa dapat membocorkan informasi tentang berapa karakter awal yang cocok. Gunakan fungsi waktu konstan dari bahasa Anda (crypto.timingSafeEqualdi Node.js,hash_equalsdi PHP,hmac.compare_digestdi Python). Periksa panjang terlebih dahulu bila fungsi mensyaratkan ukuran yang sama. - Tolak dengan diam. Jawab
400tanpa penjelasan. Jangan katakan apakah tanda tangan hilang atau salah.
Contoh di bawah menunjukkan handler lengkap: pemeriksaan tanda tangan, batas isi yang kecil, validasi field, pembaruan yang hanya mengubah pesanan yang belum dibayar, dan jawaban tepat 200. Simpan kunci di variabel lingkungan atau pengelola rahasia, jangan di repositori atau kode front-end.
import express from 'express';
import { Qvard } from '@qvard/node';
const secret = process.env.QVARD_SECRET_KEY;
const qvard = new Qvard(secret);
const app = express();
app.post('/webhooks/qvard', express.raw({ type: 'application/json', limit: '10kb' }), async (req, res) => {
let event;
try {
event = qvard.webhooks.constructEvent(req.body, req.get('X-Qvard-Signature'), secret);
} catch {
return res.sendStatus(400);
}
const { id, status } = event;
if (typeof id !== 'string' || status !== 'completed') return res.sendStatus(200);
// Idempotent: the update only matches an order that is not paid yet.
const changed = await db.orders.markPaidIfNot(id);
if (changed) queue.push({ type: 'fulfil', orderId: id }); // slow work happens after the answer
return res.sendStatus(200); // exactly 200
});Buat handler idempoten
Qvard menganggap pengiriman berhasil hanya ketika endpoint Anda menjawab tepat HTTP 200. Status lain, timeout, atau pengalihan dianggap gagal dan diulang: lima percobaan dalam sekitar enam menit. Akibatnya handler Anda kadang akan melihat peristiwa yang sama lebih dari sekali, misalnya saat server Anda sudah mengerjakan tugasnya tetapi jawabannya hilang.
Rancang dengan memperhitungkannya:
- Jadikan perubahan status bersyarat: «tandai pesanan ini dibayar jika belum dibayar», idealnya dalam satu pernyataan database, agar dua pengiriman paralel tidak menang keduanya.
- Mulai pemenuhan (email, kunci, akses) hanya ketika pembaruan benar-benar mengubah satu baris.
- Abaikan peristiwa untuk pesanan yang tidak Anda kenal, tetapi tetap jawab 200; kalau tidak Qvard terus mengulang sesuatu yang tidak akan pernah Anda tangani.
- Bertindak hanya pada
completed. Status lain tidak boleh menandai pesanan sebagai dibayar.
Ini juga pertahanan terhadap permintaan yang diputar ulang: peristiwa valid yang berulang tidak mengubah apa pun untuk kedua kalinya.
Jawab cepat, kerjakan nanti
Endpoint Anda harus menjawab sebelum Qvard menyerah, jadi buat singkat: periksa, perbarui, masukkan antrean, jawab. Bagian yang lambat, seperti mengirim email, memanggil sistem lain, dan membuat file, kerjakan dari antrean setelah menjawab.
Pikirkan juga apa yang tidak diketahui endpoint Anda. Isinya hanya membawa id dan status, jadi jumlah yang diharapkan berasal dari pesanan Anda sendiri, bukan dari webhook. Jangan pernah mengambil jumlah, mata uang, atau produk dari isi permintaan.
Sajikan endpoint hanya lewat HTTPS dan pastikan tidak ada proxy atau pengalihan di depannya: pengalihan tidak diikuti, jadi 301 dari http ke https adalah pengiriman yang gagal.
Log, pemantauan, dan pemulihan
Perlakukan webhook seperti pergerakan uang dan simpan jejaknya.
- Catat keputusan Anda, bukan rahasianya. Simpan
id,status, waktu, dan keputusan Anda (diterapkan, duplikat, pesanan tak dikenal, kunci ditolak). Jangan pernah mencatat kunci rahasia Anda. - Beri peringatan pada penolakan. Rentetan jawaban
400yang tiba-tiba berarti seseorang menyelidiki endpoint atau Anda mengganti kunci di satu tempat tetapi tidak di semua. - Rekonsiliasi. Sehari sekali, bandingkan pesanan yang Anda anggap dibayar dengan saldo dan pembayaran di panel Qvard.
- Pulihkan. Jika server Anda mati lebih lama dari jendela percobaan ulang, kirim ulang notifikasi pembayaran itu dari panel dan biarkan handler idempoten Anda menanganinya.
Jika Anda curiga kunci bocor, gantilah dan tinjau sistem Anda mana saja yang memakainya.
Daftar periksa sebelum meluncur
- Endpoint memakai HTTPS, publik, dan dapat dijangkau dari luar jaringan Anda.
- Memverifikasi header
X-Qvard-Signatureatas body mentah dan menjawab400jika tidak cocok. - Membatasi ukuran isi dan memvalidasi
iddanstatus. - Menandai pesanan dibayar hanya pada
completeddan hanya jika belum dibayar. - Menjawab tepat 200 dengan cepat dan mengerjakan pekerjaan lambat sesudahnya.
- Tidak pernah memercayai jumlah atau produk dari isi permintaan.
- Tidak pernah mencatat kunci.
- Anda telah menguji jawaban yang gagal, pengiriman duplikat, dan pesanan tak dikenal.
Untuk alur lengkap di sekitar webhook, lihat cara menerima pembayaran kripto di situs web. Format permintaan dan respons ada di dokumentasi pengembang.
FAQ
Apakah Qvard menandatangani webhook dengan HMAC?
Ya. Setiap webhook membawa header X-Qvard-Signature (t=<unix>,v1=<hex>): HMAC-SHA256 dari timestamp dan body mentah, dihitung dengan kunci rahasia Anda. Verifikasi di body mentah dalam waktu konstan dan perlakukan kunci Anda sebagai rahasia.
Kode status apa yang harus dikembalikan endpoint saya?
Tepat 200. Status lain, timeout, atau pengalihan dianggap pengiriman gagal dan diulang, lima percobaan dalam sekitar enam menit.
Bagaimana jika server saya mati saat pembayaran selesai?
Percobaan ulang mencakup sekitar enam menit. Setelah itu, kirim ulang notifikasi pembayaran dari panel. Handler idempoten membuatnya aman.
Bisakah saya memercayai jumlah di webhook?
Webhook hanya membawa id pembayaran dan statusnya. Gunakan jumlah dari pesanan Anda sendiri dan tandai dibayar hanya pada status completed.
