مستنداترویدادها
وبهوک
رویدادهای تحویل، برگشت و شکایت را همان لحظه به سرور خودتان بگیرید، با امضای HMAC.
آخرین ویرایش: ۲۱ آبان ۱۴۰۵
بهجای اینکه وضعیت هر ایمیل را بپرسید، سندک خودش وقتی چیزی اتفاق افتاد به نشانی شما POST میکند. وبهوکها را در پنل، بخش «وبهوکها»، میسازید.
نکته: تعداد وبهوکها به پلن بستگی دارد. پلن رایگان وبهوک ندارد؛ سقف هر پلن را در تعرفه ببینید.
نشانی
نشانی باید https و عمومی باشد، روی پورت 443 یا 8443، و نام کاربری و رمز نداشته باشد. نشانیهایی که به شبکهی داخلی یا آدرس خصوصی اشاره میکنند رد میشوند و ریدایرکت دنبال نمیشود. این قاعدهها از سرور سندک در برابر سوءاستفاده محافظت میکنند.
ساخت و تغییر نشانی وبهوک نیاز به تأیید رمز عبور دارد.
رویدادها
هر وبهوک مشخص میکند کدام رویدادها را میخواهد:
| رویداد | معنا |
|---|---|
message.accepted | پیام پذیرفته و در صف قرار گرفت |
message.deferred | گیرنده موقتاً نپذیرفت؛ سندک دوباره امتحان میکند |
message.delivered | سرور گیرنده ایمیل را پذیرفت |
message.bounced | برگشت خورد (نشانی وجود ندارد، رد شد، یا مهلت تمام شد) |
message.complained | گیرنده آن را اسپم گزارش کرد |
message.opened | باز شد (فقط با ردیابی) |
message.clicked | روی پیوندی کلیک شد (فقط با ردیابی) |
email.received | نامهای به یکی از صندوقهای شما رسید |
توجه:
deliveredیعنی سرور گیرنده ایمیل را پذیرفت، نه اینکه کاربر آن را در Inbox دید. ممکن است در اسپم قرار بگیرد.
درخواستی که میگیرید
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Sendak-Webhooks/1
X-Sendak-Event-Id: 01JCX9…
X-Sendak-Event-Type: message.delivered
X-Sendak-Signature: t=1731400000,v1=5a7c…{
"id": "01JCX9…",
"type": "message.delivered",
"created_at": "2026-11-12T12:04:03+00:00",
"data": {
"message_id": "01JCX8…",
"from_domain": "shop.ir",
"recipient": "ali@gmail.com",
"status": "delivered",
"response_code": 250,
"tags": ["otp"],
"metadata": { "order": "1201" }
}
}فیلدهای data بسته به رویداد کم یا زیاد میشوند. اینها همیشه هستند: message_id، from_domain، recipient، status. فیلدهای response_code، classification (دستهی برگشت)، attempt، tags و metadata فقط وقتی مقدار دارند میآیند.
message_id همان شناسهای است که هنگام ارسال در پاسخ API گرفتید.
بررسی امضا
هدر X-Sendak-Signature به شکل t=<زمان یونیکس>,v1=<امضا> است. امضا HMAC-SHA256 رشتهی <t>.<بدنهی خام> با راز وبهوک است. امضا را روی بدنهی خام حساب کنید، نه روی JSON دوبارهساختهشده.
import crypto from 'node:crypto'
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const t = Number(parts.t)
// A request older than five minutes is refused, so a captured one cannot be replayed later.
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const given = Buffer.from(parts.v1 ?? '', 'hex')
const wanted = Buffer.from(expected, 'hex')
return given.length === wanted.length && crypto.timingSafeEqual(given, wanted)
}function verifySendak(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
parse_str(str_replace(',', '&', $header), $parts);
$t = (int) ($parts['t'] ?? 0);
if ($t === 0 || abs(time() - $t) > $tolerance) {
return false;
}
return hash_equals(hash_hmac('sha256', $t.'.'.$rawBody, $secret), (string) ($parts['v1'] ?? ''));
}import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if t == 0 or abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))پاسخ دادن
فقط پاسخ 2xx موفق است. سریع پاسخ بدهید (مثلاً رویداد را در صف خودتان بگذارید و 200 برگردانید) و کار سنگین را بعداً انجام دهید.
تلاش دوباره
اگر پاسخ 2xx نگیریم، تا هشت بار دوباره امتحان میکنیم. فاصلهها بعد از هر شکست:
| تلاش | فاصله تا بعدی |
|---|---|
| ۱ | ۱ دقیقه |
| ۲ | ۵ دقیقه |
| ۳ | ۳۰ دقیقه |
| ۴ | ۲ ساعت |
| ۵ | ۶ ساعت |
| ۶ | ۱۲ ساعت |
| ۷ | ۲۴ ساعت |
| ۸ | ۲۴ ساعت |
در مجموع حدود سه روز. اگر هر هشت تلاش یک رویداد شکست بخورد، آن رویداد «ناموفق» ثبت میشود. بعد از ۱۰ رویداد ناموفق پشتسرهم، وبهوک خاموش میشود و ایمیل اخطار میگیرید؛ بعد از رفع مشکل باید دوباره روشنش کنید. یک پاسخ موفق شمارنده را صفر میکند.
در پنل، برای هر وبهوک فهرست تحویلها با کد پاسخ هست و میتوانید هر کدام را دستی دوباره بفرستید.
رویداد تکراری
ممکن است یک رویداد بیش از یک بار برسد (مثلاً پاسخ شما در راه گم شده باشد). X-Sendak-Event-Id در همهی تلاشهای همان رویداد ثابت است؛ با آن رویداد تکراری را کنار بگذارید. ترتیب رسیدن رویدادها هم تضمین نمیشود: مثلاً ممکن است delivered قبل از accepted برسد.
چرخاندن راز
اگر راز وبهوک لو رفت، «چرخش رمز امضا» را بزنید و راز تازه را همان لحظه در سرور خودتان بگذارید. تا آن لحظه، درخواستها با راز قدیمی تأیید نمیشوند.
آزمایش
از پنل، «ارسال آزمایشی» یک رویداد نمونه با نوع webhook.test میفرستد تا نشانی و امضا را بدون ایمیل واقعی بررسی کنید. این رویداد فقط یک بار و همان لحظه فرستاده میشود و نتیجهاش (کد پاسخ) در پنل دیده میشود.
{
"id": "test",
"type": "webhook.test",
"created_at": "2026-11-12T12:00:00+03:30",
"data": { "message": "This is a test from Sendak." }
}