Sahtein · מדריך למפתחים
אינטגרציית קופה
חיבור קופה, גשר מדפסות תרמיות או כלי אוטומציה (Make, Zapier, n8n) למסעדה שעובדת עם Sahtein: אנחנו דוחפים אליכם webhooks חתומים על כל אירוע הזמנה, ואתם דוחפים חזרה סטטוסים, סימוני "אזל מהמלאי" וקריאות תפריט דרך ה-REST API.
1. סקירה כללית
האינטגרציה היא שני ערוצים חד-כיווניים שחולקים אישור אחד. המסעדה מנפיקה מפתח API מתוך הדשבורד ← הגדרות ← אינטגרציות, ורושמת שם גם את כתובת ה-webhook שלכם.
| כיוון | מנגנון | משמש ל... |
|---|---|---|
| Sahtein ← אליכם | Webhooks (בקשות POST חתומות) | הזמנות חדשות, שינויי סטטוס, ביטולים, סימוני מלאי |
| אתם ← Sahtein | REST API | קידום סטטוס הזמנה מהמטבח, סימון פריט שאזל, קריאת התפריט |
תתחילו מהקצוות: קודם רשמו webhook וודאו שאתם מקבלים order.created, ורק אז הוסיפו את ה-PATCH של הסטטוס. כתובת בסיס: https://api.sahtein.app/v1 (התקנות עצמאיות משתמשות במארח שלהן).
2. אימות
כל קריאה ל-API נושאת את המפתח בכותרת Authorization:
Authorization: Bearer shk_<12-char prefix>_<48-char secret>
הטוקן המלא מוצג פעם אחת בלבד, ברגע היצירה — אצלנו נשמר רק hash. למפתח יש הרשאות (scopes) שנבחרות ביצירה (orders:read, orders:write, menu:read, menu:write); קריאה מחוץ להן מחזירה 403. המפתח מיועד לתקשורת שרת-לשרת בלבד — לעולם לא בדפדפן ולא באפליקציית מובייל. אם המפתח דלף — בטלו אותו מהדשבורד; הביטול מיידי.
3. פורמט שגיאות
כל שגיאה חוזרת כ-JSON עם קוד יציב:
{
"error": {
"code": "invalid_transition",
"message": "Invalid transition: ready → accepted",
"requestId": "..."
}
}| HTTP | code | מתי |
|---|---|---|
| 401 | unauthorized | מפתח חסר, לא תקין, מבוטל או שפג תוקפו |
| 403 | forbidden | למפתח אין את ההרשאה הנדרשת |
| 404 | not_found | המשאב לא קיים במסעדה שלכם |
| 409 | invalid_transition | מעבר סטטוס שמכונת המצבים אוסרת |
| 422 | validation_error | גוף הבקשה לא עובר ולידציה |
| 429 | rate_limited | חריגה מהמגבלות — ראו מגבלות קצב |
4. Webhooks (יוצאים)
כשמשהו קורה אנחנו שולחים POST עם JSON לכתובת שלכם. החזירו 2xx תוך 8 שניות; כל תשובה אחרת תנוסה שוב לפי לוח הזמנים שבהמשך. עיבוד כבד שייך לתור אצלכם — קודם אשרו קבלה, אחר כך עבדו.
מעטפת וכותרות
POST <your-url>
Content-Type: application/json
X-Sahtein-Event: order.created
X-Sahtein-Delivery: <uuid — use as your dedup key>
X-Sahtein-Signature: t=<unix seconds>,v1=<hex hmac>
{
"id": "<delivery uuid>",
"event": "order.created",
"created_at": "2026-08-18T10:30:45.123Z",
"tenant_id": "<restaurant uuid>",
"data": { ...event-specific payload... }
}אימות החתימה
החתימה היא HMAC-SHA256 על `${t}.${rawBody}` עם ה-secret שהוצג כשנרשם ה-endpoint. אמתו מול גוף הבקשה הגולמי (raw body), לפני כל JSON.parse:
const crypto = require("crypto");
function verify(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("="))
);
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(parts.v1)
);
}דחו payload שלא עבר אימות עם 401 ואל תעבדו אותו. כל ניסיון חוזר מקבל מזהה delivery חדש, אז בצעו dedup על X-Sahtein-Delivery ולעולם לא תעבדו אירוע פעמיים.
אירועים
| אירוע | נשלח כאשר | שדות עיקריים |
|---|---|---|
order.created | הזמנה נקלטה (מכל ערוץ: אתר, אפליקציה, קיוסק, QR בשולחן) | order_id, number, total, items[], customer |
order.status_changed | כל מעבר סטטוס מלבד ביטול | order_id, from, to, changed_at |
order.cancelled | הזמנה בוטלה על ידי הצוות | order_id, reason |
menu.availability_changed | פריט סומן כאזל / חזר למלאי | product_id, available |
שדות כסף הם מספרים שלמים באגורות (₪108.50 = 10850). שמות פריטים ומוצרים הם אובייקטים מתורגמים ({"ar": "...", "he": "...", "en": "..."}) — בחרו את השפה שהמסך שלכם מדפיס.
לוח ניסיונות חוזרים
| ניסיון | השהיה אחרי כישלון |
|---|---|
| 1 | מיידי |
| 2 | + דקה |
| 3 | + 5 דקות |
| 4 | + 15 דקות |
| 5 | + שעה |
| 6 | + 6 שעות |
אחרי שישה כישלונות ה-delivery מסומן abandoned. שום דבר לא נמחק — המסעדה רואה את ההיסטוריה המלאה בדשבורד ויכולה להריץ ניסיון חוזר ידני. ה-webhooks נשלחים טרנזקציונית יחד עם השינוי שהם מתארים: אם קיבלתם אירוע, GET על אותה הזמנה תמיד יצליח.
5. API הזמנות
GET /partner/v1/orders?status=active&page=1&per_page=30
GET /partner/v1/orders/{id}
PATCH /partner/v1/orders/{id}/status { "status": "in_preparation" }status=active מחזיר את כל מה שבעבודה. ה-PATCH מפעיל את אותה מכונת מצבים כמו הקונסולה ומסך המטבח של המסעדה — עדכון שלכם מזיז את דף המעקב של הלקוח, מדפיס בונים ומעדכן את הקונסולה במכה אחת. קביעת accepted מבצעת קבלה מלאה: בוני מטבח מנותבים ועבודות הדפסה נכנסות לתור, בדיוק כאילו הצוות לחץ "קבל".
| מותר לכם לקבוע | הערות |
|---|---|
accepted | קבלה מלאה — בונים + הדפסות + משימת משלוח |
in_preparation | המטבח התחיל |
ready | כל התחנות סיימו; מנקה את מסך המטבח |
picked_up · out_for_delivery · delivered · completed | מסירה וסיום |
ביטול והחזר כספי הם לצוות בלבד (הם נוגעים בכסף) ומחזירים 409 מה-API הזה. מעברים לא חוקיים — למשל delivered ישירות מ-accepted — מחזירים 409 invalid_transition.
זהירות מלולאה: ה-PATCH שלכם משדר order.status_changed בחזרה אליכם. בצעו dedup על מזהה ה-delivery ואל תגיבו למעבר שאתם בעצמכם יצרתם.
7. מגבלות קצב
600 קריאות לדקה לכל מפתח, ובנוסף מגן פרצים של 40 קריאות לשנייה. שתי המגבלות גבוהות בהרבה ממה שקופה מסונכרנת צריכה — התקרבות לאחת מהן בדרך כלל אומרת שיש לכם לולאה שמגיבה לעצמה. מעבר למגבלה מחזיר 429 עם Retry-After; תשובות מוצלחות נושאות את X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset עבור תקציב הדקה.
8. צ'ק-ליסט אינטגרציה
- קבלו מפתח API מהמסעדה (הגדרות ← אינטגרציות); שמרו אותו ב-vault, לעולם לא בקוד צד-לקוח.
- בדיקת עשן עם
GET /partner/v1/orders?per_page=1. - רשמו את ה-webhook endpoint שלכם; אמתו את ה-HMAC מול הגוף הגולמי; בצעו dedup על
X-Sahtein-Delivery. - אשרו קבלה תוך 8 שניות; עבודה כבדה עוברת לתור אצלכם.
- שלחו PATCH ל-
/orders/{id}/statusבכל שלב במטבח; התעלמו מהדי ה-webhook של המעברים שלכם. - אופציונלי: משכו את
/menuפעם ביום ודחפו סימוני "אזל" כשמלאי נגמר.
שאלות: [email protected] — צרפו את מזהה ה-X-Sahtein-Delivery או ה-order_id הרלוונטי כדי שנוכל לעזור מהר.