MailLandDocs
وب‌هوک‌ها

تأیید امضا

تأیید امضای 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-Signaturet=1708171200,v1=abc123...زمان‌نشانه و امضای HMAC-SHA256
X-MLND-Event-Idevt_a1b2c3d4e5f6...شناسه یکتای رویداد برای حذف تکراری
X-MLND-Eventmessage.deliveredنوع رویداد
X-MLND-Event-Version1نسخه طرح داده
X-MLND-Webhook-Idwh_abc123def456شناسه عمومی webhook شما
X-MLND-Attempt1شماره تلاش تحویل (از ۱ شروع می‌شود)
X-MLND-Delivery-Idp8r2kx7m9wq4rtشناسهٔ همین تلاش که در هر تلاش دوباره تازه است؛ همان publicId در تاریخچهٔ تحویل. برای یکتاسازی نیست: نسخه‌بندی را ببینید
X-MLND-Delivery-Timestamp1708171200زمان فرستادن همین تلاش (ثانیهٔ یونیکس)؛ همیشه برابر t در امضا

دو هدر دیگر بسته به این‌که مشترک کدام رویداد باشید، به‌صورت شرطی ارسال می‌شوند:

هدرمثالچه زمانی ارسال می‌شود
X-MLND-Sourceinboundفقط در تحویل‌های inbound.received وجود دارد
X-MLND-Inbound-Email-Idinb_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 موجود در هدر استفاده کنید.

اعتبارسنجی زمان‌نشانه

بررسی کنید که زمان‌نشانه در محدوده ۵ دقیقه از زمان فعلی باشد تا از حملات بازپخش جلوگیری شود.

نمونه کد

verify-signature.sh
#!/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 شما لو رفته است:

  1. برای دریافت کلید جدید POST /v1/webhooks/{id}/regenerate-secret را فراخوانی کنید
  2. کد تأییدیه سرور خود را با کلید جدید به‌روز کنید
  3. کلید قدیمی فوراً باطل می‌شود

هنگام بازتولید کلیدهای سری هیچ دوره تنفسی وجود ندارد. کد تأییدیه خود را قبل از تحویل webhook بعدی به‌روز کنید تا از رد شدن رویدادهای معتبر جلوگیری شود.

اشتباهات رایج

  • تجزیه بدنه به عنوان JSON قبل از تأیید: امضا روی بایت‌های خام محاسبه می‌شود، نه یک شیء JSON دوباره سریال‌شده. ابتدا تأیید کنید، سپس تجزیه کنید.
  • استفاده از مقایسه رشته به جای مقایسه زمان ثابت: مقایسه رشته معمولی در برابر حملات زمانی آسیب‌پذیر است.
  • نادیده گرفتن زمان‌نشانه: بدون اعتبارسنجی زمان‌نشانه، داده‌های ضبط‌شده می‌توانند به طور نامحدود دوباره پخش شوند.
  • کدگذاری ثابت کلید سری: از متغیرهای محیطی یا مدیر اسرار استفاده کنید.

مراحل بعدی

در این صفحه