بررسی امضای وبهوک
آنچه یاد میگیرید: امضای پیامهای وبهوک چیست، چطور آن را بررسی کنید، و چطور کد خود را بدون سرور آزمایش کنید.
امضا به زبان ساده
Section titled “امضا به زبان ساده”شما و سلر ۳۶۰ یک کلید امضای مشترک دارید که در هیچ پیامی فرستاده نمیشود. سلر ۳۶۰ هر پیام را با این کلید امضا میکند؛ شما همان محاسبه را تکرار میکنید. اگر امضاها یکی بود، پیام واقعا از سلر ۳۶۰ آمده و در راه دست نخورده است؛ مثل مهر برجسته روی نامهی اداری.
سرآیند امضا
Section titled “سرآیند امضا”S360-Signature: t=1762930000,v1=25ece598dedd14967fd7b77a3a873814605f38d98eac7bad39feecc1f8559349t: زمان ارسال (ثانیه از ۱۹۷۰).v1: امضایHMAC-SHA256روی رشتهیt + "." + بدنهی خام، با کلید امضای نشانی شما.
بررسی، قدمبهقدم
Section titled “بررسی، قدمبهقدم”- بدنهی خام را پیش از هر تبدیلی بخوانید؛ JSON تبدیلشده و دوباره ساختهشده امضای دیگری دارد.
- رشتهی
t + "." + بدنهی خامرا با کلید امضا وHMAC-SHA256امضا کنید. - حاصل را با
v1با مقایسهی ثابتزمان مقایسه کنید. - اگر
tبیش از ۵ دقیقه با زمان سرور شما فاصله دارد، پیام را رد کنید (جلوگیری از پخش دوبارهی پیام قدیمی).
function s360_verify(string $raw, string $header, string $secret, ?int $now = null): bool { // The header holds one t and one or more v1 (two while the key rotates). $t = null; $signatures = []; foreach (explode(",", $header) as $part) { [$name, $value] = array_pad(explode("=", trim($part), 2), 2, ""); if ($name === "t") { $t = $value; } if ($name === "v1") { $signatures[] = $value; } } if ($t === null || !ctype_digit($t) || abs(($now ?? time()) - (int) $t) > 300) { return false; } $expected = hash_hmac("sha256", $t . "." . $raw, $secret); foreach ($signatures as $candidate) { if (hash_equals($expected, $candidate)) { return true; } } return false;}
$raw = file_get_contents("php://input");if (!s360_verify($raw, $_SERVER["HTTP_S360_SIGNATURE"] ?? "", getenv("S360_WEBHOOK_SECRET"))) { http_response_code(400); exit;}import hashlib, hmac, time
def verify(raw, header, secret, now=None): # The header holds one t and one or more v1 (two while the key rotates). t, signatures = None, [] for part in header.split(","): name, _, value = part.strip().partition("=") if name == "t": t = value elif name == "v1": signatures.append(value) if t is None or not t.isdigit() or abs((now or time.time()) - int(t)) > 300: return False expected = hmac.new(secret.encode(), t.encode() + b"." + raw, hashlib.sha256).hexdigest() return any(hmac.compare_digest(expected, s) for s in signatures)import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(raw, header, secret, now = Date.now() / 1000) { // The header holds one t and one or more v1 (two while the key rotates). let t = null const signatures = [] for (const part of header.split(',')) { const [name, value = ''] = part.trim().split(/=(.*)/s) if (name === 't') t = value if (name === 'v1') signatures.push(value) } if (t === null || !/^\d+$/.test(t) || Math.abs(now - Number(t)) > 300) return false const expected = Buffer.from(createHmac('sha256', secret).update(`${t}.${raw}`).digest('hex')) // timingSafeEqual throws on different lengths, so compare lengths first. return signatures.some((s) => { const given = Buffer.from(s) return given.length === expected.length && timingSafeEqual(given, expected) })}نمونهی آزمون ثابت
Section titled “نمونهی آزمون ثابت”کد خود را بدون سرور با این دادهها آزمایش کنید. اگر به همان امضا رسیدید، پیادهسازی شما درست است.
| ورودی | مقدار |
|---|---|
| کلید امضا | whsec_test_seller360 |
t |
1762930000 |
| بدنهی خام | {"id":"evt_test","type":"ping"} |
امضای مورد انتظار (v1) |
25ece598dedd14967fd7b77a3a873814605f38d98eac7bad39feecc1f8559349 |
خطاهای رایج
Section titled “خطاهای رایج”- امضا روی بدنهی تبدیلشده حساب شده، نه بدنهی خام.
- مقایسه با
==به جای مقایسهی ثابتزمان. - استفاده از کلید API به جای کلید امضای نشانی وبهوک.