Cara memverifikasi webhook Qvard
Panduan singkat tentang header X-Qvard-Signature: apa itu dan cara memverifikasinya dengan beberapa baris kode.
Apa itu tanda tangan
Ketika status pembayaran berubah, Qvard mengirimkan pemberitahuan (webhook) ke alamat di pengaturan toko Anda. Siapa pun dapat mengirim permintaan ke alamat itu, jadi Anda memerlukan cara untuk membedakan pemberitahuan nyata dari yang palsu.
Tanda tangan adalah bukti itu: kode pendek yang Qvard hitung dari pesan dan kunci rahasia Anda. Hanya Qvard dan Anda yang tahu kunci itu, jadi hanya Qvard yang dapat menghasilkan kode yang benar.
Apa yang Anda terima
Setiap webhook membawa satu header tambahan dan badan JSON kecil:
X-Qvard-Signature: t=1760950000,v1=3f2a…c91e
{"id":"order_1024","status":"paid"}t adalah waktu pesan dikirim (Unix, detik). v1 adalah kodenya. Selama 24 jam setelah Anda memutar kunci rahasia, header membawa dua nilai v1: satu untuk kunci baru dan satu untuk yang lama.
Field id dan status tetap seperti sebelumnya. Empat field nilai ditambahkan ke badan yang sama dan ikut ditandatangani:
- amount dan currency: nominal dan mata uangnya, seperti yang Anda kirim dalam permintaan.
- amountUsd: nominal dalam USD yang dipakai untuk pembukuan.
- rate: jumlah satuan mata uang untuk 1 USD saat pembayaran dibuat.
- Integrasi lama tetap berjalan: mereka membaca id dan status serta mengabaikan field baru. Tanda tangan diperiksa pada badan yang sama seperti sebelumnya.
Cara memeriksanya
- 1Ambil badan permintaan persis seperti yang tiba: teks mentah, sebelum parsing JSON apa pun.
- 2Baca t dan setiap v1 dari header X-Qvard-Signature.
- 3Hitung HMAC-SHA256 dari teks t + "." + badan dengan kunci rahasia Anda (qvard_sk_…) dan tulis sebagai heksadesimal huruf kecil.
- 4Jika hasil Anda cocok dengan v1 apa pun (bandingkan dalam waktu konstan), pesannya asli.
- 5Periksa bahwa t tidak lebih lama dari 5 menit, sehingga pesan lama tidak dapat diputar ulang.
- 6Hanya kemudian gunakan data dan konfirmasi jumlah yang dibayarkan untuk pesanan itu di database Anda sendiri.
Kode siap
const crypto = require('crypto'); function verifyQvardSignature(rawBody, header, secret, toleranceSec = 300) { const parts = header.split(',').map((p) => p.trim()); const t = Number((parts.find((p) => p.startsWith('t=')) || '').slice(2)); const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3)); if (!t || sigs.length === 0) return false; if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest(); return sigs.some((s) => { const got = Buffer.from(s, 'hex'); return got.length === expected.length && crypto.timingSafeEqual(got, expected); }); } // Use the RAW body: express.raw, not express.json app.post('/qvard-webhook', express.raw({ type: 'application/json' }), (req, res) => { const raw = req.body.toString('utf8'); if (!verifyQvardSignature(raw, req.get('X-Qvard-Signature') || '', process.env.QVARD_SECRET_KEY)) { return res.sendStatus(400); } const { id, status } = JSON.parse(raw); // Confirm the paid amount for order `id` in your own database before fulfilling it. res.sendStatus(200); });
<?php function verifyQvardSignature(string $rawBody, string $header, string $secret, int $tolerance = 300): bool { $t = 0; $sigs = []; foreach (explode(',', $header) as $part) { $part = trim($part); if (strpos($part, 't=') === 0) { $t = (int) substr($part, 2); } elseif (strpos($part, 'v1=') === 0) { $sigs[] = substr($part, 3); } } if (!$t || !$sigs || abs(time() - $t) > $tolerance) { return false; } $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret); foreach ($sigs as $s) { if (hash_equals($expected, $s)) { return true; } } return false; } $raw = file_get_contents('php://input'); if (!verifyQvardSignature($raw, $_SERVER['HTTP_X_QVARD_SIGNATURE'] ?? '', getenv('QVARD_SECRET_KEY'))) { http_response_code(400); exit; } $event = json_decode($raw, true); // ['id' => ..., 'status' => ...] http_response_code(200); ?>
Selama 24 jam setelah memutar kunci rahasia Anda, header membawa dua nilai v1: terima webhook jika salah satu cocok.
Kesalahan umum
- Mengurai JSON dan menseriesnya lagi mengubah teks. Selalu gunakan badan mentah.
- Menggunakan kunci publik (qvard_pk_…) alih-alih kunci rahasia.
- Jam server yang tidak sinkron membuat pemeriksaan 5 menit gagal. Tetap sinkron.
- Membandingkan dengan == alih-alih perbandingan waktu konstan.
Tempat menemukan kunci rahasia Anda
Di panel: toko Anda, Pengaturan, Kunci API. Simpan hanya di server Anda. Jika bocor, putar di sana: kunci lama terus berfungsi selama 24 jam untuk pembayaran (bukan untuk penarikan).
Kunci lama
Jika toko Anda masih memiliki kunci format lama (tanpa awalan qvard_sk_), webhook juga membawa header x-api-key dengan kunci itu. Setelah Anda memutarnya, header itu menghilang. Beralih ke pemeriksaan tanda tangan.