API و وبهوک lemonai
سایت، اپ یا CRM خودتان را به نوبت دهی و پروندههای lemonai وصل کنید. وقت آزاد را بخوانید، نوبت بگیرید و لغو کنید، و هر نوبت تازه یا لید ربات دایرکت را همان لحظه در سیستم خودتان بگیرید. اگر CRM دیگری دارید و فقط ربات دایرکت ما را میخواهید، لیدها با وبهوک به همان CRM میرود.
شروع در سه قدم
۱. در پنل کلینیک به تنظیمات ← ربات و اتصالها ← API و وبهوک بروید و «کلید تازه» بسازید. این کار فقط از حساب صاحب کلینیک ممکن است.
۲. کلید فقط همان یک بار نشان داده میشود. آن را در سرور خودتان نگه دارید و با هر درخواست در هدر Authorization بفرستید.
۳. همهٔ نشانیها زیر https://api.lemonai.ir/v1 هستند. پاسخ همیشه JSON است: موفق {"ok":true,"data":…} و ناموفق {"ok":false,"error":"…"} با متن فارسی.
کلید را هرگز داخل کد صفحهٔ سایت (سمت مرورگر) نگذارید. هر کسی که صفحه را باز کند کلید را میبیند. API فقط برای ارتباط سرور به سرور است و هدر CORS نمیدهد. اگر کلید لو رفت، همان لحظه از پنل باطلش کنید.
کلید و دسترسی
هر کلید فقط به دادهٔ همان کلینیکی میرسد که آن را ساخته. ابطال در همان لحظه اثر میکند. سرور خود کلید را نگه نمیدارد، فقط اثر انگشتش را؛ پس کلید گمشده بازیابی نمیشود و باید کلید تازه بسازید.
Authorization: Bearer lai_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
| دسترسی | چه کاری را باز میکند |
|---|---|
read | خواندن مشخصات کلینیک، لاینها، خدمات، پرسنل نوبتپذیر و وقت آزاد |
book | ثبت، لغو و جابهجایی نوبت، و دیدن نوبتها با نام و شمارهٔ مراجع |
patients | جستوجو و ساخت پرونده |
sensitive | کد ملی و یادداشت پرونده؛ فقط همراه patients و فقط اگر صریحا تیک بخورد |
قواعد نوبت: همان قواعد صفحهٔ نوبت دهی کلینیک
نوبتی که از API میآید از همان مسیری رد میشود که نوبت صفحهٔ آنلاین کلینیک. پس API هیچ میانبری ندارد:
- ساعت کار کلینیک و ساعت کار هر لاین، و مهلت پیش از نوبت که کلینیک تعیین کرده
- فقط خدمتهایی که در تنظیمات نوبت دهی برای نوبت آنلاین تیک خوردهاند (فیلد
bookableدر فهرست خدمات) - وقتهایی که منشی بسته (مرخصی، جلسه) و سقف نوبت همزمان هر لاین و هر خدمت
- سقف روزانهٔ نوبت آنلاین برای هر شماره، و پیامک «نوبت ثبت شد» با الگوی خود کلینیک
- نوبت در همان تقویم منشی مینشیند و نشان «آنلاین» با کانال
apiمیگیرد
همهٔ زمانها به وقت تهران هستند. تاریخ را هم میلادی (2026-10-12) و هم شمسی (۱۴۰۵/۰۷/۲۰) میپذیریم، با رقم فارسی یا لاتین. ساعت به شکل 10:30.
وضعیت نوبت یکی از این پنج است: booked ثبتشده، done انجام شد، canceled لغو، moved جابهجاشده، noshow نیامد.
یک شماره، چند نفر: شماره کلید جستوجوست نه هویت (مثلا دو خواهر با یک گوشی). اگر شماره پرونده دارد و نام فرستادهشده با آن نمیخواند، پاسخ 409 با code: "choose_patient" و فهرست پروندهها برمیگردد. درخواست را با patient_id یا "new_person": true دوباره بفرستید.
اندپوینتها
| متد و مسیر | دسترسی | کار |
|---|---|---|
GET /v1/clinic | read | نام، هندل، نشانی صفحهٔ نوبت، امروز به وقت تهران، روشن بودن نوبت دهی |
GET /v1/lines | read | لاینهای کلینیک |
GET /v1/services | read | خدمات با لاین، مدت، و bookable: online یا direct (فقط تلفنی) یا off |
GET /v1/staff?service_id= | read | پرسنل نوبتپذیر، فقط نام و شناسه |
GET /v1/slots?date=&service_id= | read | وقتهای آزاد یک روز برای یک خدمت |
GET /v1/appointments?from=&to=&phone= | book | نوبتهای یک بازه (حداکثر ۶۲ روز)، با فیلتر اختیاری شماره |
GET /v1/appointments/{id} | book | یک نوبت |
POST /v1/appointments | book | ثبت نوبت: service_id، date، time، phone، name، و اختیاری patient_id، new_person، note |
POST /v1/appointments/{id}/move | book | جابهجایی: date و time تازه؛ اگر وقت پر باشد نوبت دست نمیخورد |
POST /v1/appointments/{id}/cancel | book | لغو، فقط نوبت ثبتشده و فقط پیش از ساعت خود نوبت |
GET /v1/patients?phone= یا ?q= | patients | جستوجوی پرونده با شماره، یا با نام و شمارهٔ پرونده |
GET /v1/patients/{id} | patients | یک پرونده |
POST /v1/patients | patients | ساخت پرونده: name، phone، sex، birth_date؛ شمارهٔ تکراری 409 میدهد مگر "confirm_new": true |
نمونه
وقت آزاد یک روز
curl -H "Authorization: Bearer $LEMONAI_KEY" \
"https://api.lemonai.ir/v1/slots?date=2026-10-12&service_id=12"
# {"ok":true,"data":{"date":"2026-10-12","booking_enabled":true,
# "rows":[{"time":"10:00"},{"time":"10:30"},{"time":"11:00"}]}}
ثبت نوبت
curl -X POST https://api.lemonai.ir/v1/appointments \
-H "Authorization: Bearer $LEMONAI_KEY" -H "Content-Type: application/json" \
-d '{"service_id":12,"date":"2026-10-12","time":"10:30","phone":"09121234567","name":"مریم رضایی"}'
# 201 {"ok":true,"data":{"created":true,"appointment":{"id":845,"status":"booked",
# "start_at":"2026-10-12T10:30","date_jalali":"۱۴۰۵/۰۷/۲۰","time":"۱۰:۳۰",
# "service":"لیزر صورت","channel":"api","client":{"patient_id":311,"name":"مریم رضایی",
# "phone":"09121234567"}}}}
# 409 {"ok":false,"error":"این وقت همین الان پر شد؛ وقت دیگری انتخاب کنید"}
خطاها و سقفها
| کد | یعنی |
|---|---|
400 | ورودی ناقص یا نامعتبر (تاریخ، ساعت، شماره) |
401 | کلید نیست، اشتباه است یا باطل شده |
403 | کلید این دسترسی را ندارد، یا ماژول CRM برای کلینیک خاموش است |
404 | چنین نوبت، پرونده یا مسیری در این کلینیک نیست |
409 | وقت پر است، خدمت آنلاین نوبت نمیگیرد، نوبت دیگر قابل لغو نیست، یا choose_patient |
429 | از سقف گذشتید؛ هدر Retry-After میگوید چند ثانیه صبر کنید |
سقف هر کلید: ۱۲۰ درخواست و ۳۰ نوشتن در دقیقه. هر کلینیک روزانه تا ۲۰۰ نوبت از API میگیرد. اگر بیشتر لازم دارید در واتساپ بگویید.
وبهوک: خبر خودکار به سیستم شما
در همان صفحهٔ تنظیمات «نشانی تازه» بزنید، نشانی https:// سرور خودتان را بدهید و رویدادها را انتخاب کنید. یک راز امضا میگیرید که فقط همان بار نشان داده میشود. دکمهٔ «ارسال آزمایشی» یک رویداد ping میفرستد و نتیجهٔ هر ارسال در همان کارت دیده میشود.
| رویداد | کی میرسد |
|---|---|
appointment.created | هر نوبت تازه: از منشی، صفحهٔ آنلاین، ربات یا API |
appointment.canceled | لغو نوبت |
appointment.moved | عوض شدن ساعت نوبت |
appointment.done | منشی نوبت را «انجام شد» زد |
appointment.noshow | منشی نوبت را «نیامد» زد |
lead.created | ربات دایرکت اینستاگرام گفتگو را به آدم سپرد و مراجع شماره داد یا نوبت خواست |
شکل درخواست
POST https://crm.example.com/lemonai
Content-Type: application/json; charset=utf-8
X-Lemonai-Event: appointment.created
X-Lemonai-Event-Id: evt_5355d7892e9979e44193a147
X-Lemonai-Signature: sha256=8c1f…
{"id":"evt_5355d7892e9979e44193a147","event":"appointment.created",
"created_at":"2026-10-07T11:13:43","clinic":"my-clinic",
"data":{"appointment":{"id":845,"status":"booked","start_at":"2026-10-12T10:30",
"date_jalali":"۱۴۰۵/۰۷/۲۰","time":"۱۰:۳۰","duration_min":30,"service":"لیزر صورت",
"line":"لیزر","staff":null,"source":"online","channel":"api"},
"client":{"patient_id":311,"name":"مریم رضایی","phone":"09121234567"}}}
# lead.created:
# "data":{"lead":{"id":17,"channel":"instagram","created_at":"…","service":"بوتاکس",
# "time":null,"summary":"برای بوتاکس وقت میخوام 0912…","reason":"…"},
# "client":{"name":null,"phone":"09121234567","instagram":"username","patient_id":null}}
- ظرف ۱۵ ثانیه با کد
2xxجواب بدهید. هر جواب دیگر یا قطعی یعنی شکست. - شکست دوباره فرستاده میشود: ۱ دقیقه، ۵ دقیقه، ۳۰ دقیقه، ۲ ساعت و ۶ ساعت بعد. بعد از آن «نرسید» ثبت میشود.
- در تلاش دوباره
idرویداد عوض نمیشود. اگر یکidرا دو بار گرفتید، دومی را نادیده بگیرید. - ارسال از سرورهای lemonai بیرون از ایران است. اگر سرور شما آیپی خارجی را نمیپذیرد، ارسال در پنل «نرسید» میخورد؛ نشانی را روی سروری بگذارید که از بیرون در دسترس است.
- فقط نشانی
httpsبا دامنهٔ عمومی پذیرفته میشود، نه آیپی یا نشانی داخلی.
سنجش امضا
امضا HMAC-SHA256 روی بدنهٔ خام درخواست است، با راز همان نشانی. بدنه را پیش از JSON کردن بسنجید و مقایسه را با تابع زمانثابت انجام دهید. درخواست بی امضای درست را رد کنید.
import crypto from 'node:crypto';
// rawBody: Buffer همان بدنهٔ دریافتی، پیش از JSON.parse
function verify(rawBody, header, secret) {
const want = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(want), b = Buffer.from(String(header || ''));
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: app.post('/lemonai', express.raw({ type: 'application/json' }), (req, res) => {
// if (!verify(req.body, req.get('X-Lemonai-Signature'), process.env.LEMONAI_SECRET)) return res.sendStatus(401);
// const event = JSON.parse(req.body); res.sendStatus(200);
// });
<?php
$raw = file_get_contents('php://input');
$want = 'sha256=' . hash_hmac('sha256', $raw, getenv('LEMONAI_SECRET'));
$got = $_SERVER['HTTP_X_LEMONAI_SIGNATURE'] ?? '';
if (!hash_equals($want, $got)) { http_response_code(401); exit; }
$event = json_decode($raw, true);
http_response_code(200);
import hmac, hashlib, os, json
def verify(raw_body: bytes, header: str) -> bool:
want = 'sha256=' + hmac.new(os.environ['LEMONAI_SECRET'].encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, header or '')
# Flask:
# @app.post('/lemonai')
# def hook():
# if not verify(request.get_data(), request.headers.get('X-Lemonai-Signature')): abort(401)
# event = json.loads(request.get_data()); return '', 200
کدام داده بیرون میرود
داده مال کلینیک است و مقصدش را خود کلینیک تعیین میکند. در وبهوک فقط اینها میرود: نام، شماره، خدمت، زمان، کانال، و خلاصهٔ پیام (برای لید).
کد ملی، یادداشت پزشکی، یادداشت منشی روی نوبت و شرح کار هرگز در وبهوک نمیروند. در API هم کد ملی و یادداشت پرونده فقط با دسترسی sensitive که صاحب کلینیک صریحا تیکش زده باشد.
هر استفاده از کلید در «گزارش کارها»ی پنل کلینیک ثبت میشود.
API در همان پلن CRM است
هزینهٔ جدا ندارد و در ماه رایگان هم باز است. برای مشتری فقط ربات دایرکت، وبهوک لید در همان پلن ربات است.