آنچه در این مقاله میخوانید [پنهانسازی]
Webhook چیست؟ یک فراخوانی HTTP یک طرفه است که وقتی رویدادی در نرم افزار A رخ می دهد، همان لحظه یک درخواست POST به آدرس از پیش تعیین شده در نرم افزار B می فرستد. نتیجه این است که دو نرم افزار بدون نظارت دستی و بدون Polling، با یک رویداد، ارتباط خودکار برقرار می کنند.
Webhook چگونه کار می کند؟
- تعریف رویداد: در نرم افزار مبدا مشخص می کنید کدام رویدادها (مثل ایجاد سفارش یا تغییر وضعیت) وبهوک را فعال کنند.
- ثبت مقصد: یک URL عمومی در نرم افزار مقصد (Endpoint) معرفی می کنید.
- ارسال درخواست: با وقوع رویداد، مبدا یک درخواست HTTP (معمولا POST با JSON) به مقصد می فرستد.
- تایید سریع: مقصد باید سریعا پاسخ 2xx بدهد تا مبدا درخواست را موفق بداند.
- پردازش آسنکرون: مقصد داده را در صف یا سرویس پس زمینه پردازش می کند تا تایم اوت رخ ندهد.
- تلاش مجدد: اگر مقصد خطای موقتی برگرداند، مبدا ممکن است با سیاست Retry دوباره ارسال کند؛ بنابراین باید تحمل ارسال تکراری را داشته باشید.
تفاوت با Polling و چه زمانی بهتر است؟
در Polling شما دوره ای API را چک می کنید؛ این کار یا با تاخیر همراه است یا بار اضافی ایجاد می کند. وبهوک برعکس، Push مبتنی بر رویداد است: تاخیر کمتر، منابع کمتر، هماهنگ با رخدادهای واقعی. اگر نیاز به اتصال بلادرنگ دو طرفه یا استریم پیوسته دارید، WebSocket مناسب تر است؛ اگر مقصد پشت فایروال خصوصی است و URL عمومی ندارد، Polling یا صف پیام گزینه بهتری است.
اجزای کلیدی یک Webhook قابل اتکا
- Endpoint: یک URL عمومی با HTTPS.
- روش و قالب: معمولا POST با Content-Type: application/json.
- هدرهای کنترل: نوع رویداد (مثلا X-Event)، شناسه پیام (X-Request-Id)، و معمولا امضای پیام (X-Signature) برای اعتبارسنجی.
- Payload: شامل نام رویداد، شناسه یکتا، مهر زمان و داده مرتبط.
- پاسخ: کد 2xx سریع. اگر نیاز به پردازش سنگین دارید، Ack بدهید و بعدا پردازش کنید.
- تحمل تکرار: رویدادها ممکن است دوبار برسند؛ Idempotency را پیاده کنید.
یک مثال واضح از سناریوی استفاده
فرض کنید در سیستم فروش شما فاکتور پرداخت شد. همان لحظه سرویس پرداخت یک Webhook به آدرس شما می فرستد. شما با دریافت آن، وضعیت سفارش را به پرداخت شده تغییر می دهید و ایمیل تایید ارسال می کنید.
{
"event": "invoice.paid",
"id": "evt_8f92ab7c",
"createdAt": "2026-09-26T07:47:00Z",
"data": {
"invoiceId": 12345,
"amount": 990000,
"currency": "IRR",
"customer": { "id": 777, "email": "user@example.com" }
}
}
پیاده سازی یک Endpoint امن در Node.js (Express)
هدف: ساخت یک مسیر /webhooks/example که امضای پیام را با HMAC-SHA256 بررسی کند، سریعا Ack بدهد و پردازش را آسنکرون انجام دهد. توجه: نام هدرها و فرمت امضا بین سرویس ها فرق دارد؛ در این نمونه فرض می کنیم هدر X-Signature به صورت sha256=<hex> ارسال می شود.
const express = require("express");
const crypto = require("crypto");
const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || "change_me";
// نگه داشتن بدنه خام برای محاسبه امضا
app.use(express.json({
verify: (req, res, buf) => { req.rawBody = buf; }
}));
// حافظه ساده برای جلوگیری از پردازش دوباره (در تولید از دیتابیس یا کش استفاده کنید)
const processed = new Set();
app.post("/webhooks/example", (req, res) => {
const signatureHeader = req.get("X-Signature") || "";
const received = signatureHeader.replace(/^sha256=/, "");
// محاسبه امضا روی بدنه خام
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.rawBody)
.digest("hex");
const validSig = received.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!validSig) {
return res.status(401).send("invalid signature");
}
// Idempotency با شناسه رویداد
const eventId = req.body?.id || req.get("X-Request-Id");
if (eventId && processed.has(eventId)) {
return res.status(200).send("duplicate ignored");
}
// Ack سریع
res.status(200).send("ok");
// پردازش آسنکرون
queueMicrotask(() => {
try {
if (eventId) processed.add(eventId);
const { event, data } = req.body;
// مثال: بروزرسانی وضعیت سفارش یا صدور رسید
handleEvent(event, data);
} catch (e) {
console.error("processing error:", e);
// در عمل: ثبت در صف خطا یا تلاش مجدد داخلی
}
});
});
function handleEvent(event, data) {
if (event === "invoice.paid") {
console.log("mark invoice as paid:", data.invoiceId);
// updateDB(data.invoiceId) ...
} else {
console.log("unhandled event:", event);
}
}
app.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT}`);
});
نکات مهم این کد:
- امضای پیام را قبل از دستکاری بدنه بررسی کنید؛ هر تغییری در فرمت JSON می تواند امضا را نامعتبر کند.
- Ack سریع (2xx) بدهید و کار سنگین را پس از پاسخ انجام دهید.
- برای جلوگیری از پردازش تکراری از شناسه یکتا استفاده کنید.
تست محلی و بررسی نتیجه
برای تست، همان بدنه ای را که ارسال می کنید امضا کنید و در هدر بفرستید. مراقب باشید فاصله ها و ترتیب کلیدهای JSON روی امضا اثر دارد.
# بدنه را در یک فایل ذخیره کنید
cat > body.json <<EOF
{"event":"invoice.paid","id":"evt_test","data":{"invoiceId":42}}
EOF
# محاسبه امضا با secret دلخواه (مطابق WEBHOOK_SECRET)
SIG=$(cat body.json | openssl dgst -sha256 -hmac "change_me" -binary | xxd -p -c 256)
# ارسال درخواست با امضا
curl -i -X POST http://localhost:3000/webhooks/example \
-H "Content-Type: application/json" \
-H "X-Signature: sha256=$SIG" \
--data-binary @body.json
روش بررسی نتیجه:
- در لاگ سرور باید پیام ok و سپس لاگ پردازش رویداد را ببینید.
- اگر امضا اشتباه باشد، باید 401 دریافت کنید.
- با تکرار همان درخواست، باید پاسخ duplicate ignored یا معادل آن را ببینید.
نکات امنیتی ضروری
- HTTPS اجباری است؛ هرگز Webhook را روی HTTP بدون TLS نپذیرید.
- اعتبارسنجی امضا یا توکن اشتراکی را پیاده کنید. مقادیر را با مقایسه ثابت زمان (timing-safe) بررسی کنید.
- در برابر حملات Replay: از مهر زمان و انقضا استفاده کنید و رویدادهای قدیمی را رد کنید.
- Payload را اسکیما-ولیدیت کنید تا از تزریق داده جلوگیری شود.
- محدودیت اندازه بدنه و نرخ درخواست (Rate Limit) اعمال کنید.
- اسرار (Secret) را بچرخانید و در لاگ ها یا خطاها افشا نکنید.
- در صورت ارائه دهنده، می توانید IP allowlist را به عنوان لایه اضافی اضافه کنید.
خطاهای رایج و راه حل
- پردازش طولانی قبل از پاسخ: باعث تایم اوت و Retry می شود. راه حل: Ack سریع و پردازش آسنکرون.
- اعتماد کور به داده: ممکن است ساختار یا نوع داده غیرمنتظره باشد. راه حل: ولیدیشن و کنترل خطا.
- عدم توجه به تکراری بودن: رویداد ممکن است چند بار برسد. راه حل: Idempotency با کلید یکتا.
- بررسی امضا روی بدنه پارس شده: به دلیل تغییر فرمت شکست می خورد. راه حل: امضا را روی بدنه خام بررسی کنید.
- بازگرداندن کد 200 حتی در خطای اعتبارسنجی: منبع تصور می کند موفق بوده است. راه حل: کد مناسب (4xx/5xx) را برگردانید.
چه زمانی Webhook انتخاب خوبی نیست؟
- مقصد URL عمومی ندارد یا پشت شبکه خصوصی است.
- به تضمین قوی ترتیب و تحویل نیاز دارید. در این حالت از صف پیام/انتشار-اشتراک استفاده کنید.
- کلاینت موبایل ناپایدار است و نمی تواند شنونده HTTP باشد؛ بهتر است از Push notification یا Polling هدفمند استفاده کنید.
چک لیست اجرایی راه اندازی Webhook
- تعریف رویدادها و اسکیما payload.
- طراحی Endpoint با HTTPS، پاسخ سریع و صف پس زمینه.
- اعتبارسنجی امضا و Idempotency.
- محدودیت اندازه بدنه، ولیدیشن، لاگ گذاری و مانیتورینگ.
- سناریوهای تست: موفق، امضای نامعتبر، تکراری، خطای موقت.
- مستندسازی و چرخش دوره ای Secret.
گام بعدی چیست؟
رویدادهای مهم سیستم خود را فهرست کنید، یک Endpoint ساده با تایید امضا بسازید، با ابزار تونلینگ در محیط توسعه آن را در دسترس قرار دهید و با سناریوهای موفق، نامعتبر و تکراری تست کنید. سپس پردازش ها را به صف منتقل و مانیتورینگ خطا را فعال کنید.







