نحوه تأیید امضاهای Square Webhook (و تله Notification-URL)

اعتبارسنجی وبهوک Square: نکات کلیدی و تلههای رایج
امضای وبهوک Square (x-square-hmacsha256-signature) بر اساس HMAC-SHA256 از ترکیب سه مورد محاسبه میشود: کلید امضا، URL اطلاعرسانی دقیق و بدنه خام درخواست. پیام امضا شده، اتصال URL و بدنه خام (به همان ترتیب) و کدگذاری Base64 نتیجه است.
دو تله اصلی شکست اعتبارسنجی:
- بدنه خام: فریمورکها باید بایتهای خام را دریافت کنند (
express.raw())، نه JSON پارسشده. هر تغییری در فضای خالی، ترتیب کلیدها یا فرمت اعداد، امضا را باطل میکند. - URL دقیق: URL استفاده شده در کد باید کاراکتر به کاراکتر با تنظیمات داشبورد Square匹配 باشد. بازسازی URL از هدرهای درخواست (به دلیل پراکسی/Load Balancer) یا تفاوت اسلش انتهایی (
/) باعث عدم تطابق میشود. از ثابت محیطی (Environment Variable) برای URL استفاده کنید.
نکات امنیتی و عملیاتی:
- همیشه از
crypto.timingSafeEqualبرای مقایسه امضا استفاده کنید تا از حملات تایمینگ جلوگیری شود. - هدر قدیمی
x-square-signature(SHA-1) منسوخ است؛ تنها از هدر SHA-256 استفاده کنید. - کلیدها و URLهای Sandbox و Production متمایز هستند.
پیشنهاد: اعتبارسنجی امضا فقط منبع را تضمین میکند، نه تحویل. برای جلوگیری از از دست رفتن رویدادهای مالی در زمان قطعی سرور، از لایهای مانند EventDock برای ذخیرهسازی، تأیید فوری و تحویل باRetry استفاده کنید.
امضای وب هوک Square افراد را به روشی خاص جلب می کند: امضا از طریق URL اعلان که به بدنه درخواست خام پیوست شده است، محاسبه می شود، نه بدن به تنهایی. URL را از دست ندهید، یا یک URL را بازسازی کنید که حتی با یک اسلش انتهایی متفاوت است، و هر اعلان اعتبارسنجی نمی شود در حالی که کد HMAC شما کاملاً صحیح به نظر می رسد. این پست طرح دقیقی را که Square استفاده میکند، دو تلهای که آن را میشکند و هدر قدیمیتری که نباید به آن اعتماد نکنید را نشان میدهد.
چه میدانی در واقع علامت میدهد
هر وب هوک از Square دارای یک x-square-hmacsha256-signature هدر بر اساس اسناد اعتبار سنجی خود Square، مقدار یک HMAC-SHA-256 است که از سه چیز ساخته شده است:
- را کلید امضا برای اشتراک وبهوک شما (در کنار اشتراک در داشبورد Square، یکی برای هر اشتراک یافت میشود).
- را URL اطلاع رسانی شما برای آن اشتراک پیکربندی کرده اید.
- را بدن خام درخواست، دقیقاً همانطور که ارسال شده است.
پیامی که از طریق HMAC اجرا میکنید URL اعلان است که بدنه خام و به ترتیب به هم پیوسته است و نتیجه با Base64 کدگذاری میشود. برای اعتبارسنجی، شما همان چیزی را محاسبه کرده و آن را با هدر مقایسه میکنید.
const crypto = require('crypto');
// From your webhook subscription in the Square Dashboard
const SIGNATURE_KEY = process.env.SQUARE_SIGNATURE_KEY;
// The EXACT notification URL you configured for the subscription
const NOTIFICATION_URL = 'https://api.yourapp.com/webhooks/square';
function isValidSquareSignature(rawBody, signatureHeader) {
const payload = NOTIFICATION_URL + rawBody; // URL first, then the raw body
const expected = crypto
.createHmac('sha256', SIGNATURE_KEY)
.update(payload, 'utf8')
.digest('base64');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: capture the RAW body, verify, then parse
app.post('/webhooks/square',
express.raw({ type: '*/*' }),
(req, res) => {
const rawBody = req.body.toString('utf8');
const signature = req.get('x-square-hmacsha256-signature');
if (!isValidSquareSignature(rawBody, signature)) {
return res.status(403).send('bad signature');
}
const event = JSON.parse(rawBody); // only parse after the check passes
// ... handle event
res.sendStatus(200);
});
به کاربردهای مقایسه توجه کنید crypto.timingSafeEqual به جای ===. یک مقایسه رشته ساده در اولین بایت متفاوت، زود برمی گردد، و این تفاوت زمان نشان می دهد که چه مقدار از امضای شما درست است. از یک مقایسه با زمان ثابت استفاده کنید و ابتدا از طول آن محافظت کنید زیرا timingSafeEqual باعث عدم تطابق طول می شود.
تله اول: بدن خام
HMAC بایت ها را علامت گذاری می کند، نه اشیاء را. اگر فریمورک شما JSON را تجزیه کند و رشته ای را از شی تجزیه شده برای تایید مجدد بسازید، بایت ها دیگر با آنچه Square ارسال کرده یکسان نیستند. ترتیب کلید، فضای خالی و قالب بندی اعداد همه جابجا می شوند و امضا هرگز مطابقت ندارد. بدنه خام و تجزیه نشده را ضبط کنید، آن را تأیید کنید، و تنها پس از رد شدن چک آن را تجزیه کنید. در Express به این معنی است express.raw() در مسیر وب هوک، نه express.json(). این همان تله Stripe، Paddle، HubSpot و Slack است که در مقایسه تایید امضای وب هوک در کنار هم قرار گرفته اند.
دام دوم: URL اطلاع رسانی باید دقیقا مطابقت داشته باشد
از آنجایی که URL بخشی از پیام امضا شده است، رشتهای که به هم متصل میکنید باید همان اعلان دقیق URL مربع برای اشتراک، کاراکتر برای کاراکتر باشد. این جایی است که اکثر خرابیهای Square از آنجا ناشی میشوند:
-
بازسازی URL از درخواست. اگر URL را از
req.protocolوreq.headers.host، یک متعادل کننده بار که TLS را خاتمه می دهد می تواند برنامه شما را تحویل دهدhttpبه جایhttps، یا میزبان دیگری، و URL بازسازی شده شما مطابقت ندارد. آن را از نو بسازید. از URL دقیقی که ثبت کرده اید به عنوان یک ثابت استفاده کنید. -
اسلش دنباله دار و تفاوت مسیر.
https://api.yourapp.com/webhooks/squareو همان URL با یک اسلش انتهایی امضاهای متفاوتی تولید می کند. آنچه را که در اشتراک ذخیره شده است دقیقاً مطابقت دهید. - Sandbox در مقابل تولید. این دو محیط دارای اشتراکهای جداگانه، کلیدهای امضای مجزا و معمولاً آدرسهای اینترنتی اعلان جداگانه هستند. تأیید یک رویداد جعبه ایمنی در برابر کلید تولید و URL شما هر بار با شکست مواجه میشود.
راه حل تمیز این است که URL اعلان را به عنوان یک ثابت شناخته شده در هر محیط به جای چیزی که در زمان درخواست استخراج می کنید، در نظر بگیرید. شما آن را پیکربندی کرده اید، بنابراین از قبل آن را می دانید. این همان کلاس مشکلات Twilio است، که در آن امضا به URL درخواست گره خورده است و یک پروکسی می تواند آن را خراب کند، که در راهنمای اعتبارسنجی امضای Twilio پوشش داده شده است.
استفاده از هدر x-square-signature قدیمی را متوقف کنید
ادغامهای مربع قدیمیتر یک را تأیید کردند x-square-signature هدر که از HMAC-SHA1 استفاده می کند. این طرح قدیمی است و به نفع هدر SHA-256 در بالا بازنشسته شده است. اگر کد شما همچنان خوانده می شود x-square-signature، حرکت به x-square-hmacsha256-signature. SHA1 یک هش نیست که در درب رویدادهای پرداخت خود بخواهید، و راهنمایی فعلی Square استفاده از امضای SHA-256 است.
اشتباهاتی که عامل اکثر شکست ها هستند
- امضا کردن فقط بدنه و فراموش کردن نشانی وب اعلان.
- تأیید در برابر یک بدنه سریالسازی مجدد به جای بایتهای خام.
- بازسازی URL از سرصفحه های درخواست در پشت پراکسی به جای استفاده از ثابت پیکربندی شده.
- استفاده از کلید امضای اشتراک اشتباه، یا عبور از جعبه ماسهبازی و کلیدهای تولید.
- مقایسه با
===به جای تابع زمان ثابت
امضاها فقط نیمی از آن هستند
تأیید اعتبار امضا ثابت می کند که یک اعلان واقعاً از Square آمده است. برای اعلانی که هرگز نمی رسد، اعلانی که در حالی که سرور شما در اواسط استقرار بود شلیک می شود، یا اعلانی که به دلیل اینکه کنترل کننده شما قبل از اینکه تایید کند پرتاب کرده است رها شده است، کاری انجام نمی دهد. Square تحویلهای ناموفق را برای مدتی دوباره امتحان میکند و سپس در صورت عدم موفقیت، اشتراک را غیرفعال میکند، بنابراین یک ساعت بد در سمت شما میتواند به اشتراکی تبدیل شود که Square ارسال به آن را متوقف کرده است. اتفاقاتی که در پس آن رخ می دهد، رویدادهایی هستند که با پول، پرداخت تکمیل شده، بازپرداخت، اختلاف، دقیقاً مواردی هستند که نمی توانید از دست بدهید.
این همان لایه ای است که EventDock اضافه می کند. شما Square را به جای برنامه خود به EventDock نشان می دهید. EventDock امضا را تأیید میکند، رویداد را ذخیره میکند، و Square را فوراً تأیید میکند، بنابراین هیچ مهلت یا پنجره توقفی برای شما هزینه تحویل ندارد، سپس آن را با تلاشهای مجدد خود و یک صف نامه مرده که میتوانید با دست دوباره پخش کنید، به برنامهتان ارسال میکند. اگر برنامه شما برای یک ساعت خاموش باشد، رویدادها به جای حذف شدن و در نهایت غیرفعال شدن، منتظر میمانند و پس از بازیابی میرسند. برای تحویلهای تکراری که هر خط لوله آزمایشی دوباره تولید میکند، الگوی پردازش دقیقاً یکبار نحوه بیتوان ماندن را پوشش میدهد.
میتوانید یک اشتراک Square sandbox را در EventDock در سطح رایگان نشان دهید و تأیید، ذخیره و تحویل هر رویداد را تماشا کنید. با EventDock رایگان شروع کنید.



