پرش به محتوای اصلی

راهنمای برنامه‌نویسان منشی هوشمند

منشی صوتی هوش مصنوعی را به سامانهٔ فروشگاه، رزرو، CRM یا پشتیبانی خود وصل کنید تا وسط مکالمه استعلام بگیرد و کار ثبت کند.

وب‌هوک HTTPS امضای HMAC-SHA256 JSON هدرهای احراز هویت اختصاصی

۱. منشی هوشمند چطور به سامانهٔ شما وصل می‌شود؟

منشی هوشمند نقطه‌فون تلفن مشتریان را جواب می‌دهد و با صدای طبیعی و هم‌زمان صحبت می‌کند. هرجا برای پاسخ به اطلاعات لحظه‌ای سامانهٔ شما نیاز داشته باشد (وضعیت سفارش، تیکت، رزرو، موجودی، نوبت خالی و…) یا بخواهد کاری ثبت کند (سفارش تلفنی، رزرو، لغو)، یک ابزار را صدا می‌زند: یک درخواست HTTPS امضاشده به نشانی‌ای که شما تعیین کرده‌اید.

  1. در پنل، بخش «منشی هوشمند» ← «اتصال به سامانه‌ها»، ابزار را با نام، کاربرد، ورودی‌ها و نشانی وب‌هوک تعریف می‌کنید.
  2. وسط مکالمه، منشی بر اساس «کاربرد» تصمیم می‌گیرد ابزار را صدا بزند و ورودی‌ها را از مشتری می‌پرسد.
  3. نقطه‌فون درخواست را با کلید امضای منشی امضا کرده و به سرور شما می‌فرستد.
  4. سرور شما در کمتر از چند ثانیه JSON برمی‌گرداند و منشی نتیجه را به زبان ساده برای مشتری می‌گوید.

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

۲. تعریف ابزار

فیلدتوضیح
nameنام فنی یکتا با حروف کوچک انگلیسی، عدد و _ (مثلاً order_status). در هدر X-Noghte-Tool و بدنه فرستاده می‌شود.
عنواننامی که در پنل و گزارش‌ها می‌بینید.
کاربردمهم‌ترین بخش: به زبان ساده بنویسید چه زمانی استفاده شود، چه ورودی‌هایی لازم است و چه برمی‌گرداند. منشی فقط از روی همین متن تصمیم می‌گیرد.
ورودی‌هاهر ورودی نام انگلیسی، نوع (string، number، integer، boolean، date به‌صورت YYYY-MM-DD، array)، توضیح، الزامی بودن و در صورت نیاز فهرست مقادیر مجاز دارد.
روشPOST (پیشنهادی؛ بدنهٔ JSON) یا GET (ورودی‌ها در query string).
هدرهای اختصاصیمثلاً Authorization برای احراز هویت سامانهٔ شما. رمزنگاری‌شده نگه‌داری می‌شوند و در پنل نمایش داده نمی‌شوند.
پیش از اجرا تأیید بگیردبرای ابزارهایی که چیزی ثبت، پرداخت یا لغو می‌کنند: منشی جزئیات را برای مشتری تکرار می‌کند و فقط با «بله»ی صریح ابزار را صدا می‌زند.
جملهٔ انتظارجمله‌ای که منشی هنگام صدا زدن ابزار می‌گوید («یک لحظه بررسی می‌کنم») تا سکوت پیش نیاید.
حداکثر زمان پاسخ۱ تا ۱۵ ثانیه (پیش‌فرض ۶). پس از آن منشی عذرخواهی کرده و تماس برگشتی پیشنهاد می‌دهد.

۳. درخواستی که دریافت می‌کنید

http
POST /api/noghte/order-status HTTP/1.1
Host: shop.example.com
Content-Type: application/json
User-Agent: NoghtePhone-Agent/1.0
X-Noghte-Event: tool.call
X-Noghte-Tool: order_status
X-Noghte-Agent: 9b1f0c7e-1d0b-4a8e-9a43-2a5f0e7c1d11
X-Noghte-Conversation: 5e0b7c55-8f1e-4a5c-b3c6-0e3c9f1f2a77
X-Noghte-Caller: 09121234567
X-Noghte-Signature: t=1792138211,v1=3f6c0a…e91b

{
  "event": "tool.call",
  "tool": "order_status",
  "arguments": { "order_number": "100245" },
  "agent": { "id": "9b1f0c7e-1d0b-4a8e-9a43-2a5f0e7c1d11", "name": "سارا" },
  "conversation": {
    "id": "5e0b7c55-8f1e-4a5c-b3c6-0e3c9f1f2a77",
    "channel": "phone",
    "caller": "09121234567",
    "collected": { "name": "مریم احمدی" }
  },
  "test": false,
  "sent_at": "2026-10-16T09:30:11+00:00"
}
فیلدتوضیح
argumentsورودی‌هایی که منشی از مشتری گرفته؛ مطابق تعریف ابزار.
conversation.idشناسهٔ یکتای مکالمه؛ برای ربط دادن چند درخواست یک تماس و جلوگیری از ثبت تکراری (idempotency) استفاده کنید.
conversation.callerشمارهٔ تماس‌گیرنده (۰۹…) — اغلب برای یافتن مشتری کافی است.
conversation.collectedاطلاعاتی که منشی تا این لحظه از مشتری ثبت کرده (نام، شهر و…).
testدر دکمهٔ «آزمایش» پنل true است؛ در این حالت چیزی را واقعاً ثبت نکنید.

در روش GET بدنه‌ای فرستاده نمی‌شود: ورودی‌ها به query string نشانی شما اضافه می‌شوند (?order_number=100245) و شناسهٔ مکالمه و شمارهٔ تماس‌گیرنده در هدرهای X-Noghte-Conversation و X-Noghte-Caller می‌آید.

۴. بررسی امضا

هر درخواست هدر X-Noghte-Signature: t=…,v1=… دارد. t زمان ارسال (Unix) و v1 برابر HMAC-SHA256 (hex) رشتهٔ {t}.{payload} با کلید امضای منشی (whsec_…) است. payload در POST بدنهٔ خام درخواست و در GET کل query string است، دقیقاً همان‌طور که دریافت شده.

  • کلید را در پنل ← منشی ← «برنامه‌نویسان» ببینید یا عوض کنید.
  • درخواست‌های با اختلاف زمانی بیش از ۵ دقیقه را رد کنید تا جلوی تکرار درخواست گرفته شود.
  • از مقایسهٔ زمان‌ثابت (hash_equals / timingSafeEqual / compare_digest) استفاده کنید.
PHP (Laravel)
<?php
// Laravel: Route::post('/api/noghte/order-status', OrderStatusController::class);
public function __invoke(Request $request)
{
    $secret = config('services.noghte.webhook_secret'); // whsec_…
    $raw = $request->isMethod('GET') ? $request->server('QUERY_STRING') : $request->getContent();

    parse_str(str_replace(',', '&', (string) $request->header('X-Noghte-Signature')), $sig);
    $expected = hash_hmac('sha256', ($sig['t'] ?? '').'.'.$raw, $secret);

    abort_unless(hash_equals($expected, $sig['v1'] ?? '') && abs(time() - (int) ($sig['t'] ?? 0)) < 300, 401);

    $order = Order::where('number', $request->input('arguments.order_number'))->first();
    if (! $order) {
        return ['result' => 'سفارشی با این شماره پیدا نشد.'];
    }

    return ['result' => ['status' => $order->status_label, 'eta' => $order->eta?->format('Y-m-d')]];
}
Node.js (Express)
// Express — the raw body is needed for the signature
import crypto from "node:crypto";
import express from "express";

const app = express();
app.post("/api/noghte/order-status", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-Noghte-Signature") ?? "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", process.env.NOGHTE_WEBHOOK_SECRET).update(`${t}.${req.body}`).digest("hex");

  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  if (!fresh || !v1 || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(401);

  const { arguments: args } = JSON.parse(req.body);
  res.json({ result: { status: "ارسال شده", eta: "فردا" } });
});
Python (FastAPI)
# FastAPI
import hashlib, hmac, os, time
from fastapi import FastAPI, Header, HTTPException, Request

app = FastAPI()
SECRET = os.environ["NOGHTE_WEBHOOK_SECRET"].encode()

@app.post("/api/noghte/order-status")
async def order_status(request: Request, x_noghte_signature: str = Header("")):
    raw = await request.body()
    sig = dict(part.split("=", 1) for part in x_noghte_signature.split(",") if "=" in part)
    expected = hmac.new(SECRET, f"{sig.get('t', '')}.".encode() + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig.get("v1", "")) or abs(time.time() - int(sig.get("t", 0))) > 300:
        raise HTTPException(401)

    args = (await request.json())["arguments"]
    return {"result": {"status": "ارسال شده", "eta": "فردا"}}
Go
func verify(r *http.Request, body []byte, secret string) bool {
	parts := map[string]string{}
	for _, p := range strings.Split(r.Header.Get("X-Noghte-Signature"), ",") {
		if k, v, ok := strings.Cut(p, "="); ok {
			parts[k] = v
		}
	}
	t, _ := strconv.ParseInt(parts["t"], 10, 64)
	if math.Abs(float64(time.Now().Unix()-t)) > 300 {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(parts["t"] + "."))
	mac.Write(body) // for GET: []byte(r.URL.RawQuery)
	return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(parts["v1"]))
}

۵. پاسخ شما

با کد ۲۰۰ و JSON پاسخ دهید. ساده‌ترین شکل فقط کلید result دارد؛ منشی آن را می‌خواند و به زبان خودش برای مشتری بیان می‌کند:

http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "result": {
    "status": "ارسال شده",
    "carrier": "پست پیشتاز",
    "tracking_code": "2401234567",
    "eta": "پنجشنبه"
  }
}

کلیدهای اختیاری برای کنترل بیشتر:

json
{
  "result": { "balance": 0 },
  "say": "حساب شما تسویه است و بدهی ندارید.",
  "end_call": false,
  "transfer_to": "accounting"
}
کلیداثر
resultهر مقدار JSON (متن، شیء یا آرایه). اگر نباشد، کل پاسخ (به‌جز کلیدهای زیر) به منشی داده می‌شود.
sayجمله‌ای که منشی عیناً و بلافاصله به مشتری می‌گوید (مثلاً کد پیگیری).
end_calltrue: منشی پس از جملهٔ آخر تماس را مؤدبانه پایان می‌دهد.
transfer_toکلید یکی از بخش‌های تعریف‌شده در «انتقال» (مثلاً sales)؛ پس از جملهٔ آخر منشی، تماس به آن بخش وصل می‌شود. کلید نامعتبر نادیده گرفته می‌شود.

خطا: اگر کاری ممکن نیست (سفارش پیدا نشد)، بهتر است با ۲۰۰ و توضیح در result پاسخ دهید تا منشی همان را بگوید. پاسخ‌های ۴xx/۵xx با {"error": "…"} هم پذیرفته می‌شوند؛ منشی عذرخواهی کرده و راه دیگری (تماس برگشتی) پیشنهاد می‌دهد.

مکالمه زنده است: هرچه سریع‌تر پاسخ دهید، مکالمه طبیعی‌تر است. پاسخ زیر ۱ ثانیه عالی و تا ۳ ثانیه قابل قبول است. نتیجه را کوتاه نگه دارید (ترجیحاً زیر ۲ کیلوبایت) و فقط آنچه مشتری لازم دارد برگردانید.

۶. رویداد پایان مکالمه

اگر در «برنامه‌نویسان» نشانی دریافت رویداد تعیین کنید، پس از هر تماس یک POST امضاشده (با همان روش امضا) با متن کامل مکالمه، اطلاعات ثبت‌شده و نتیجه دریافت می‌کنید. همین اطلاعات با رویداد ai_agent.conversation_ended به وب‌هوک‌های عمومی بخش «اتصال و API» هم فرستاده می‌شود.

http
POST /api/noghte/events
X-Noghte-Event: conversation.ended
X-Noghte-Signature: t=1792138460,v1=…

{
  "event": "conversation.ended",
  "agent": { "id": "9b1f0c7e-…", "name": "سارا" },
  "conversation": {
    "id": "5e0b7c55-…",
    "caller": "09121234567",
    "started_at": "2026-10-16T09:29:40+00:00",
    "ended_at": "2026-10-16T09:31:12+00:00",
    "seconds": 92,
    "outcome": "transfer",
    "transfer": { "department": "sales", "label": "فروش", "reason": "خرید عمده" },
    "collected": { "name": "مریم احمدی", "city": "شیراز" },
    "transcript": [
      { "role": "agent", "text": "سلام، فروشگاه نمونه، سارا هستم. بفرمایید." },
      { "role": "caller", "text": "سلام، برای خرید عمده تماس گرفتم." }
    ],
    "tool_calls": [{ "name": "save_caller_info", "arguments": { "name": "مریم احمدی" }, "ok": true }]
  }
}
outcomeمعنی
completedمکالمه عادی تمام شد.
transferتماس به یکی از بخش‌ها منتقل شد (جزئیات در transfer).
caller_hangupتماس‌گیرنده قطع کرد.
no_responseتماس‌گیرنده صحبتی نکرد.
time_limitبه سقف مدت مکالمه رسید.
errorخطای فنی.

۷. توانایی‌های داخلی (بدون نیاز به وب‌سرویس)

  • شناسایی تماس‌گیرنده در CRM نقطه‌فون و سلام با نام او
  • ثبت نام و اطلاعات مشتری در CRM (همان «اطلاعاتی که از تماس‌گیرنده گرفته می‌شود»)
  • جستجو در منابع دانش (وب‌سایت، PDF، متن و پرسش‌های متداول شما)
  • اعلام باز یا بسته بودن بر اساس ساعات کاری و تعطیلات رسمی
  • انتقال تماس به داخلی، گروه زنگ، صف کال‌سنتر یا شمارهٔ بیرونی
  • ثبت درخواست تماس برگشتی و گرفتن پیام برای همکاران (با اعلان)
  • پایان مؤدبانهٔ تماس

۸. نمونه‌ها بر اساس صنف

پیگیری سفارشفروشگاه اینترنتیorder_status

مشتری وضعیت سفارشش را می‌پرسد. شمارهٔ سفارش یا موبایل سفارش‌دهنده را بگیرید.

arguments
{ "order_number": "100245" }
response
{ "result": { "status": "تحویل پست شده", "tracking_code": "2401234567", "eta": "پنجشنبه" } }

💡 اگر سفارش با شمارهٔ تماس‌گیرنده ثبت شده، از conversation.caller هم برای جستجو استفاده کنید تا لازم نباشد مشتری شماره سفارش را بخواند.

وضعیت تیکت پشتیبانیپشتیبانی نرم‌افزار و اینترنتticket_status

مشتری دربارهٔ درخواست قبلی‌اش می‌پرسد.

arguments
{ "ticket_id": "T-5521" }
response
{ "result": { "status": "در حال بررسی توسط واحد فنی", "updated": "امروز ساعت ۱۰", "next_step": "تا پایان روز تماس می‌گیریم" } }
بررسی ووچر و رزرو هتلهتل و اقامتگاهcheck_reservation

مهمان با کد ووچر یا نام سرپرست، رزروش را بررسی می‌کند.

arguments
{ "voucher": "HT-88412", "guest_name": "رضایی" }
response
{ "result": { "found": true, "room": "دوتخته رو به دریا", "check_in": "2026-11-02", "nights": 3, "breakfast": true, "paid": true } }
استعلام اتاق خالی و ثبت رزروهتل و اقامتگاهcheck_availability → create_reservation

ابتدا اتاق خالی و قیمت استعلام می‌شود؛ پس از تأیید صریح مشتری، رزرو ثبت می‌شود (گزینهٔ «پیش از اجرا تأیید بگیرد» را روشن کنید).

arguments
{ "check_in": "2026-11-02", "check_out": "2026-11-05", "adults": 2 }
response
{ "result": [{ "room_id": "DBL-SEA", "title": "دوتخته رو به دریا", "price_per_night": 4200000 }, { "room_id": "SUITE", "title": "سوئیت", "price_per_night": 7900000 }] }

💡 برای ثبت رزرو، پاسخ را با کد پیگیری برگردانید؛ منشی آن را برای مشتری تکرار می‌کند. لینک پرداخت را با پیامک از سامانهٔ خودتان بفرستید.

سفارش تلفنی غذارستوران و فست‌فودplace_order

مشتری غذا سفارش می‌دهد؛ منشی اقلام، تعداد، نشانی و روش پرداخت را می‌پرسد و پیش از ثبت، سفارش را بلند تکرار می‌کند.

arguments
{ "items": ["پیتزا مخصوص", "نوشابه"], "address": "شیراز، معالی‌آباد، کوچه ۱۲", "payment": "cash_on_delivery" }
response
{ "result": { "order_id": "R-3391", "total": 680000, "ready_in_minutes": 40 }, "say": "سفارش شما با کد ۳۳۹۱ ثبت شد و حدود چهل دقیقهٔ دیگر می‌رسد." }
جستجو و رزرو پرواز یا تورآژانس مسافرتیsearch_flights → book_flight

مبدأ، مقصد، تاریخ و تعداد مسافر پرسیده و نتایج خوانده می‌شود؛ رزرو قطعی با تأیید مشتری.

arguments
{ "origin": "THR", "destination": "MHD", "date": "2026-11-10", "adults": 2 }
response
{ "result": [{ "flight": "IR 452", "departs": "07:30", "price": 3150000, "seats": 4 }] }

💡 فقط ۳ تا ۵ گزینهٔ مناسب برگردانید؛ خواندن فهرست طولانی پشت تلفن برای مشتری خسته‌کننده است.

نوبت‌دهیکلینیک، سالن زیبایی، تعمیرگاهavailable_slots → book_appointment

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

arguments
{ "service": "جرم‌گیری", "date": "2026-10-20" }
response
{ "result": { "slots": ["10:00", "11:30", "16:00"] } }

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

۹. توصیه‌ها

  • یکتایی: برای ابزارهای ثبت (سفارش، رزرو) با conversation.id و ورودی‌ها از ثبت تکراری جلوگیری کنید؛ ممکن است مشتری درخواست را تکرار کند.
  • حریم خصوصی: فقط اطلاعات لازم را برگردانید. منشی هرگز شمارهٔ کارت، CVV2، رمز یا کد یک‌بارمصرف نمی‌پرسد؛ پرداخت را با لینک پیامکی از سامانهٔ خودتان انجام دهید.
  • متن خوانا: مقادیر را همان‌طور که باید گفته شوند برگردانید (مثلاً «پنجشنبه ۲۴ مهر» به‌جای timestamp). مبالغ را با واحد مشخص (تومان) بفرستید.
  • آزمایش: با دکمهٔ «آزمایش» کنار هر ابزار، درخواست واقعی با test: true بفرستید و پاسخ را ببینید؛ سپس در «گفت‌وگوی آزمایشی» یا با شمارهٔ آزمایشی منشی، مکالمهٔ کامل را امتحان کنید.
  • HTTPS: نشانی وب‌سرویس باید HTTPS با گواهی معتبر باشد.

۱۰. محدودیت‌ها

موردمقدار
ابزار برای هر منشی۳۰
ورودی برای هر ابزار۲۵
هدر اختصاصی برای هر ابزار۱۰
حداکثر زمان پاسخ۱۵ ثانیه
منبع دانش برای هر منشی۵۰ (هر فایل تا ۲۰ مگابایت؛ هر وب‌سایت تا ۶۰ صفحه)