تأیید امضا
تأیید امضای webhook برای اطمینان از اصالت دادههای webhook دریافتی
هر تحویل webhook شامل یک امضای رمزنگاریشده است تا بتوانید تأیید کنید که درخواست از MailLand آمده و در حین انتقال دستکاری نشده است. همیشه امضاها را در محیط تولید تأیید کنید.
نحوه امضاکردن
MailLand هر بار دادههای webhook را با استفاده از HMAC-SHA256 و کلید سری webhook شما امضا میکند. امضا در هدر X-MLND-Signature گنجانده میشود.
قالب امضا:
X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67| مؤلفه | توضیحات |
|---|---|
t | زماننشانه یونیکس (ثانیه) در زمان تولید امضا |
v1 | امضای هگزادسیمال HMAC-SHA256 (حروف کوچک) |
دادههای امضاشده:
{timestamp}.{raw_json_body}امضا روی زماننشانهای که با یک نقطه و بدنه خام JSON درخواست به هم متصل شده محاسبه میشود.
مثال کامل
پیش از راهاندازی، پیادهسازی خود را دقیقاً با این مقادیر بسنجید. همه چیز در ادامه واقعی است: امضا همان چیزی است که MailLand برای این کلید، این مهر زمانی و این بدنه تولید میکند.
| ورودی | مقدار |
|---|---|
| کلید | whsec_docs_example_secret_do_not_use |
مهر زمانی (t) | 1708171200 |
| بدنهٔ خام | {"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"} |
رشتهٔ امضاشده: مهر زمانی، یک نقطه و سپس بدنه بدون تغییر:
1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}مقدار HMAC-SHA256 این رشته با کلید مذکور، بهصورت هگزادسیمال کوچک:
b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67بنابراین سرایند این تحویل چنین خواهد بود:
X-MLND-Signature: t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67همان مقدار را در یک خط بازتولید کنید:
printf '%s' '1708171200.{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}' \
| openssl dgst -sha256 -hmac 'whsec_docs_example_secret_do_not_use'مهر زمانی این مثال مربوط به گذشته است، بنابراین یک تأییدکنندهٔ واقعی آن را بهدلیل پنجرهٔ تحمل رد میکند. ابتدا HMAC را با غیرفعالکردن بررسی زمان آزمایش کنید و سپس دوباره آن را فعال کنید.
هدرهای ارسالشده
هر تحویل webhook این هدرها را شامل میشود:
| هدر | مثال | توضیحات |
|---|---|---|
X-MLND-Signature | t=1708171200,v1=abc123... | زماننشانه و امضای HMAC-SHA256 |
X-MLND-Event-Id | evt_a1b2c3d4e5f6... | شناسه یکتای رویداد برای حذف تکراری |
X-MLND-Event | message.delivered | نوع رویداد |
X-MLND-Event-Version | 1 | نسخه طرح داده |
X-MLND-Webhook-Id | wh_abc123def456 | شناسه عمومی webhook شما |
X-MLND-Attempt | 1 | شماره تلاش تحویل (از ۱ شروع میشود) |
X-MLND-Delivery-Id | p8r2kx7m9wq4rt | شناسهٔ همین تلاش که در هر تلاش دوباره تازه است؛ همان publicId در تاریخچهٔ تحویل. برای یکتاسازی نیست: نسخهبندی را ببینید |
X-MLND-Delivery-Timestamp | 1708171200 | زمان فرستادن همین تلاش (ثانیهٔ یونیکس)؛ همیشه برابر t در امضا |
دو هدر دیگر بسته به اینکه مشترک کدام رویداد باشید، بهصورت شرطی ارسال میشوند:
| هدر | مثال | چه زمانی ارسال میشود |
|---|---|---|
X-MLND-Source | inbound | فقط در تحویلهای inbound.received وجود دارد |
X-MLND-Inbound-Email-Id | inb_a1b2c3d4e5f6 | فقط در تحویلهای inbound.received وجود دارد |
هدر X-MLND-Message-Id در حال حاضر در تحویلهای message.* ارسال نمیشود. بهجای آن از فیلد data.message_id در بدنهی رویداد استفاده کنید.
مراحل تأیید
استخراج زماننشانه و امضا
هدر X-MLND-Signature را تجزیه کنید. با تقسیم بر , مؤلفههای t=... و v1=... را استخراج کنید.
آمادهسازی دادههای امضاشده
زماننشانه، یک نقطه (.) و بدنه خام درخواست را به هم متصل کنید:
signed_payload = timestamp + "." + raw_bodyمحاسبه امضای مورد انتظار
با استفاده از کلید سری webhook به عنوان کلید و دادههای امضاشده به عنوان پیام، یک هش HMAC-SHA256 تولید کنید. آن را به هگزادسیمال با حروف کوچک تبدیل کنید.
مقایسه امضاها
از مقایسه زمان ثابت برای بررسی تطابق امضای محاسبهشده با مقدار v1 موجود در هدر استفاده کنید.
اعتبارسنجی زماننشانه
بررسی کنید که زماننشانه در محدوده ۵ دقیقه از زمان فعلی باشد تا از حملات بازپخش جلوگیری شود.
نمونه کد
#!/usr/bin/env bash
# Compute and verify an HMAC-SHA256 webhook signature using openssl
SIGNATURE_HEADER="t=1708171200,v1=b9f747ea899eafe911777060ca8f3c90828d37f1e16ee6e9ee98dacf8b316b67"
RAW_BODY='{"event":"message.delivered","event_id":"evt_a1b2c3d4e5f6"}'
SECRET="whsec_docs_example_secret_do_not_use"
TOLERANCE=300
# Parse timestamp and signature from header
TIMESTAMP=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=t=)[^,]+')
RECEIVED_SIG=$(echo "$SIGNATURE_HEADER" | grep -oP '(?<=v1=).+')
# Check timestamp tolerance
NOW=$(date +%s)
DIFF=$(( NOW - TIMESTAMP ))
if [ "${DIFF#-}" -gt "$TOLERANCE" ]; then
echo "Error: Timestamp outside tolerance window" >&2
exit 1
fi
# Compute expected signature
SIGNED_PAYLOAD="${TIMESTAMP}.${RAW_BODY}"
EXPECTED=$(printf '%s' "$SIGNED_PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
# Compare signatures
if [ "$EXPECTED" = "$RECEIVED_SIG" ]; then
echo "Signature valid"
else
echo "Signature invalid" >&2
exit 1
fiپیشگیری از حملات بازپخش
زماننشانه موجود در هدر امضا از حملات بازپخش جلوگیری میکند. اگر مهاجمی یک تحویل معتبر webhook را ضبط کند و بعداً آن را دوباره ارسال کند، بررسی زماننشانه آن را رد خواهد کرد.
تحمل پیشفرض MailLand ۵ دقیقه (۳۰۰ ثانیه) است. میتوانید این مقدار را در کد تأییدیه خود تنظیم کنید، اما آن را به اندازه کافی کوتاه نگه دارید تا از حملات بازپخش معنادار جلوگیری شود.
علاوه بر این، هر رویداد دارای یک event_id یکتا (در هدر X-MLND-Event-Id) است. شناسههای رویدادهای پردازششده را ذخیره کرده و موارد تکراری را برای محافظت بیشتر رد کنید.
MailLand در سمت خود قصد ارسال را یکتا میکند: رویدادی که دو بار تولید شود، در بازهٔ ۲۴ ساعته تنها یکبار برای هر وبهوک ارسال میشود. این با «حداکثر یکبار تحویل» یکی نیست: تحویل ناموفق دوباره تلاش میشود و هر تلاش همان event_id و همان بدنه را دارد؛ تنها X-MLND-Attempt و مهر زمانی امضا تغییر میکند. بر پایهٔ event_id یکتاسازی کنید و تحویل تکراری را طبیعی بدانید.
هر تحویل امضا دارد
هیچ مسیر تحویل بدون امضایی وجود ندارد. اگر webhookای کلید سری امضای قابلاستفادهای نداشته باشد،
MailLand تحویل را شکستخورده اعلام میکند و آن شکست را ثبت میکند، بهجای اینکه payload را بدون
هدر X-MLND-Signature ارسال کند؛ پس هدر امضای گمشده هرگز چیزی نیست که تأییدکنندهٔ شما مجبور به
تحمل آن باشد. هر درخواستی را که این هدر را نداشته باشد رد کنید.
بازتولید کلیدهای سری
اگر مشکوک هستید که کلید سری webhook شما لو رفته است:
- برای دریافت کلید جدید
POST /v1/webhooks/{id}/regenerate-secretرا فراخوانی کنید - کد تأییدیه سرور خود را با کلید جدید بهروز کنید
- کلید قدیمی فوراً باطل میشود
هنگام بازتولید کلیدهای سری هیچ دوره تنفسی وجود ندارد. کد تأییدیه خود را قبل از تحویل webhook بعدی بهروز کنید تا از رد شدن رویدادهای معتبر جلوگیری شود.
اشتباهات رایج
- تجزیه بدنه به عنوان JSON قبل از تأیید: امضا روی بایتهای خام محاسبه میشود، نه یک شیء JSON دوباره سریالشده. ابتدا تأیید کنید، سپس تجزیه کنید.
- استفاده از مقایسه رشته به جای مقایسه زمان ثابت: مقایسه رشته معمولی در برابر حملات زمانی آسیبپذیر است.
- نادیده گرفتن زماننشانه: بدون اعتبارسنجی زماننشانه، دادههای ضبطشده میتوانند به طور نامحدود دوباره پخش شوند.
- کدگذاری ثابت کلید سری: از متغیرهای محیطی یا مدیر اسرار استفاده کنید.
مراحل بعدی
- منطق تلاش مجدد: رفتار تلاش مجدد و مدیریت خطا را درک کنید
- رویدادها: مرجع کامل انواع رویداد
- مرور کلی Webhookها: webhookها را ایجاد و مدیریت کنید