MercanPay Blog

Webhook İmzası Nasıl Doğrulanır? HMAC-SHA256 Rehberi

Webhook imzası doğrulama rehberi: HMAC-SHA256, t=…,v1=… başlığı, ham gövde, sabit zamanlı karşılaştırma ve replay saldırılarına karşı koruma örneklerle.

Yayınlanma: 30 Eylül 20265 dk okumaRead in English
Webhook İmzası Nasıl Doğrulanır? HMAC-SHA256 Rehberi

Webhook imzası doğrulamak, sunucunuza gelen bir ödeme bildiriminin gerçekten ödeme sağlayıcınızdan geldiğini ve yolda değiştirilmediğini kanıtlamanın yoludur. İmza kontrolü yapılmayan bir webhook uç noktasına herkes sahte bir "ödeme alındı" isteği gönderebilir. Bu rehberde HMAC-SHA256 ile webhook imzasının nasıl doğrulanacağını, sık yapılan hataları ve replay saldırılarına karşı korumayı anlatıyoruz.

Webhook imzası neden gerekli?

Webhook uç noktanız internete açık bir URL'dir, örneğin https://magazam.com/mercanpay/webhook. Bu adresi bilen herkes buraya şöyle bir istek gönderebilir:

{ "type": "invoice.paid", "data": { "invoice": { "order_id": "order-1001", "status": "paid" } } }

İmza doğrulaması yapmıyorsanız sisteminiz bu siparişi ödenmiş sayar ve ürünü teslim eder. Hiç ödeme yapılmadığı hâlde. Bu, kripto ödeme entegrasyonlarında en pahalıya patlayan hatalardan biridir.

İmza bu sorunu çözer. Ödeme sağlayıcısı ile sizin aranızda paylaşılan gizli bir anahtar (sır) vardır. Sağlayıcı her isteği bu sırla imzalar. Sırrı bilmeyen biri geçerli bir imza üretemez.

HMAC-SHA256 nasıl çalışır?

HMAC (Hash-based Message Authentication Code), bir mesajı ve gizli bir anahtarı birlikte özetleyen bir yöntemdir. SHA-256 ise kullanılan özet (hash) fonksiyonudur. Sonuç şu özelliklere sahiptir:

  • Aynı mesaj ve aynı sır her zaman aynı imzayı üretir.
  • Mesajda tek bir bayt değişirse imza tamamen değişir.
  • Sırrı bilmeden geçerli imza üretmek pratikte imkânsızdır.

MercanPay'de imza, HMAC-SHA256(sır, zaman_damgası + "." + ham_gövde) değerinin hex hâlidir. Sonuç X-MercanPay-Signature başlığında şu biçimde gönderilir: t=1790000052,v1=5f2b...e91a.

Burada t Unix zaman damgası (saniye), v1 ise imzanın hex hâlidir. Sır, panelde Ayarlar sayfasında bulunan ve whsec_ ile başlayan webhook imza sırrıdır.

Webhook imzası doğrulama: 5 adım

  1. Ham gövdeyi okuyun. İsteğin gövdesini JSON olarak ayrıştırmadan, bayt bayt alın.
  2. Başlığı ayrıştırın. t ve v1 değerlerini virgülle ayrılmış parçalardan çıkarın.
  3. Zaman damgasını kontrol edin. t değeri şimdiki zamandan 5 dakikadan fazla uzaksa isteği reddedin.
  4. Beklenen imzayı hesaplayın. t + "." + ham_gövde metnini sırrınızla HMAC-SHA256'dan geçirin.
  5. Sabit zamanlı karşılaştırın. Hesapladığınız imzayı v1 ile, zamanlamaya dayalı saldırılara açık olmayan bir fonksiyonla karşılaştırın.

İmza geçerliyse gövdeyi JSON olarak ayrıştırıp olayı işleyin. Geçersizse 401 döndürün ve hiçbir işlem yapmayın.

PHP ile doğrulama

<?php
$secret = getenv('MERCANPAY_WEBHOOK_SECRET');           // whsec_...
$body   = file_get_contents('php://input');            // ham gövde
$header = $_SERVER['HTTP_X_MERCANPAY_SIGNATURE'] ?? '';

parse_str(str_replace(',', '&', $header), $sig);        // t=...,v1=...
$fresh = isset($sig['t']) && abs(time() - (int) $sig['t']) <= 300;
$mac   = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);

if (!$fresh || !hash_equals($mac, $sig['v1'] ?? '')) {
    http_response_code(401);
    exit;
}

$event = json_decode($body, true);

Resmi kütüphaneyi kullanıyorsanız (composer require mercanpay/mercanpay-php) tüm bunlar tek satırdır:

if (!\MercanPay\MercanPay::verifyWebhook($secret, $body, $header)) { http_response_code(401); exit; }

Python ile doğrulama

import hashlib, hmac, time

def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
    sig = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t = int(sig.get("t", 0))
    if "v1" not in sig or abs(time.time() - t) > tolerance:
        return False
    mac = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, sig["v1"])

pip install mercanpay ile gelen verify_webhook(secret, body, header) fonksiyonu aynı işi yapar. Flask ve Django örnekleri için Python entegrasyon rehberine bakabilirsiniz.

Node.js ile doğrulama

import crypto from 'node:crypto';
import express from 'express';

function verify(secret, rawBody, header, tolerance = 300) {
  const sig = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(sig.t);
  if (!sig.v1 || !Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
  const mac = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return mac.length === sig.v1.length && crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig.v1));
}

const app = express();
// imza ham gövde üzerinden hesaplanır: bu rotada express.json() kullanmayın
app.post('/mercanpay/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(process.env.MERCANPAY_WEBHOOK_SECRET, req.body.toString(), req.get('X-MercanPay-Signature') || '')) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  res.sendStatus(200);
});

Sık yapılan hatalar

Hata Sonucu Doğrusu
Gövdeyi JSON'a çevirip yeniden serileştirmek Boşluk ve sıra değişir, imza tutmaz Ham baytları kullanın
== ile karşılaştırmak Zamanlama saldırısına açık hash_equals, hmac.compare_digest, timingSafeEqual
Zaman damgasını kontrol etmemek Eski istekler tekrar oynatılabilir 5 dakikalık tolerans uygulayın
Sırrı koda gömmek Depo sızarsa sır da sızar Ortam değişkeni kullanın
İmza hatasında 200 dönmek Hata gizlenir 401 dönün ve loglayın
Siparişi success_url yönlendirmesine göre onaylamak URL taklit edilebilir Yalnızca imzalı webhook veya API

Replay saldırılarına karşı koruma

Geçerli bir webhook isteğini ele geçiren biri, onu daha sonra tekrar gönderebilir. Buna replay (tekrar oynatma) saldırısı denir. İmza geçerli olduğu için yalnızca imza kontrolü bunu durduramaz. Koruma iki katmandan oluşur:

  1. Zaman penceresi: İmzaya zaman damgası da dahil olduğu için saldırgan t değerini değiştiremez. 5 dakikadan eski istekleri reddettiğinizde eski bir istek işe yaramaz.
  2. Olay kimliğiyle tekrar eleme: Her webhook gövdesinde benzersiz bir id alanı bulunur. İşlediğiniz olay kimliklerini benzersiz bir veritabanı sütununda saklayın ve daha önce gördüğünüz kimliği tekrar işlemeyin.

İkinci katman yalnızca saldırılara karşı değil, meşru tekrarlara karşı da gereklidir. Sunucunuz 10 saniye içinde 2xx dönmezse MercanPay isteği artan aralıklarla yeniden gönderir: 30 sn, 2 dk, 10 dk, 30 dk, 1 sa, 3 sa, 6 sa, 12 sa ve 24 sa. Aynı olay size birkaç kez ulaşabilir. Kodunuz bunu sorunsuz karşılamalıdır.

İmzadan sonra: iş mantığı kontrolleri

İmza, isteğin MercanPay'den geldiğini kanıtlar. Siparişi onaylamadan önce yine de şunları kontrol edin:

  • data.invoice.order_id sizin sisteminizde gerçekten var mı?
  • token ve paid_amount siparişinizle uyumlu mu?
  • Olay türü invoice.paid veya invoice.overpaid mı? invoice.partial bir eksik ödemedir ve sipariş henüz tamamlanmamıştır.

Eksik ve fazla ödemelerin nasıl ele alınacağını eksik, fazla ve geç ödemeler yazısında anlattık.

Sık sorulan sorular

Webhook'umu nasıl test ederim?

Panelde Webhooklar sayfasındaki "Test gönder" butonu imzalı bir test.ping olayı gönderir. Teslimat geçmişinde her isteğin yanıt kodunu ve süresini görebilir, dilediğiniz olayı tekrar gönderebilirsiniz.

Neden 5 dakikalık tolerans?

Sunucu saatleri arasında küçük farklar olabilir. 5 dakika, meşru istekleri reddetmeyecek kadar geniş, eski isteklerin tekrar oynatılmasını engelleyecek kadar dardır. Sunucu saatinizin NTP ile senkron olduğundan emin olun.

HTTPS kullanıyorsam imzaya yine de gerek var mı?

Evet. HTTPS trafiği yolda şifreler, ancak isteği kimin gönderdiğini kanıtlamaz. Uç noktanıza herkes HTTPS üzerinden istek atabilir.

MercanPay ile başlayın

MercanPay webhook'ları HMAC-SHA256 ile imzalanır, otomatik olarak yeniden denenir ve panelde teslimat geçmişiyle izlenebilir. PHP ile başlamak için PHP entegrasyon rehberine, tüm uç noktalar için dokümantasyona göz atın. Satıcı hesabınızı oluşturun.

#Webhook#HMAC#Güvenlik#API

İlgili yazılar

Kripto ödemeleri dakikalar içinde almaya başlayın

USDT ve TRX (TRC20) ile ödeme alın. %0,4'ten başlayan komisyon, hazır ödeme sayfası, API ve webhook.