Webhook و Callback رویدادها

Webhook برای سامانه‌هایی است که نمی‌خواهند وضعیت پیام یا پیام‌های ورودی را دائماً Poll کنند. SMSDaily رویداد را با POST و بدنه JSON به آدرس HTTPS ثبت‌شده مشتری ارسال می‌کند.

در نسخه فعلی دو رویداد قابل عضویت هستند:

رویداد کاربرد
message.status تغییر وضعیت یک پیام خروجی
inbound.received در دسترس شدن یک پیام ورودی نرمال‌شده برای حساب

رویداد webhook.test فقط توسط Endpoint تست تولید می‌شود و در فهرست Subscription ثبت نمی‌شود.

ایجاد Webhook

POST /v1/webhooks/
X-API-Key: YOUR_API_KEY
Content-Type: application/json
{
  "url": "https://example.com/integrations/smsdaily/webhook",
  "events": [
    "message.status",
    "inbound.received"
  ]
}

محدودیت‌های امنیتی آدرس مقصد:

  • فقط HTTPS پذیرفته می‌شود.
  • در نسخه فعلی فقط پورت استاندارد 443 مجاز است.
  • مقصد باید به IP عمومی اینترنتی resolve شود؛ localhost، شبکه خصوصی و آدرس‌های رزرو‌شده پذیرفته نمی‌شوند.
  • Redirect سمت مقصد دنبال نمی‌شود.
  • حداکثر ۵ Webhook برای هر حساب قابل تعریف است.

نمونه پاسخ ایجاد:

{
  "id": "18a08676-47e2-4be0-b10b-2301135aec45",
  "url": "https://example.com/integrations/smsdaily/webhook",
  "events": ["inbound.received", "message.status"],
  "isActive": true,
  "signingSecret": "KEEP_THIS_SECRET_IN_YOUR_SECRET_STORE",
  "createdAtUtc": "2026-08-12T07:10:00Z",
  "updatedAtUtc": "2026-08-12T07:10:00Z"
}

signingSecret را هنگام ایجاد در Secret Store ذخیره کنید. پاسخ‌های فهرست و ویرایش، Secret را دوباره نمایش نمی‌دهند.

مدیریت Webhookها

GET /v1/webhooks/
PATCH /v1/webhooks/{id}
DELETE /v1/webhooks/{id}
POST /v1/webhooks/{id}/test

نمونه ویرایش:

{
  "events": ["message.status"],
  "isActive": true
}

Endpoint تست یک رویداد آزمایشی را در صف Callback قرار می‌دهد:

{
  "eventId": "evt_test_73efed4184e947f695bfd313ddd11ab0",
  "status": "queued"
}

ساختار Callback

نمونه رویداد تغییر وضعیت پیام:

{
  "id": "evt_msg_10421",
  "type": "message.status",
  "createdAt": "2026-08-12T07:15:18Z",
  "data": {
    "trackingCode": "SMS-20260812-A1B2C3D4",
    "clientReference": "order-781",
    "sender": "300099000121",
    "recipient": "09121234567",
    "status": "delivered",
    "changedAt": "2026-08-12T07:15:18Z"
  }
}

مقادیر عمومی status همان وضعیت‌های قرارداد Public API هستند: queued، processing، sent، delivered، failed و cancelled.

نمونه پیام ورودی:

{
  "id": "evt_in_c3848e3d4ca74748a68979d634882ecf",
  "type": "inbound.received",
  "createdAt": "2026-08-12T07:18:41Z",
  "data": {
    "trackingCode": "IN-20260812-22AABBCC",
    "sender": "09121234567",
    "receiver": "300099000121",
    "message": "تایید",
    "receivedAt": "2026-08-12T07:18:41Z"
  }
}

فقط پیام ورودی‌ای Callback می‌شود که در هسته SMSDaily به حساب مشتری نگاشت و به وضعیت قابل استفاده (Available) رسیده باشد. وجود API یا Webhook ورودی به معنی پشتیبانی دریافت روی همه خطوط و Providerها نیست.

هدرهای امنیتی Callback

در هر درخواست SMSDaily این هدرها را ارسال می‌کند:

X-SMSDaily-Event-Id: evt_msg_10421
X-SMSDaily-Event: message.status
X-SMSDaily-Timestamp: 1786520118
X-SMSDaily-Signature: v1=9f...64-hex-characters...

امضا روی بدنه خام درخواست محاسبه می‌شود:

signedPayload = unixTimestamp + "." + rawRequestBody
signature = HMAC-SHA256(signingSecret, signedPayload)

برای بررسی امن:

  1. مقدار X-SMSDaily-Timestamp را بخوانید و درخواست‌های قدیمی‌تر از بازه قابل قبول خودتان، مثلاً ۵ دقیقه، رد کنید.
  2. بدنه را قبل از Parse شدن و دقیقاً به همان شکل دریافت‌شده نگه دارید.
  3. HMAC را با signingSecret محاسبه کنید.
  4. نتیجه را با مقدار پس از v1= به‌صورت ثابت‌زمان (constant-time) مقایسه کنید.
  5. X-SMSDaily-Event-Id را برای Idempotency ذخیره کنید و یک رویداد تکراری را دوباره پردازش نکنید.

نمونه اعتبارسنجی با SDK رسمی .NET

using SmsDaily.Client;

var valid = WebhookSignature.Verify(
    signingSecret,
    Request.Headers["X-SMSDaily-Timestamp"].ToString(),
    Request.Headers["X-SMSDaily-Signature"].ToString(),
    rawBody,
    tolerance: TimeSpan.FromMinutes(5));

if (!valid)
{
    return Results.Unauthorized();
}

Retry و پاسخ Endpoint مشتری

Endpoint مشتری باید پس از اعتبارسنجی و ثبت رویداد، سریعاً پاسخ 2xx برگرداند. پردازش سنگین را بهتر است داخل Queue خودتان انجام دهید.

در صورت Timeout، خطای شبکه یا پاسخ غیر 2xx، SMSDaily Callback را با Backoff نمایی Retry می‌کند. تنظیم پیش‌فرض سرور حداکثر ۸ تلاش است و فاصله Retry تا حداکثر یک ساعت افزایش پیدا می‌کند. به همین دلیل Receiver باید حتماً Idempotent باشد.

Webhook جایگزین Endpointهای Polling نیست؛ می‌توانید برای بازیابی یا تطبیق نهایی همچنان از GET /v1/messages/{trackingCode} و GET /v1/inbound-messages استفاده کنید.