Webhooks
رویدادهای شاهو رو در لحظه به سیستم خودت دریافت کن — با احراز هویت HMAC و تلاش مجدد خودکار.
راهاندازی Webhook
- در پنل، برو به تنظیمات ← اتصالها
- روی افزودن Webhook کلیک کن
- آدرس URL خودت رو وارد کن (مثلاً
https://example.com/webhook) - رویدادهایی که میخوای دریافت کنی رو انتخاب کن
- یک Secret Key بساز یا خودت وارد کن
- ذخیره کن
رویدادهای پشتیبانیشده
| رویداد | زمان ارسال |
|---|---|
message.created |
پیام جدیدی از مشتری دریافت یا به او ارسال شد |
conversation.created |
مکالمهی جدیدی شروع شد |
conversation.assigned |
مکالمه به اپراتور یا تیم ارجاع داده شد |
conversation.closed |
مکالمه بسته شد |
customer.created |
مشتری جدیدی به سیستم اضافه شد |
customer.updated |
اطلاعات مشتری تغییر کرد |
customer.tag_added |
تگ جدیدی به مشتری اضافه شد |
automation.triggered |
یک قانون اتوماسیون اجرا شد |
ai.summary_created |
خلاصهی AI برای مکالمه ساخته شد |
ساختار Payload
هر درخواست Webhook یک بدنهی JSON داره که ساختار کلی آن به این صورته:
{
"event": "message.created",
"timestamp": "2025-09-30T14:32:00Z",
"id": "evt_abc123def456",
"data": {
...
}
}
| فیلد | توضیح |
|---|---|
event |
نام رویداد |
timestamp |
زمان وقوع (ISO 8601، UTC) |
id |
شناسهی یکتای رویداد (برای جلوگیری از تکرار) |
data |
اطلاعات مربوط به رویداد |
نمونه: message.created
{
"event": "message.created",
"timestamp": "2025-09-30T14:32:00Z",
"id": "evt_abc123def456",
"data": {
"message_id": "msg_xyz789",
"conversation_id": "conv_abc123",
"sender_type": "customer",
"content": "سلام، درباره پلنها سوال داشتم",
"channel": "whatsapp",
"created_at": "2025-09-30T14:32:00Z",
"customer": {
"id": "cust_xyz789",
"name": "مریم رضایی",
"phone": "09123456789",
"email": "maryam@example.com"
},
"conversation": {
"id": "conv_abc123",
"status": "open",
"channel": "whatsapp",
"assigned_to": null,
"tags": ["سوال قیمت"]
}
}
}
نمونه: conversation.created
{
"event": "conversation.created",
"timestamp": "2025-09-30T14:30:00Z",
"id": "evt_conv_def456",
"data": {
"conversation_id": "conv_abc123",
"channel": "whatsapp",
"status": "open",
"customer": {
"id": "cust_xyz789",
"name": "مریم رضایی",
"phone": "09123456789"
},
"first_message": "سلام، وقت بخیر",
"created_at": "2025-09-30T14:30:00Z"
}
}
نمونه: customer.created
{
"event": "customer.created",
"timestamp": "2025-09-30T14:30:00Z",
"id": "evt_cust_ghi789",
"data": {
"customer_id": "cust_xyz789",
"first_name": "مریم",
"last_name": "رضایی",
"phone": "09123456789",
"email": "maryam@example.com",
"status": "lead",
"channel": "whatsapp",
"created_at": "2025-09-30T14:30:00Z"
}
}
هدرهای درخواست
هر درخواست Webhook این هدرها رو داره:
| هدر | توضیح |
|---|---|
Content-Type |
application/json |
User-Agent |
Shahoo-Webhook/1.0 |
X-Shahoo-Event |
نام رویداد |
X-Shahoo-Delivery |
شناسهی یکتای تحویل |
X-Shahoo-Signature |
امضای HMAC-SHA256 |
X-Shahoo-Timestamp |
زمان ارسال (برای جلوگیری از replay attack) |
احراز هویت با HMAC
برای اطمینان از اینکه درخواست واقعاً از طرف شاهو اومده، باید امضای
X-Shahoo-Signature رو تأیید کنی.
روش تأیید
- بدنهی درخواست خام (raw body) رو بردار
- با
HMAC-SHA256و Secret Key خودت امضا کن - نتیجه رو با
X-Shahoo-Signatureمقایسه کن - اگر مطابقت داشت، درخواست معتبره
نمونه پایتون
import hmac
import hashlib
def verify_signature(raw_body, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
نمونه Node.js
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
نمونه Django
import hmac
import hashlib
from django.http import HttpResponse
def webhook_view(request):
signature = request.headers.get('X-Shahoo-Signature', '')
secret = 'YOUR_WEBHOOK_SECRET'
expected = hmac.new(
secret.encode(),
request.body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
return HttpResponse(status=401)
import json
payload = json.loads(request.body)
event = payload['event']
if event == 'message.created':
handle_new_message(payload['data'])
elif event == 'conversation.created':
handle_new_conversation(payload['data'])
return HttpResponse(status=200)
پاسخ مورد انتظار
سرور تو باید با کد وضعیت 2xx پاسخ بده تا تحویل موفق تلقی بشه. اگر پاسخ با تأخیر یا خطا باشه:
| وضعیت | رفتار شاهو |
|---|---|
| 2xx | موفق — تحویل تمام |
| 4xx | خطای کلاینت — تلاش مجدد نمیشه |
| 5xx | خطای سرور — تلاش مجدد میشه |
| Timeout (بیشتر از ۱۰ ثانیه) | تلاش مجدد میشه |
زمان پاسخ
سرور تو باید در مدت ۱۰ ثانیه پاسخ بده. اگر طولانیتر شد، درخواست timeout میشه.
تلاش مجدد خودکار
اگر سرور تو پاسخ نده یا خطای 5xx برگردونه، شاهو بهصورت خودکار تلاش مجدد میکنه:
| تلاش | فاصله از تلاش قبلی |
|---|---|
| ۱ (اصلی) | بلافاصله |
| ۲ | ۳۰ ثانیه |
| ۳ | ۵ دقیقه |
| ۴ | ۳۰ دقیقه |
| ۵ | ۲ ساعت |
| ۶ | ۱۲ ساعت |
| ۷ (آخر) | ۲۴ ساعت |
اگر بعد از ۷ تلاش هم موفق نشد، رویداد بهعنوان failed علامتگذاری میشه و در پنل نمایش داده میشه.
جلوگیری از تکرار
چون ممکنه یک webhook چند بار برسه (بهخاطر retry)، از فیلد
id استفاده کن تا از پردازش تکراری جلوگیری کنی:
processed_events = set()
def handle_webhook(event_id, payload):
if event_id in processed_events:
return # قبلاً پردازش شده
processed_events.add(event_id)
process(payload)
در محیط production، از Redis یا دیتابیس استفاده کن نه از حافظهی محلی.
تست Webhook
در پنل، بخش تنظیمات ← اتصالها ← Webhooks، میتونی یک رویداد تستی بفرستی:
- روی دکمهی ارسال تست کنار Webhook کلیک کن
- نوع رویداد رو انتخاب کن
- شاهو یک payload نمونه به آدرس تو میفرسته
- پاسخ سرورت رو در لاگ میبینی
لاگ تحویلها
در پنل، میتونی لیست تمام تحویلهای Webhook رو ببینی:
- زمان — چه زمانی ارسال شد
- رویداد — نوع رویداد
- وضعیت — موفق، خطا، در انتظار
- کد پاسخ — از سرور تو
- زمان پاسخ — میلیثانیه
- پاسخ — بدنهی پاسخ سرور تو
لاگها به مدت ۳۰ روز نگهداری میشن.
نکات امنیتی
۱. همیشه امضا رو تأیید کن
حتی اگر HTTPS داری، حتماً امضای HMAC رو چک کن. این تضمین میکنه درخواست از طرف شاهو اومده.
۲. Timestamp رو چک کن
از X-Shahoo-Timestamp استفاده کن تا از
replay attack جلوگیری کنی. اگر timestamp بیشتر از
۵ دقیقه قدیمی بود، درخواست رو رد کن.
from datetime import datetime, timezone, timedelta
def is_timestamp_valid(timestamp_str):
event_time = datetime.fromisoformat(
timestamp_str.replace('Z', '+00:00')
)
now = datetime.now(timezone.utc)
diff = abs((now - event_time).total_seconds())
return diff < 300 # 5 دقیقه
۳. Secret رو محرمانه نگه دار
Secret Key رو در متغیر محیطی ذخیره کن، نه در کد. اگر بهاشتباه لو رفت، بلافاصله از پنل عوضش کن.
۴. آدرس Webhook رو عمومی نکن
اگر آدرس webhook تو در جای عمومی منتشر شده، ممکنه درخواستهای جعلی دریافت کنی. یک مسیر یکتا و غیرقابل حدس انتخاب کن:
https://example.com/webhooks/shaoo/random-secret-path
مثالهای کاربردی
ارسال پیام خوشآمد به مشتری جدید
def handle_customer_created(data):
customer_id = data['customer_id']
requests.post(
'https://myshahoo.ir/api/v1/messages/send/',
headers={'Authorization': f'Bearer {API_KEY}'},
json={
'customer_id': customer_id,
'channel': 'whatsapp',
'content': 'سلام! به شاهو خوش آمدید 👋'
}
)
افزودن مشتری به CRM خارجی
def handle_customer_created(data):
requests.post(
'https://my-crm.com/api/customers',
headers={'X-API-Key': CRM_KEY},
json={
'name': f"{data['first_name']} {data['last_name']}",
'phone': data['phone'],
'email': data.get('email'),
'source': 'shahoo'
}
)
اعلان به Slack هنگام پیام فوری
def handle_message_created(data):
conversation = data['conversation']
if 'فوری' in conversation.get('tags', []):
requests.post(
SLACK_WEBHOOK_URL,
json={
'text': f"🚨 پیام فوری از {data['customer']['name']}",
'blocks': [{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': f"*{data['customer']['name']}*\n{data['content']}"
}
}]
}
)
بهترین شیوهها
- همیشه سریع پاسخ بده — زیر ۱۰ ثانیه
- از صف استفاده کن — پردازش سنگین رو جدا کن
- Idempotent باشه — پردازش دوبارهی یک رویداد مشکلی ایجاد نکنه
- امضا رو چک کن — برای امنیت
- Timestamp رو تأیید کن — برای جلوگیری از replay
- لاگ بگیر — برای دیباگ
- Fail gracefully — اگر خطا داشتی، 2xx برگردون تا retry نکنه