TR

Kripto ödeme webhook'ları: imzayı doğrulayın, yeniden denemeleri ve tekrarları yönetin

SnapEX Pay webhook'ları

Mağazanız, faturalandırma paneliniz veya botunuz, müşterinin ödeme yaptığını webhook sayesinde öğrenir. SnapEX Pay her fatura, ödeme ve para çekme olayında URL'nize bir HTTP POST isteği gönderir. Doğru kurulduğunda elinizdeki en hızlı ve en güvenilir sinyaldir. Özensiz kurulduğunda ise hiç ödenmemiş siparişlerin ödendi olarak işaretlenmesine yol açar. Bu rehber önemli noktaları anlatıyor.

Kurulum

Üye işyeri panelinde “Geliştiriciler” sayfasını açın, “Webhook” kartında URL'nizi girin ve “Kaydet” düğmesine basın. Hesap başına tek bir URL vardır ve tüm olay türleri ona gelir. İlk kayıtta panel “Webhook imza anahtarı” (whsec_…) değerini bir kez gösterir: bunu yalnızca kendi sunucunuzda saklayın.

URL, herkese açık bir host'a sahip mutlak bir https:// adresi olmalıdır: içinde login:password olmamalı, localhost olmamalı ve host özel (private) veya ayrılmış (reserved) bir IP'ye çözümlenmemelidir. Yönlendirmeler takip edilmez, bu yüzden son URL'yi verin.

Ne gelir

Her istek dört header taşır:

Header Anlamı
Webhook-Id Benzersiz olay kimliği. Yeniden denemelerde ve elle yeniden gönderimlerde değişmez.
Webhook-Timestamp Bu denemenin gönderildiği Unix zamanı (saniye). Her yeniden denemede değeri yenilenir.
Webhook-Signature v1= ve ardından {Webhook-Timestamp}.{ham gövde} üzerinden hesaplanan HMAC-SHA256'nın küçük harfli hex değeri.
Content-Type Her zaman application/json.

Gövdede id (Webhook-Id ile aynı), type, created_at ve data bulunur. invoice.* olaylarında data, GET /invoices/{id} ile aynı yapıdaki faturadır; environment, amount_usd, status ve sizin external_id alanınız da buna dahildir.

Fatura olayları:

Olay Ne zaman
invoice.created Fatura oluşturuldu, henüz ödeme yöntemi seçilmedi.
invoice.awaiting Bir ödeme yöntemi seçildi ve adres verildi.
invoice.underpaid Tutarın bir kısmı geldi; aynı adres kalanını bekliyor.
invoice.paid Tutarın tamamı ödendi ve bakiyeye eklendi. Siparişi teslim etmeniz için tek sinyal budur.
invoice.partially_paid Yalnızca bir kısmı ödendi, tamamlama süresi doldu, alınan kısım bakiyenize eklendi.
invoice.expired Fatura süresi ödeme yapılmadan doldu.
invoice.extra_payment Fatura ödendikten sonra bir transfer daha geldi; ayrıca bakiyeye eklenir.

İmzayı doğrulayın

  1. İstek gövdesini, herhangi bir JSON ayrıştırmasından önce, bayt olarak ham haliyle okuyun. Yeniden serileştirilmiş JSON eşleşmez.
  2. {Webhook-Timestamp}.{ham gövde} üzerinden, anahtar olarak whsec_ öneki dahil gizli anahtarın tamamını kullanarak HMAC-SHA256 hesaplayın ve hex olarak kodlayın.
  3. v1= + hex değerini Webhook-Signature header'ı ile sabit zamanlı (constant-time) bir fonksiyonla karşılaştırın.
  4. Sunucu saatinizden 5 dakikadan fazla farklı olan Webhook-Timestamp değerini reddedin. Yeniden denemeler yeni bir zaman damgası taşıdığı için bu kontrol onları bozmaz.

PHP:

$secret = getenv('SNAPEX_WEBHOOK_SECRET');          // whsec_… from the cabinet
$body   = file_get_contents('php://input');           // raw body, before json_decode
$ts     = $_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$sig    = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';

$expected = 'v1=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (!hash_equals($expected, $sig) || abs(time() - (int) $ts) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// skip if $event['id'] was already processed; do the heavy work in a queue
http_response_code(200);

Node.js (Express):

const crypto = require('crypto');

app.post('/hooks/snapex', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('Webhook-Timestamp') || '';
  const sig = Buffer.from(req.get('Webhook-Signature') || '');
  const expected = Buffer.from('v1=' + crypto
    .createHmac('sha256', process.env.SNAPEX_WEBHOOK_SECRET)
    .update(ts + '.' + req.body)
    .digest('hex'));

  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!fresh || sig.length !== expected.length || !crypto.timingSafeEqual(sig, expected)) {
    return res.sendStatus(400);
  }

  const event = JSON.parse(req.body);
  // skip if event.id was already processed; queue the heavy work
  res.sendStatus(200);
});

Kod bloklarındaki yorumlar: whsec_… from the cabinet — paneldeki whsec_… anahtarı; raw body, before json_decode — json_decode'dan önceki ham gövde; skip if … was already processed — bu id daha önce işlendiyse atlayın, ağır işi kuyrukta yapın.

Yeniden denemeler, tekrarlar ve sıra

  • Her 2xx yanıtı başarı sayılır. Başka her kod, zaman aşımı veya ağ hatası başarısızlık sayılır. Zaman aşımları bağlantı için 5 saniye, isteğin tamamı için 15 saniyedir; bu yüzden hızlı yanıt verin, ağır işi asenkron yapın.
  • Yeniden denemeler. Olay başına en fazla 5 deneme: ilki, ardından her başarısızlıktan sonra yaklaşık 10 saniye, 1 dakika, 5 dakika ve 15 dakika sonra. Beşinci başarısızlıktan sonra otomatik gönderim durur; panel son gönderimleri “Webhook gönderimleri” listesinde gösterir ve olayı aynı Webhook-Id ile tekrar gönderen bir “Yeniden gönder” düğmesi sunar.
  • Tekrarlar. Gönderim en az bir kez (at-least-once) garantilidir: aynı olay birden fazla kez gelebilir. İşlenmiş Webhook-Id değerlerini saklayın ve tekrarları yok sayın.
  • Sıra. Olaylar sırası karışık gelebilir: yeniden denenen eski bir olay, daha yeni bir olaydan sonra gelebilir. Sıraya güvenmeyin; olaydaki durumu elinizdeki durumla karşılaştırın.
  • İsterseniz ayrıca doğrulayın. Bir webhook'tan sonra nesneyi API üzerinden yeniden okuyabilirsiniz: GET /invoices/{id}.

Gerçek para olmadan test edin

Bir test faturası oluşturun ("environment": "test"), ödeme sayfasında ödeyin ve webhook'ların data.environment = "test" ile gelişini izleyin. Ayrıntılar: Kripto ödemeleri gerçek para olmadan nasıl test edilir.

WHMCS, BILLmanager, WooCommerce ve diğer platformlar için hazır modüller imzayı zaten doğrular ve bir ödemeyi asla iki kez işlemez; bkz. pay.snapex.pro. Tüm API ayrıntıları: SnapEX Pay API dokümantasyonu.

Kripto ile ödeme almaya hazır mısınız?

Üye işyeri hesabı oluşturun ve API anahtarınızı alın. Ödeme başına %1, aylık ücret yok.