# מפרט אינטגרציה

> תיעוד למפתחים · גרסה 2.0 · אוקטובר 2026 · https://journeez.io/he/docs/integration

עמוד זה מתאר כיצד Journeez מחליפה נתונים עם מערכת הלקוח (CRM,‏ ERP או WMS): איך משימות נמשכות, איך שדות ממופים, איך מטופלות משימות מיובאות, ואילו Webhooks נשלחים כשסידור העבודה משתנה.

- **נקודת קצה אחת** — `GET /tasks` היא נקודת הקצה היחידה שחובה לממש.
- **5 שדות** — שדות החובה לכל משימה, בשמות השדות של מערכת הלקוח.
- **4 אירועים** — נשלחים לכתובת `POST` אחת.
- **HMAC-SHA256** — חתימה אופציונלית, עם חותמת זמן ומפתח idempotency בכל שליחה.

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

האינטגרציה בנויה משלושה חלקים. כולם מוגדרים לכל אינטגרציה בנפרד בממשק הניהול של Journeez.

### משיכה

לפי לוח זמנים מוגדר, Journeez פונה לנקודת הקצה של המשימות במערכת הלקוח ומושכת משימות שהשתנו מאז הריצה הקודמת. מערכת הלקוח יכולה גם להפעיל סנכרון מיידי באמצעות הודעה נכנסת.

### מיפוי

כל פריט ממופה לשדות של Journeez באמצעות ביטויי JSONPath, טרנספורמים וטבלאות תרגום לסטטוסים ולמיומנויות.

### עדכון חוזר

כשיום עבודה של עובד מאושר, משתנה או משוחרר, או כשמשימה מתעדכנת, Journeez שולחת Webhook למערכת הלקוח.

### מחברים

כל אינטגרציה נוצרת מתבנית ב**קטלוג האינטגרציות**: Generic REST,‏ OData או GraphQL, או מחבר ייעודי ל-Priority ERP,‏ monday.com או SAP Business One. לחלופין, **הגדרה עם AI** קוראת תגובה לדוגמה ומציעה מיפוי לאישור.

### באחריות Journeez

- תזמון המשיכה וסמן הסנכרון.
- המרת כתובות המשימות לקואורדינטות (Geocoding).
- תרגום סטטוסים, שמוגדר פעם אחת בטבלה.

## 2. API למשיכת משימות

Journeez יוזמת כל בקשה; מערכת הלקוח רק עונה ל-`GET /tasks`. הנתיב ניתן להגדרה. נקודות הקצה האופציונליות `/employees` ו-`/locations` משמשות להתאמת עובדים ואתרים מוכרים.

| הגדרה | ערכים נתמכים |
|---|---|
| אימות | Bearer token,‏ Basic, כותרת עם מפתח API,‏ OAuth2 client credentials, או בקשות חתומות ב-HMAC (`sha256=<hex>` על `<timestamp>.`). |
| אסטרטגיית עימוד | `offset` / `limit`, מספר עמוד או cursor. גודל עמוד ברירת מחדל 50, מקסימום 200. |
| פרמטר הסינון המצטבר | **חובה.** זמן הסנכרון המוצלח האחרון נשלח בפרמטר שאילתה שניתן להגדרה (ברירת מחדל `updatedAfter`). הפורמט הוא תאריך ISO ב-UTC כברירת מחדל, או Unix בשניות / במילישניות. כל קריאה חופפת לקודמת ב-5 דקות. |
| איך המערכת מחזירה משימות | **רק מה שהשתנה מאז המשיכה האחרונה** (מומלץ): רק משימות שהשתנו. **כל המשימות הפעילות בכל פעם**: תמונת מצב מלאה בכל קריאה; נדרש כדי לסגור משימות שנעלמות מהמקור. |
| תדירות | ביטוי Cron לכל אינטגרציה. בתבניות ברירת המחדל היא כל 15 דקות. |
| ייבוא משימות שנוצרו החל מ | אופציונלי. משימות שנוצרו לפני התאריך הזה לא מיובאות. ברירת המחדל היא היום שבו האינטגרציה חוברה; ניקוי התאריך מייבא את כל ההיסטוריה. נדרש שדה תאריך היצירה של הפריט (JSONPath, למשל `$.createdAt`). אם ה-API תומך בסינון לפי תאריך יצירה (למשל `?createdAfter=`), ציינו גם את שם הפרמטר, ומשימות ישנות לא יתבקשו מהמקור בכלל. |
| Timeout | 30 שניות לכל עמוד. |

### פריט מינימלי תקין

כולל רק את שדות החובה. שמות השדות הם של מערכת הלקוח, והם ממופים בהגדרות.

```json
{
  "items": [
    {
      "id": 12345,
      "type": "AC unit replacement",
      "skill": "MECHANIC",
      "location": { "street": "Rothschild 12", "city": "Tel Aviv" },
      "updatedAt": "2026-06-01T08:14:22Z"
    }
  ]
}
```

### פריט מלא

מוסיף את השדות המומלצים: סטטוס, זמנים מתוכננים, משך, עובד משויך, לקוח וקואורדינטות.

```json
{
  "items": [
    {
      "id": 12345,
      "type": "AC unit replacement",
      "status": "APPROVED",
      "scheduledStartTime": "2026-06-04T09:00:00Z",
      "scheduledEndTime": "2026-06-04T12:00:00Z",
      "duration": 180,
      "assignedToId": 22,
      "skill": "MECHANIC",
      "customer": "Acme Facilities",
      "location": {
        "street": "Rothschild 12",
        "city": "Tel Aviv",
        "lat": 32.0628,
        "lng": 34.7716
      },
      "updatedAt": "2026-06-01T08:14:22Z"
    }
  ],
  "nextCursor": "10481"
}
```

## 3. מיפוי שדות

כל שדה ב-Journeez מוגדר באמצעות JSONPath לתוך פריט המקור, טרנספורם אופציונלי וערך ברירת מחדל אופציונלי. מערך הפריטים מאותר לפי נתיב שורש (ברירת מחדל `$.items[*]`). משימה שחסר בה שדה חובה לא מיובאת.

| שדה ב-Journeez | סוג | רמה | הערות |
|---|---|---|---|
| `originId` | string | חובה | מזהה ייחודי ויציב של המשימה במערכת הלקוח. משמש למניעת כפילויות ומוחזר בכל Webhook. |
| `name` | string | חובה | שם המשימה שמוצג לסדרנים, בדרך כלל סוג העבודה. |
| `roles` | string[] | חובה | המיומנויות הנדרשות, מתורגמות דרך טבלת התפקידים. ערך בודד יכול להיעטף במערך. |
| `startAddress` | object | חובה | המינימום הוא `startAddress.city`. הוסיפו `street`,‏ `houseNumber` ו-`position.lat` / `position.lon` למיקום מדויק. |
| `updatedAt` | ISO-8601 UTC | חובה | זמן העדכון האחרון במערכת הלקוח. משמש את סינון השינויים. |
| `date` / `startHour` | date / timestamp | מומלץ | יום היעד ושעת ההתחלה המתוכננת. אם לא ממופה תאריך, נעשה שימוש ביום הנוכחי. |
| `duration` | number (hours) | מומלץ | נשמר בשעות; לערכים בדקות השתמשו ב-`minutesToHours`. משך חסר או אפס מסומן כבעיית נתונים שחוסמת שיבוץ עד לתיקון. ניתן גם להשלים אותו בחוק גזירה. |
| `endHour` | timestamp | מומלץ | סיום מתוכנן. אם חסר, מחושב כזמן ההתחלה ועוד המשך. |
| `providerOriginId` | string | מומלץ | מזהה העובד המשויך במערכת הלקוח. משמש בייבוא משימות שכבר שובצו. |
| `clientName` | string | אופציונלי | שם הלקוח. מותאם ללקוחות קיימים ללא תלות באותיות גדולות/קטנות; ראו יצירת לקוחות אוטומטית בהמשך. |
| `priority`, `description` | string | אופציונלי | העדיפות היא `Normal`,‏ `High` או `Critical` לאחר התרגום. |

- **טרנספורמים:** ממירים ערך מקור לסוג שהשדה ב-Journeez מצפה לו: תאריכים, שעות, מספרים, מחרוזות ומערכים. טרנספורם אחד לכל כלל, מתוך רשימה קבועה של אחד-עשר.
- **טבלאות תרגום:** ממפות ערכים של הלקוח לערכים של Journeez: סטטוסים (מיפוי ערכי סטטוס) ומיומנויות (מיפוי ערכי תפקיד / מיומנות). ההתאמה מדויקת; שורות סטטוס יכולות לחול בכיוון נכנס, יוצא או בשניהם.
- **חוקי גזירה:** משלימים משך, מיומנויות או מיקום שלא קיימים במערכת המקור (למשל, סוג עבודה X ← שעתיים). החוק הראשון שמתאים מנצח, מלמעלה למטה; כשאף חוק לא מתאים, נעשה שימוש בברירת מחדל או שהשורה מסומנת לטיפול.

### כללי מיפוי

כל שדה ב-Journeez מוגדר בכלל שמורכב מהחלקים שבטבלה. כל החלקים אופציונליים, אבל כלל צריך לכלול לפחות אחד מ-`from`,‏ `template` או `default`.

| חלק | התנהגות | דוגמה |
|---|---|---|
| `from` | JSONPath לתוך פריט המקור. נתיב עם wildcard ‏(`[*]`) מחזיר מערך. | `$.location.street` |
| `template` | תבנית: בונה מחרוזת אחת מכמה נתיבים. כל `{{path}}` מפוענח באמצעות JSONPath, ערך `null` מוצג כריק, והתוצאה מנוקה מרווחים. עוקפת את `from`. | `{{type}} - {{customer}}` |
| `default` | ערך ברירת מחדל: חל כשהערך שחולץ חסר, `null`, מחרוזת ריקה או מערך ריק. בלי `from` או `template` הוא קובע ערך קבוע. | `"Normal"` |
| `transform` | טרנספורם: ממיר את הערך. אחד לכל כלל; ראו את הרשימה בהמשך. | `minutesToHours` |
| `translateWith` | תרגום באמצעות: מחפש את הערך בטבלת תרגום (סטטוסים, תפקידים). מערכים מתורגמים איבר אחר איבר. | `roleTable` |

#### סדר העיבוד

1. חילוץ הערך באמצעות `template`, ואם אין — באמצעות `from`.
2. אם הערך ריק, החלת `default`.
3. החלת `transform`. בנתיב עם wildcard הוא רץ על כל איבר.
4. החלת `translateWith`, שמדולגת אם הטרנספורם נכשל.

אם טרנספורם לא מצליח לפענח את הקלט, השדה נשאר ריק והשגיאה נרשמת בשורה עם שם השדה והסיבה. היא מוצגת בריצת הסנכרון ובתצוגה המקדימה החיה. אם זה שדה חובה, המשימה לא מיובאת.

#### דוגמה

```json
"fields": {
  "originId":  { "from": "$.id", "transform": "toString" },
  "name":      { "template": "{{type}} - {{customer}}" },
  "roles":     { "from": "$.skill", "transform": "wrapArray",
                 "translateWith": "roleTable" },
  "date":      { "from": "$.scheduledStartTime",
                 "transform": "isoDateOrNow" },
  "startHour": { "from": "$.scheduledStartTime",
                 "transform": "extractHHmm" },
  "duration":  { "from": "$.duration",
                 "transform": "minutesToHours" },
  "startAddress.position.lat":
               { "from": "$.location.lat", "transform": "toFloat" },
  "priority":  { "default": "Normal" }
}
```

### טבלאות תרגום

טבלת תרגום ממפה את הערכים שמערכת הלקוח שולחת לערכים של Journeez. שדה משתמש בטבלה דרך `translateWith` (בממשק: „תרגום באמצעות”). בממשק הניהול שתי הטבלאות הן **מיפוי ערכי סטטוס** ו**מיפוי ערכי תפקיד / מיומנות**.

#### איך מתבצע חיפוש בטבלה

ההתאמה מדויקת ותלויה באותיות גדולות/קטנות: `"Approved"` ו-`"APPROVED"` הם מפתחות שונים. כשהערך הוא מערך, כל איבר מחופש בנפרד.

| הערך שהתקבל | התוצאה |
|---|---|
| מופיע בטבלה | הערך הממופה. |
| לא מופיע, ובטבלה יש `_default` | ערך ה-`_default`. |
| לא מופיע, ואין `_default` | `null`. |
| לא מופיע, ובטבלה יש `_missing: "ERROR"` | נרשמת שגיאת מיפוי בשורה עבור השדה. לא נעשה שימוש ב-`_default`. |
| `null` או חסר | ערך ה-`_default` אם הוגדר, אחרת `null`. |

#### מיפוי ערכי סטטוס

- לכל שורה יש כיוון: **→ נכנס** (מהלקוח ל-Journeez), **← יוצא** (מ-Journeez ללקוח) או **↔ דו-כיווני**. שורות נכנסות יוצרות את הטבלה הישירה ושורות יוצאות יוצרות טבלה הפוכה (`statusReverse`). אם כמה שורות ממופות לאותו סטטוס ב-Journeez בכיוון היוצא, נעשה שימוש בשורה הראשונה.
- **כשאין `_default` שמיש, הטבלה קובעת אילו משימות מיובאות.** משימה חדשה שהסטטוס שלה לא מופיע בטבלה לא נוצרת. משימה קיימת שעוברת לסטטוס שלא מופיע מסומנת לסדרן (ראו התנהגות הייבוא).
- **כשיש `_default`,** כל סטטוס שלא מופיע מקבל את הערך הזה, והמשימה מיובאת.
- **כשאין טבלת סטטוסים,** הערך מהמקור מועבר כמו שהוא, ולכן הוא חייב להיות אחד משישת הסטטוסים של Journeez.
- שורות יוצאות משמשות כש-Journeez כותבת סטטוס חזרה דרך מחבר ייעודי או מיפוי יוצא. סטטוס בלי שורה יוצאת לעולם לא מנוחש: השליחה נכשלת באופן גלוי בהיסטוריית השליחה. ה-payload הגולמי של ה-Webhook נושא תמיד את הסטטוס של Journeez ללא תרגום.

#### מיפוי ערכי תפקיד / מיומנות

- בכיוון נכנס בלבד. ממפה את קודי המיומנות של הלקוח לתפקידים ב-Journeez. כשהמקור שולח מיומנות אחת, החילו קודם את `wrapArray`.
- ערך שמתורגם לתפקיד שלא קיים בארגון לא חוסם את הייבוא: המשימה מיובאת, שומרת את התפקידים שכן זוהו, ומסומנת עם השמות שלא נמצאה להם התאמה. אי אפשר לשבץ אותה עד שהתפקיד נוסף או שהשורה מתוקנת.
- משימה שנשארה בלי תפקידים (למשל, כל הערכים לא מופיעים ואין `_default`) מסומנת כחסרת תפקידים.
- ממשק הניהול מציג ערכים שנמצאו בדוגמית ואין להם שורה, ומאפשר להוסיף את כולם בלחיצה.

#### דוגמה

```json
"translations": {
  "statusTable": {
    "APPROVED":    "Open",
    "DISPATCHED":  "Scheduled",
    "IN_PROGRESS": "InProgress",
    "COMPLETED":   "Closed",
    "CANCELLED":   "Closed"
  },
  "statusReverse": {
    "Scheduled": "DISPATCHED",
    "Closed":    "COMPLETED"
  },
  "roleTable": {
    "MECHANIC": "Mechanic",
    "ELEC":     "Electrician",
    "_default": "General technician"
  }
}
```

### רשימת הטרנספורמים

אלה הטרנספורמים היחידים שקיימים. שם לא מוכר הוא שגיאת הגדרה. אלא אם צוין אחרת, קלט `null`, חסר או מחרוזת ריקה מחזיר `null` ללא שגיאה.

| טרנספורם | התנהגות | קלט ← פלט | שימוש נפוץ |
|---|---|---|---|
| `isoDate` | מפענחת תאריך או תאריך ושעה ומחזירה ISO-8601 UTC. נכשלת על קלט שלא ניתן לפענח. | `"2026-06-04 09:00"` → `"2026-06-04T09:00:00.000Z"` | מחרוזות תאריך בפורמט לא סטנדרטי. |
| `unixToIso` | ממירה חותמת זמן Unix (מספר או מחרוזת מספרית) ל-ISO-8601 UTC. ערכים מ-10¹¹ ומעלה נחשבים מילישניות, ערכים קטנים יותר — שניות. | `"1775773020000"` → `"2026-04-09T22:17:00.000Z"` | חותמות זמן Epoch, ש-`isoDate` לא מקבלת כמחרוזת. |
| `isoDateOrNow` | מחזירה את היום הקלנדרי של הקלט באזור הזמן של הארגון, בחצות UTC. קלט ריק או רווחים בלבד הופך להיום. | `null` → היום, למשל `"2026-10-21T00:00:00.000Z"` | `date` כשעבודה לא משובצת צריכה להופיע בלוח של היום. |
| `extractHHmm` | מחזירה את השעה בפורמט `HH:mm` כפי שנכתבה במקור. היסטי אזור זמן לא מומרים ל-UTC. | `"2026-05-31T08:15:00+03:00"` → `"08:15"` | `startHour` / `endHour` משדה תאריך ושעה. |
| `toFloat` | ממירה למספר ושומרת על ספרות אחרי הנקודה. נכשלת על קלט לא מספרי. | `"32.0628"` → `32.0628` | קואורדינטות שנשלחות כמחרוזות. |
| `toInt` | ממירה למספר שלם ומשמיטה את החלק העשרוני. נכשלת על קלט לא מספרי. | `"90.7"` → `90` | כמויות ומזהים מספריים שנשלחים כמחרוזות. |
| `minutesToHours` | מחלקת ב-60. | `90` → `1.5` | `duration` שמגיע בדקות. |
| `toString` | ממירה ערך למחרוזת. | `12345` → `"12345"` | מזהים מספריים, למשל `originId`. |
| `concat` | מחברת מערך עם רווח בודד ומדלגת על איברים ריקים. ערך שאינו מערך מוחזר כמחרוזת; `null` הופך ל-`""`. | `["Herzl", null, "5"]` → `"Herzl 5"` | בניית ערך אחד מנתיב עם wildcard. |
| `splitArrayItem` | מחזירה את האיבר הראשון במערך. ערך שאינו מערך מוחזר כמו שהוא. | `["MECHANIC"]` → `"MECHANIC"` | נתיב עם wildcard שמחזיר ערך יחיד. |
| `wrapArray` | עוטפת ערך בודד במערך. מערכים עוברים ללא שינוי; קלט ריק הופך ל-`[]`. | `"MECHANIC"` → `["MECHANIC"]` | `roles` כשבמקור יש מיומנות אחת. בשילוב עם `translateWith`. |

ערכים שדורשים מבנה שלא מופיע ברשימה, למשל המרת יחידות שאינה מדקות לשעות, צריכים להיות מוכנים במערכת הלקוח לפני שהתגובה נשלחת.

## 4. התנהגות הייבוא

### מצב בדיקה

מופעל כברירת מחדל באינטגרציות חדשות. המשיכות רצות והנתונים ממופים, אבל לא נשמר שום דבר למשימות. השתמשו בבדיקת החיבור ובתצוגה המקדימה החיה, ואז כבו את מצב הבדיקה כדי לעלות לאוויר.

### סינון לפי סטטוס

כשאין `_default` בטבלת הסטטוסים, משימה חדשה נוצרת רק אם הסטטוס שלה מופיע בטבלה. משימה קיימת שעוברת לסטטוס שלא מופיע (למשל, מוקפאת) מסומנת לסדרן. אפשר גם להפעיל **מחיקת משימות עם סטטוס שאינו ממופה**, שמוחקת משימות כאלה כל עוד הן פתוחות ולא משובצות.

### סגירת משימות

**סטטוס סופי (מומלץ):** החזירו את המשימה עם סטטוס הושלמה או בוטלה שממופה ל-`Closed`. **סגור משימות שנעלמות מהמקור:** זמין רק עם „כל המשימות הפעילות בכל פעם”; משימות שכבר לא מוחזרות נסגרות. בשני המקרים היסטוריית השיבוץ נשמרת.

### משימות שכבר שובצו

כשסטטוס של הלקוח ממופה ל-`Scheduled` והפריט כולל עובד, תאריך, שעת התחלה וסיום או משך, המשימה משובצת ליום העבודה של אותו עובד ומוחרגת מהאופטימיזציה האוטומטית. שיבוצים מתנגשים מוחלים ומסומנים לבדיקה. כשאחד הערכים חסר, **שמירת משימות משובצות שלא ניתן לשבץ** שומרת את המשימה ומסמנת אותה; אחרת היא מדולגת.

### עריכות ידניות

שדה שסדרן תיקן ב-Journeez לא נדרס בסנכרונים הבאים, אלא אם הערך במקור השתנה.

### יצירה אוטומטית של לקוחות

כבויה כברירת מחדל. כשהיא מופעלת, `clientName` שלא נמצא לו לקוח תואם יוצר רשומת לקוח חדשה בזמן הייבוא.

## 5. סטטוסים של משימות

ב-Journeez יש שישה סטטוסים. הסטטוסים של הלקוח מתורגמים דרך טבלת הסטטוסים. `Closed` הוא הסטטוס הסופי היחיד, והוא כולל גם השלמה וגם ביטול; מערכת הלקוח נשארת מקור האמת להבחנה ביניהם.

- `Open`: לא שובצה ליום עבודה של עובד.
- `Scheduled`: שובצה ליום עבודה של עובד.
- `OnRoute`: העובד בדרך לאתר.
- `OnSite`: העובד הגיע לאתר.
- `InProgress`: העבודה התחילה.
- `Closed`: הושלמה או בוטלה.

הסטטוסים `OnRoute`,‏ `OnSite` ו-`InProgress` נקבעים באפליקציית המובייל של Journeez.

### פעולות ואירועים

| פעולה | אירוע | סטטוס ב-Journeez | סטטוס אופייני אצל הלקוח |
|---|---|---|---|
| משימה נוצרה במערכת הלקוח | — (נמשכת) | `Open` | ממתינה לתיאום |
| משימה שובצה ביום מאושר | `schedule.upserted` | `Scheduled` | מתואמת; העובד והשעה נשמרים |
| הזזת שעה או החלפת עובד | `schedule.upserted` (היום נשלח מחדש) | `Scheduled` | ללא שינוי; העובד והשעה מתעדכנים |
| משימה הוסרה מיום מאושר | `schedule.deleted` | `Open` | העובד והשעה מתאפסים; חזרה להמתנה |
| משימה נערכה, שינתה סטטוס או נסגרה בשטח | `task.updated` | למשל `Closed` | למשל ממתינה לאישור מנהל |
| משימה נמחקה ב-Journeez | `task.deleted` | — | לפי הגדרת הלקוח |

## 6. Webhooks יוצאים

כל האירועים נשלחים כבקשות `POST` לכתובת אחת. אירועי סידור עבודה נושאים את השיבוץ (עובד, יום, שעות). אירועי משימה נושאים נתוני משימה בלבד, ולעולם לא כוללים מידע על שיבוץ.

| אירוע | שם בממשק | נשלח כאשר |
|---|---|---|
| `schedule.upserted` | לוח זמנים אושר | יום עבודה של עובד אושר, או שיום מאושר (או היום הנוכחי) נשמר. |
| `schedule.deleted` | יום נוקה | אישור של יום בוטל, או שהיום נוקה או נמחק. יש לשחרר את השיבוצים שברשימה. |
| `task.updated` | משימה שונתה | משימה נוצרה, נערכה, שינתה סטטוס או נסגרה. |
| `task.deleted` | משימה נמחקה | משימה נמחקה ב-Journeez. |

### מתי לשלוח לוח זמנים

**רק אחרי אישור** (ברירת מחדל): אירועי לוח זמנים נשלחים לימים מאושרים וליום הנוכחי. טיוטות לימים עתידיים לא נשלחות. **בכל שינוי**: נשלחות גם טיוטות. ההגדרה חלה רק על אירועי לוח זמנים; אירועי משימות נשלחים תמיד.

### דרישות מהשרת המקבל

- להחזיר `2xx` תוך 15 שניות.
- לסנן כפילויות לפי `X-Idempotency-Key`.
- להחיל כל הודעה כמצב הנוכחי, לא כשינוי יחסי.
- באירועי משימה, להתייחס ל-`null` כ„אין נתון” ולא לשנות את השדה.
- להחזיר `200` בלי לכתוב נתונים כשקיימת הכותרת `x-journeez-webhook-test: true`.

### כותרות

| כותרת | ערך | נשלחת כאשר |
|---|---|---|
| `X-Journeez-Signature` | `sha256=<hex>` | הוגדר סוד HMAC |
| `X-Journeez-Timestamp` | Unix בשניות | הוגדר סוד HMAC |
| `X-Idempotency-Key` | `outbox-<n>` | תמיד |
| `Authorization` | `Bearer <token>` | הוגדר Bearer token |
| `X-API-Key` (שם ניתן להגדרה) | API key | הוגדר API key |
| `x-journeez-webhook-test` | `true` | בבדיקות חיבור בלבד |

לפני הפעלת Webhooks יש להגדיר לפחות אחד מהבאים: Bearer token,‏ API key או סוד HMAC.

### מבנה ההודעות

#### `schedule.upserted`

עובד אחד, יום אחד. `confirmedDate` מופיע רק כשהאירוע נוצר מאישור היום.

```json
{
  "version": 1,
  "event": "schedule.upserted",
  "orgId": 63,
  "confirmedDate": "2026-07-23",
  "schedule": {
    "date": "2026-07-23",
    "endDate": "2026-07-23",
    "providerId": 773,
    "providerOriginId": "97",
    "resourceId": null,
    "isPlanned": true,
    "tasks": [
      {
        "id": 31798,
        "originId": "82600020",
        "startHour": "2026-07-23T08:00:00.000",
        "endHour": "2026-07-23T09:00:00.000",
        "providerOriginId": "97"
      }
    ]
  }
}
```

#### `schedule.deleted`

אותו מבנה כמו `schedule.upserted`. שעות ועובד עם ערך `null` מסמנים שיבוץ ששוחרר; `originId` מזהה אותו.

```json
{
  "version": 1,
  "event": "schedule.deleted",
  "orgId": 63,
  "schedule": {
    "date": "2026-07-23",
    "providerId": 773,
    "providerOriginId": "97",
    "resourceId": null,
    "isPlanned": true,
    "tasks": [
      {
        "id": 31798,
        "originId": "82600020",
        "startHour": null,
        "endHour": null,
        "providerOriginId": null
      }
    ]
  }
}
```

#### `task.updated`

`duration` בדקות. `status` הוא הסטטוס של Journeez ללא תרגום. ניתן להגביל את השדות לכל אינטגרציה; `id` ו-`originId` נשלחים תמיד. `task.deleted` משתמש באותה מעטפת.

```json
{
  "version": 2,
  "event": "task.updated",
  "orgId": 63,
  "task": {
    "id": 4711,
    "originId": "12345",
    "status": "Closed",
    "duration": 90,
    "name": "Brake inspection",
    "description": "Rear axle noise reported",
    "priority": "Normal",
    "clientId": 812,
    "startAddress": {
      "displayName": "Herzl 5, Tel Aviv",
      "street": "Herzl",
      "houseNumber": "5",
      "city": "Tel Aviv",
      "position": { "lat": 32.0668, "lon": 34.7647 }
    },
    "endAddress": null
  }
}
```

### אימות החתימה

HMAC-SHA256 של `<timestamp>.<raw body>`, שמחושב על גוף הבקשה הגולמי לפני פענוח ה-JSON.

```js
import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-journeez-timestamp"];
  const received = headers["x-journeez-signature"];

  // Replay protection: reject more than 5 minutes of clock skew.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = "sha256=" + createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(received ?? "");
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}
```

## 7. שגיאות וניסיונות חוזרים

### בקשות משיכה

| תגובה | התנהגות Journeez |
|---|---|
| `200` | העמוד מוחל והסמן מתקדם. |
| `400` / `422` / 4xx אחר | הריצה נכשלת. גוף `{"error": "..."}` מוצג בממשק הניהול. |
| `401` / `403` | הריצה נכשלת ולא מנוסה שוב. אחרי 3 כשלים קבועים ברצף, המשיכה מושהית עד לתיקון ההגדרות. |
| `429` | מנוסה שוב בריצה המתוזמנת הבאה. |
| `5xx`, timeout, תקלת רשת | מנוסה שוב באותה ריצה אחרי 1, 2 ו-4 שניות, ואז בריצה המתוזמנת הבאה. |

### היסטוריית שליחה

| תגובה | התנהגות Journeez |
|---|---|
| `2xx` | מסומן כנמסר. |
| `400` `401` `403` `409` `410` `422` | כשל קבוע. עובר לתור ההודעות שנכשלו (DLQ) בלי ניסיון חוזר; ניתן לשלוח מחדש מממשק הניהול. |
| `429` / `503` | מנוסה שוב, בהתאם ל-`Retry-After` ועד שעה. |
| `404`, ‏`5xx` אחר, timeout | מנוסה שוב אחרי 1 שנ׳, 5 שנ׳, 30 שנ׳, 2 דק׳, 10 דק׳ ושעה (7 ניסיונות בסך הכול), ואז עובר ל-DLQ. |

כשממתינים כמה אירועים לאותו יום או לאותה משימה, נשלח רק האחרון.

## 8. מקרי קצה

### כתובות ללא רחוב

נפוץ באתרי בנייה ובשכונות חדשות. כשמוזן רק יישוב, המשימה ממוקמת במרכז היישוב, הכתובת מסומנת כמשוערת, וסדרן מתבקש לאשר אותה.

### משימות צוות

משימה יכולה לדרוש כמה עובדים בעלי מיומנויות שונות באותו אתר. לכל חברי הצוות אותה שעת התחלה ואותה שעת סיום. משימות צוות שכבר שובצו במקור לא ממוקמות בייבוא; הן מסומנות לסדרן.

### סידור עבודה לרכבים ולציוד

לסידור עבודה של רכב או ציוד יש `providerOriginId: null`, בדיוק כמו לשיבוץ ששוחרר. השתמשו ב-`resourceId` כדי להבחין ביניהם.

## 9. רשימת בדיקות לפני עלייה לאוויר

1. `GET /tasks` מחזירה את חמשת שדות החובה ומכבדת את פרמטר הסינון המצטבר.
2. בדיקת החיבור מצליחה והתצוגה המקדימה החיה תקינה במצב בדיקה.
3. טבלאות מיפוי ערכי הסטטוס והתפקיד מכסות כל ערך שמערכת הלקוח מחזירה.
4. השרת המקבל מחזיר `200` בלי לכתוב נתונים כשקיימת הכותרת `x-journeez-webhook-test: true` (כפתור „שלח אירוע בדיקה”).
5. השרת המקבל מסנן כפילויות לפי `X-Idempotency-Key`, ואם הוגדר סוד HMAC — מאמת את החתימה.
6. השרת המקבל מטפל בכל ארבעת האירועים, כולל `schedule.deleted` עם ערכי `null`.
7. מצב הבדיקה כבוי.

---

https://journeez.io/he/docs/integration · 2026-10-21
