Skip to navigation

رویدادهای وب‌هوک

نسخه پیش‌انتشار v1، منتشرشده در 2026-09-14. این صفحه به شکل API متعهد است، نه به تاریخ مشخصی. دقیقاً همان چیزی را می‌سازیم که اینجا مستند شده است. شکل API تا 2026-10-14 هنوز ممکن است تغییر کند؛ پس از آن، تغییرات از نسخه‌بندی و پایداری پیروی می‌کنند.

این بنر با راه‌اندازی هر مسیر، صفحه به صفحه برداشته می‌شود. تا وقتی اینجاست، بر اساس قرارداد پیاده‌سازی کنید و فرض کنید آن مسیر هنوز قابل فراخوانی نیست.

تحویل

POST <your url>
Content-Type: application/json
X-ParsChat-Signature: <hex hmac-sha256 of raw body, keyed with your secret>
X-ParsChat-Event: message
X-ParsChat-Delivery: dlv_9fK2mQ

به محض اینکه رویداد را ذخیره کردید، 2xx برگردانید و پردازش را بعد از آن انجام دهید. پردازشگر کُند به timeout می‌انجامد، و timeout به تلاش دوباره.

تأیید امضا

import hashlib
import hmac
def is_authentic(raw_body: bytes, header_signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_signature)

امضا را روی بایت‌های خام و پیش از تجزیه JSON تأیید کنید. سریال‌سازی دوباره ترتیب کلیدها و فاصله‌ها را تغییر می‌دهد و امضا هرگز مطابقت نخواهد داشت. از compare_digest استفاده کنید، نه ==.

اگر اندپوینت شما احراز هویت خودش را لازم دارد

برخی شرکا نمی‌توانند POST بدون احراز هویت بپذیرند. ممکن است درگاه یا WAF یک بانک، پیش از آنکه درخواست اصلاً به برنامه برسد، توکن bearer یا کلید API بخواهد. امضای ما ثابت می‌کند محتوای رویداد از طرف ما آمده است، اما قاعده زیرساخت شما را برآورده نمی‌کند.

می‌توانید مجموعه‌ای از هدرها به ما بدهید تا آن‌ها را عیناً به هر تحویل اضافه کنیم:

{
"Authorization": "Bearer <a token you issue to us>",
"X-Your-Api-Key": "…",
"X-Client-Id": "…"
}

یک توکن bearer ساده هم کار می‌کند. این هم فقط یک هدر Authorization است، مثل هر هدر دیگری.

چه کسی تنظیم می‌کندخودتان، در پنل پارس‌چت، در تنظیمات پیشرفته اختیاری
نحوه نگهداریرمزگذاری‌شده در حالت ذخیره، و هیچ اندپوینت خواندنی آن را برنمی‌گرداند
تعویضخودتان در پنل تغییرش دهید؛ تحویل‌های در جریان از مقداری استفاده می‌کنند که هنگام ارسال معتبر بوده است

آدرس وب‌هوک و این توکن هر دو در پنل شما هستند. فقط آدرس بازگشت (return URL) به دست ما می‌رسد، چون همین آدرس تعیین می‌کند مرورگر یک مشتری واقعی در میانه ثبت‌نام به کجا فرستاده شود.

چیزی که اصالت را ثابت می‌کند همچنان امضاست. هدرهای شما درخواست را از دروازه خودتان عبور می‌دهند؛ X-ParsChat-Signature به شما می‌گوید بدنه واقعاً از ماست. حتی وقتی احراز هویت خودتان موفق بود، امضا را تأیید کنید. در غیر این صورت، کسی که توکن شما را به دست آورده می‌تواند رویدادهای جعلی برایتان بفرستد.

پشتیبانی نمی‌شود: mTLS. اگر زیرساخت شما گواهی کلاینت را الزامی می‌کند، پیش از یکپارچه‌سازی به ما اطلاع دهید. این کار به جای تغییر تنظیمات، نیازمند پیاده‌سازی در سمت ما است.

یک گیرنده کامل

import hashlib
import hmac
import os
from fastapi import FastAPI, Header, Request, Response
app = FastAPI()
SECRET = os.environ["PARSCHAT_WEBHOOK_SECRET"]
@app.post("/parschat/events")
async def receive(
request: Request,
x_parschat_signature: str = Header(default=""),
x_parschat_event: str = Header(default=""),
x_parschat_delivery: str = Header(default=""),
) -> Response:
raw = await request.body()
expected = hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, x_parschat_signature):
return Response(status_code=401)
# Store first, process later. A slow handler becomes a timeout,
# and a timeout becomes a retry of an event you already have.
enqueue(x_parschat_delivery, x_parschat_event, raw)
return Response(status_code=200)

X-ParsChat-Delivery در تلاش‌های دوباره یک رویداد ثابت می‌ماند. آن را ذخیره کنید و شناسه تحویلی را که قبلاً پردازش کرده‌اید نادیده بگیرید. این کار وقتی پاسخ 2xx شما در مسیر گم شود و ما رویدادی را که پردازش کرده بودید دوباره بفرستیم، رفتار شما را درست نگه می‌دارد.

یک رویداد آزمایشی برای خودتان بفرستید

پیش از آنکه منتظر ترافیک واقعی بمانید، از ما بخواهید هر وقت خواستید رویدادی برایتان بفرستیم:

curl -X POST https://api-chat.parstechai.com/v1/webhooks/test \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx"
{
"delivery_id": "dlv_4mN6pL",
"status": "pending"
}

این یک تحویل واقعی است، نه شبیه‌سازی: با secret شما امضا می‌شود، هدرهای سفارشی شما را دارد، و با همان زمان‌بندی هر رویداد دیگری دوباره تلاش می‌شود. بنابراین تأیید امضا و قواعد فایروال شما را می‌آزماید، نه فقط اینکه نام میزبان شما resolve می‌شود یا نه.

فیلد data آن "test": true دارد تا پردازشگر شما بتواند آن را کنار بگذارد و از روی آن گفتگو نسازد.

if event["data"].get("test"):
return 200

فراخوانی به محض صف‌شدن رویداد 202 برمی‌گرداند؛ این ثابت نمی‌کند اندپوینت شما آن را پذیرفته است. delivery_id را در گزارش تحویل (پایین‌تر) جستجو کنید تا ببینید واقعاً چه پاسخی داده‌اید.

پاسخ 422 یعنی هنوز هیچ آدرس وب‌هوکی برای حساب شما تنظیم نشده است. ابتدا آن را در پنل پارس‌چت تنظیم کنید.

تلاش دوباره

هر پاسخی غیر از 2xx، از جمله timeout، با فاصله‌گذاری فزاینده (backoff) دوباره تلاش می‌شود: ۶ تلاش در حدود یک ساعت.

تلاشپس از
۱فوراً
۲۱۵ ثانیه
۳۱ دقیقه
۴۵ دقیقه
۵۱۵ دقیقه
۶۱ ساعت

پس از آن، تحویل به صف نامه‌های مرده (dead-letter) می‌رود و در پنل دیده می‌شود، تا به جای کشف یک شکاف بی‌صدا، ببینید چه چیزی شکست خورده است.

تلاش دوباره را تنها تور ایمنی خود ندانید. پس از آخرین تلاش، رویداد به‌طور خودکار دوباره فرستاده نمی‌شود. برای پر کردن شکاف‌ها از API مصرف استفاده کنید.

گزارش تحویل

وقتی رویدادی که انتظارش را داشتید هرگز نرسید، این گزارش پاسخ می‌دهد: آیا واقعاً آن را فرستادید؟

curl "https://api-chat.parstechai.com/v1/webhooks/deliveries?status=failed" \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx"
{
"data": [
{
"delivery_id": "dlv_7bX4tK",
"event": "chat",
"status": "failed",
"attempt_count": 3,
"last_status_code": 502,
"last_error": "upstream returned 502",
"next_attempt_at": "2026-09-20T11:24:00Z",
"delivered_at": null,
"created_at": "2026-09-20T11:08:11Z"
}
],
"pagination": { "limit": 50, "offset": 0, "total_count": 1, "has_more": false }
}

delivery_id همان مقداری است که در بدنه رویداد می‌رسد، پس می‌توانید هر ردیف گزارش را با چیزی که دریافت کرده‌اید یا نکرده‌اید تطبیق دهید.

statusمعنا
pendingدر صف است، یا منتظر تلاش دوباره بعدی
deliveredاندپوینت شما 2xx برگرداند
failedیک تلاش شکست خورد. دوباره تلاش می‌شود؛ next_attempt_at را ببینید
dead_letteredهر ۶ تلاش انجام شد. دیگر تلاش نمی‌کنیم

هنگام اشکال‌زدایی، last_status_code و last_error جفت مفیدی هستند: یک 502 که next_attempt_at آن مقدار دارد گذراست و خودبه‌خود درست می‌شود، اما شش 500 که به dead_lettered ختم شده، شکافی است که خودتان باید پر کنید.

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

تنظیمات خود را بررسی کنید

curl https://api-chat.parstechai.com/v1/webhooks \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx"
{
"url": "https://partner.example/parschat/events",
"secret_set": true,
"events": ["message", "chat", "reaction", "is_typing"],
"updated_at": "2026-09-12T08:15:00Z"
}

فقط‌خواندنی است، پس می‌توانید تنظیمات خود را از داخل کد بررسی کنید بی‌آنکه بتوانید تغییرش دهید. secret هرگز برگردانده نمی‌شود، فقط اینکه تنظیم شده یا نه.

پوشش رویداد

همه رویدادها شکل بیرونی یکسانی دارند:

{
"schema_version": "1.0.0",
"event": "message",
"delivery_id": "dlv_9fK2mQ",
"occurred_at": "2026-09-20T11:09:02Z",
"robot_id": "rbt_8fK2mQ",
"external_id": "shop_10422",
"channel_id": "chn_3pQ7xL",
"data": { }
}

schema_version از semver پیروی می‌کند. فیلدها بدون افزایش نسخه major اضافه می‌شوند، پس فیلدهایی را که نمی‌شناسید نادیده بگیرید. هیچ فیلدی بدون نسخه major جدید و اطلاع‌رسانی حذف یا تغییرکاربری نمی‌شود.

انواع رویداد

رویدادچه زمانی ارسال می‌شود
messageپیامی ارسال یا دریافت می‌شود، از جمله پیوست‌ها
message.generatingهوش مصنوعی نوشتن پاسخ را شروع کرده است. نشانگر «در حال نوشتن» را نمایش دهید
chatوضعیت یک گفتگو تغییر می‌کند
reactionواکنشی اضافه یا حذف می‌شود
is_typingیک انسان در طرف مقابل شروع به نوشتن می‌کند یا متوقف می‌شود

message.generating و is_typing دو واقعیت متفاوت‌اند. اولی یعنی هوش مصنوعی در حال نوشتن است، دومی یعنی یک اپراتور انسانی در حال تایپ است. شاید بخواهید آن‌ها را متفاوت نمایش دهید.

محتوای کامل همه رویدادها: مرجع رویدادهای وب‌هوک.

انواع پیام

پیوست‌ها به صورت رویدادهای عادی message با type غیرمتنی می‌رسند، هرگز به صورت رویداد جداگانه:

text · voice · image · video · multimedia · document · poll · form · template

چهار فیلدی که توضیح لازم دارند

اگر پیامی با external_id ای بفرستید که قبلاً استفاده کرده‌اید، پیام تکراری نمی‌سازیم. از آن استفاده کنید تا تلاش دوباره بی‌خطر باشد.

message_id پارس‌چتی پیامی که این پیام به آن پاسخ می‌دهد، تا بتوانید پیام نقل‌قول‌شده را نمایش دهید. وقتی پیام پاسخ نیست، null است.

وقتی محتوا قالب‌بندی دارد مقدار می‌گیرد. text همان نسخه ساده باقی می‌ماند، پس می‌توانید از هر دو استفاده کنید: markdown در نمای گفتگوی غنی، و متن ساده در اعلان یا جایگزین پیامکی. هر کدام ممکن است null باشد، پس هرگز فرض نکنید هر دو وجود دارند.

فهرستی از {type, situation, key, value}، که وقتی type برابر form است وجود دارد.

{
"type": "form",
"text": null,
"form_data": [
{ "type": "text", "situation": "question", "key": "Your name", "value": "Sara" },
{ "type": "single_select", "situation": "rate", "key": "Service rating", "value": "excellent" }
]
}

فرم‌ها فقط مخصوص اینستاگرام نیستند. مشتری‌ای که از طریق ابزارک (ویجت) وصل می‌شود همین شکل را تولید می‌کند.