Bir Qvard webhook'ını nasıl doğrularsınız
X-Qvard-Signature başlığı hakkında kısa bir kılavuz: nedir ve bunu birkaç satır kod ile nasıl kontrol edersiniz.
İmza nedir
Bir ödemenin durumu değiştiğinde, Qvard mağaza ayarlarınızdaki adrese bir bildirim (webhook) gönderir. O adrese herhangi biri bir istek gönderebilir, bu nedenle gerçek bir bildirimi sahte bir bildirimden ayırt etmenin bir yoluna ihtiyaç vardır.
İmza bu kanıttır: Qvard'ın ileti ve gizli anahtarınızdan hesapladığı kısa bir kod. Yalnız Qvard ve siz anahtarı bilirsiniz, bu nedenle yalnızca Qvard doğru kodu üretebilir.
Ne alırsınız
Her webhook ekstra bir başlık ve küçük bir JSON gövdesi taşır:
X-Qvard-Signature: t=1760950000,v1=3f2a…c91e
{"id":"order_1024","status":"paid"}t mesajın gönderildiği saattir (Unix, saniye). v1 koddur. Gizli anahtarınızı döndürdükten sonra 24 saat boyunca başlık iki v1 değeri taşır: biri yeni anahtar için, biri eski anahtar için.
id ve status alanları aynen kalır. İmzalanan aynı gövdeye dört tutar alanı eklenir:
- amount ve currency: isteğde gönderdiğiniz tutar ve para birimi.
- amountUsd: muhasebede kullanılan USD tutarı.
- rate: ödeme oluşturulduğu andaki 1 USD karşılığı para birimi miktarı.
- Eski entegrasyonlar çalışmaya devam eder: id ve status alanlarını okurlar, yeni alanları yok sayarlar. İmza öncekiyle aynı gövde üzerinden doğrulanır.
Nasıl kontrol edersiniz
- 1İstek gövdesini tam olarak geldiği gibi alın: ham metin, herhangi bir JSON ayrıştırmasından önce.
- 2X-Qvard-Signature başlığından t ve her v1'i okuyun.
- 3Metni t + "." + gövde ile gizli anahtarınızı (qvard_sk_…) kullanarak HMAC-SHA256 olarak hesaplayın ve küçük harf onaltılık olarak yazın.
- 4Sonucunuz herhangi bir v1 ile eşleşirse (sabit zamanda karşılaştırın), ileti gerçektir.
- 5İnsanların 5 dakikadan uzun bir mesajı tekrar göndermesini sağlamayacak şekilde t'nin 5 dakikadan daha eski olmadığını kontrol edin.
- 6Ancak o zaman verileri kullanın ve o sipariş için ödenen tutarı kendi veritabanınızda doğrulayın.
Hazır kod
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); ?>
Gizli anahtarınızı döndürdükten sonra 24 saat boyunca başlık iki v1 değeri taşır: herhangi biri eşleşirse webhook'u kabul edin.
Yaygın hatalar
- JSON'u ayrıştırıp yeniden seri hale getirmek metni değiştirir. Her zaman ham gövdeyi kullanın.
- Gizli anahtar yerine genel anahtarı (qvard_pk_…) kullanma.
- Senkronize olmayan bir sunucu saati 5 dakikalık kontrolü başarısız kılar. Senkronize tutun.
- Sabit zaman karşılaştırması yerine == ile karşılaştırma.
Gizli anahtarınızı nerede bulursunuz
Panelde: mağazanız, Ayarlar, API anahtarları. Yalnızca sunucunuzda saklayın. Sızarsa, orada döndürün: eski anahtar ödememeler için 24 saat daha çalışır (para çekmeleri için değil).
Eski anahtarlar
Mağazanızda hala eski format anahtarı varsa (qvard_sk_ öneki olmadan), webhook'lar da o anahtarı içeren x-api-key başlığını taşır. Döndürdükten sonra, o başlık kaybolur. İmza kontrolüne geçin.