İmza doğrulama
Webhook adresin herkese açıktır; birinin sahte bir istek göndermesini engellemek için her isteği imzayla doğrula.
Nasıl imzalanır
Her istekte X-Mekante-Signature başlığı bulunur:
X-Mekante-Signature: t=1792749428,v1=794b3947df267c3354af75b5d8931538c765d768e5ca83983dcd52f7c6f93270
t: isteğin imzalandığı an, saniye cinsinden Unix zamanı.v1: şu metnin HMAC-SHA256 özeti (küçük harfli onaltılık):
HMAC_SHA256(imza_anahtarın, "<t>.<ham gövde>")
imza_anahtarın, uç noktayı eklerken gösterilen whsec_… değeridir. Gövde, sunucunun gönderdiği ham metindir: JSON'u çözüp yeniden oluşturma, boşluklar ve alan sırası değişir ve imza tutmaz.
Doğrulama adımları
- Başlıktan
tvev1değerlerini al. tdeğerinin şimdiki zamandan en fazla 5 dakika farklı olduğunu kontrol et (eski bir isteğin yeniden gönderilmesini engeller)."<t>.<ham gövde>"metninin HMAC-SHA256'sını kendi anahtarınla hesapla.- Sonucu
v1ile sabit zamanlı karşılaştır (===kullanma). - Tutuyorsa isteği işle ve
200dön; tutmuyorsa400dön.
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.MEKANTE_WEBHOOK_SECRET; // whsec_…
const TOLERANCE_SECONDS = 300;
function verify(rawBody, header) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
const v1 = parts.v1;
if (!t || !v1) return false;
if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
// Ham gövde gerekli: express.json() KULLANMA.
app.post("/hooks", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verify(raw, req.get("X-Mekante-Signature") ?? "")) return res.sendStatus(400);
const event = JSON.parse(raw);
// event.id'yi daha önce işlediysen yok say, sonra event.event'e göre işle.
res.sendStatus(200);
});
Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, request, abort
SECRET = os.environ["MEKANTE_WEBHOOK_SECRET"].encode() # whsec_…
TOLERANCE = 300
app = Flask(__name__)
def verify(raw: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = parts.get("t"), parts.get("v1")
if not t or not v1 or abs(time.time() - int(t)) > TOLERANCE:
return False
expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
@app.post("/hooks")
def hooks():
raw = request.get_data() # ham gövde
if not verify(raw, request.headers.get("X-Mekante-Signature", "")):
abort(400)
event = request.get_json()
# event["id"]'yi daha önce işlediysen yok say.
return "", 200
PHP
<?php
$secret = getenv('MEKANTE_WEBHOOK_SECRET'); // whsec_…
$raw = file_get_contents('php://input'); // ham gövde
$header = $_SERVER['HTTP_X_MEKANTE_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $parts);
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
$fresh = ctype_digit($t) && abs(time() - (int) $t) <= 300;
if (!$fresh || !hash_equals($expected, $v1)) {
http_response_code(400);
exit;
}
$event = json_decode($raw, true);
// $event['id']'yi daha önce işlediysen yok say.
http_response_code(200);
Hata ayıklama
- İmza hiç tutmuyorsa genellikle ham gövde değiştirilmiştir (JSON'a çevirip geri yazmak, satır sonu dönüşümü, karakter kodlaması).
- Anahtarın başında ya da sonunda boşluk olmadığından emin ol.
- Panelde Test gönder ile bir
pinggönder ve kodunu bu istekle dene.