Sahtein صحتين
Menu

Sahtein · מדריך למפתחים

אינטגרציית קופה

חיבור קופה, גשר מדפסות תרמיות או כלי אוטומציה (Make, Zapier, n8n) למסעדה שעובדת עם Sahtein: אנחנו דוחפים אליכם webhooks חתומים על כל אירוע הזמנה, ואתם דוחפים חזרה סטטוסים, סימוני "אזל מהמלאי" וקריאות תפריט דרך ה-REST API.

1. סקירה כללית

האינטגרציה היא שני ערוצים חד-כיווניים שחולקים אישור אחד. המסעדה מנפיקה מפתח API מתוך הדשבורד ← הגדרות ← אינטגרציות, ורושמת שם גם את כתובת ה-webhook שלכם.

כיווןמנגנוןמשמש ל...
Sahtein ← אליכםWebhooks (בקשות POST חתומות)הזמנות חדשות, שינויי סטטוס, ביטולים, סימוני מלאי
אתם ← SahteinREST 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": "..."
  }
}
HTTPcodeמתי
401unauthorizedמפתח חסר, לא תקין, מבוטל או שפג תוקפו
403forbiddenלמפתח אין את ההרשאה הנדרשת
404not_foundהמשאב לא קיים במסעדה שלכם
409invalid_transitionמעבר סטטוס שמכונת המצבים אוסרת
422validation_errorגוף הבקשה לא עובר ולידציה
429rate_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. צ'ק-ליסט אינטגרציה

  1. קבלו מפתח API מהמסעדה (הגדרות ← אינטגרציות); שמרו אותו ב-vault, לעולם לא בקוד צד-לקוח.
  2. בדיקת עשן עם GET /partner/v1/orders?per_page=1.
  3. רשמו את ה-webhook endpoint שלכם; אמתו את ה-HMAC מול הגוף הגולמי; בצעו dedup על X-Sahtein-Delivery.
  4. אשרו קבלה תוך 8 שניות; עבודה כבדה עוברת לתור אצלכם.
  5. שלחו PATCH ל-/orders/{id}/status בכל שלב במטבח; התעלמו מהדי ה-webhook של המעברים שלכם.
  6. אופציונלי: משכו את /menu פעם ביום ודחפו סימוני "אזל" כשמלאי נגמר.

שאלות: [email protected] — צרפו את מזהה ה-X-Sahtein-Delivery או ה-order_id הרלוונטי כדי שנוכל לעזור מהר.