Shipnest

חיפוש בתיעוד

חפשו לפי נושא, endpoint, או מונח

API Reference

API ציבורי של Shipnest

REST API לפי scopes, פורמט תשובה אחיד, ו-Outbound Webhooks חתומים ב-HMAC. כל הדוגמאות כאן הן curl — אפשר להשתמש בכל שפה / כלי שמדבר HTTP.

עודכן: 20 ביולי 2026

התחלה

Base URL

ה-Base URL של ה-API שלכם הוא הדומיין של פורטל הלקוחות שלכם (אותו דומיין שעליו רץ ה-Tenant). למשל:

http
https://app.shipnest.example

את ה-Base URL המדויק שלכם תמצאו בכרטיס הגדרות → API ו-Webhooks בפורטל. כל הנתיבים כאן יחסיים ל-Base URL הזה.

זמנים, מטבע, ושיטת הקידוד

תאריכים ב-payloads נכתבים תמיד ב-ISO 8601 עם Z בסוף (UTC), גם אם המערכת מציגה ללקוח בשעון ישראל. סכומי כסף ב-API נשלחים בשקלים (decimal) — המערכת ממירה אגורות (integer) פנימית, כך שלא צריך לדאוג לכך מצדכם.
אבטחה

אימות

כל בקשה ל-/api/v1/* חייבת לכלול שני headers:

http
Authorization: Bearer ship_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Timestamp: 1714838400
  • Authorization — מפתח API בפורמט ship_live_…. מפתחות נוצרים ומבוטלים ב-self-service מתוך הפורטל, באותו דפוס של Stripe / GitHub / Vercel.
  • X-Timestamp — Unix time (שניות או מילישניות) של רגע הבקשה. הסטייה המותרת מול שעון השרת היא ±5 דקות. ההגבלה מצמצמת את חלון התקיפות מסוג Replay.

מחזור חיים של טוקן

  1. יצירה ב-/portal/settings/api — בוחרים שם, רשימת scopes, ותאריך תפוגה אופציונלי (ללא תפוגה / 30 / 90 / 365 ימים).
  2. הטוקן הגולמי מוצג פעם אחת בלבד בדיאלוג אחרי היצירה. שומרים אותו מיד ב-secret manager (1Password, AWS Secrets Manager, Vercel env, וכו'). המערכת שומרת רק SHA-256 hash — לא נוכל לשחזר.
  3. אם הטוקן אבד או דלף — מבטלים אותו בקליק ויוצרים חדש. הביטול תקף מיידית. כל בקשה עם טוקן מבוטל מקבלת 401 Unauthorized.

הטוקן מוצג פעם אחת בלבד

זה לא baggy — זה דרישה של SOC 2 / ISO 27001. דליפת DB לא חושפת מפתחות חיים, ו-session hijack לא מקבל את הטוקן "תמיד גלוי" בממשק. במקום זה — ביטול-ויצירה-מחדש בקליק.

מגבלות חשבון

  • עד 10 מפתחות פעילים בו-זמנית לכל Tenant.
  • מפתחות שפג תוקפם או בוטלו לא נמחקים — הם נשמרים לתיעוד (מי יצר, מתי, מי ביטל), אבל לא נספרים במכסה.
  • פעולות יצירה/ביטול נרשמות ביומני השרת ומופיעות בכרטיס "המפתחות שלי" עם זמני שימוש אחרון.

המשימה הנפוצה ביותר: יש לכם מספר משלוח (ברקוד), ואתם רוצים את הקישור האישי של הנמען למעקב — כדי לשלוח אותו ללקוח שלכם במייל, ב-SMS או בכל דיוור אחר. שלוש שורות:

  1. צרו מפתח API עם ה-scope shipments:read ב-הגדרות → API ו-Webhooks.
  2. שלחו בקשת GET ל-/api/v1/shipments/{barcode} עם מספר המשלוח.
  3. קחו את השדה trackingUrl מהתשובה — זה הקישור המוכן לשליחה.
bash
curl 'https://app.shipnest.example/api/v1/shipments/26157003' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400'

התשובה מכילה את שלושת שדות הקישור (שאר שדות המשלוח הושמטו לקיצור):

json
{
  "success": true,
  "data": {
    "barcode": "26157003",
    "publicId": "C8PKR9GBMZ",
    "trackingUrl":   "https://app.shipnest.example/tracking/C8PKR9GBMZ",
    "validationUrl": "https://app.shipnest.example/validate/C8PKR9GBMZ"
  }
}
השדות
trackingUrlעמוד המעקב האישי של הנמען — מצב המשלוח, כתובת מלאה, מפה וצפי הגעה. זה הקישור לשלוח ללקוח.
validationUrlעמוד שבו הנמען יכול לעדכן פרטי מסירה (קומה, דירה, הערות לשליח).
publicIdמזהה המעקב הגולמי, אם אתם בונים את הקישור בעצמכם. אינו הברקוד.

לשלוף כמה קישורים בבת אחת

לדיוור המוני, GET /api/v1/shipments (ללא ברקוד) מחזיר רשימת משלוחים — כל אחד עם אותם שלושה שדות קישור. אפשר גם לקבל את trackingUrl אוטומטית ב-webhook של shipment.created, ברגע שהמשלוח נכנס למערכת.

אל תרכיבו את הקישור מהברקוד ידנית

מפתה לבנות /tracking/{barcode} לבד — אבל קישור לפי ברקוד מצומצם בכוונה (מצב, עיר וצפי בלבד, בלי שם, כתובת או מפה), כי מספרי המשלוח רצופים וניתנים לניחוש. השתמשו תמיד ב-trackingUrlשה-API מחזיר — הוא הקישור המלא לפי מזהה שאינו ניתן לניחוש.
הרשאות

Scopes

לכל מפתח מצורפת רשימה סגורה של scopes — מינימום ההרשאות שהוא צריך. בקשה ל-endpoint שדורש scope שאינו במפתח נדחית ב-403 Forbidden.

Scopes זמינים
shipments:readקריאת משלוחים, פרטי משלוח בודד, היסטוריית visits, והורדת מדבקת PDF.
shipments:writeיצירה ועדכון של משלוחים, וביטול דרך POST /shipments/[barcode]/cancel.
customers:readקריאת רשימת לקוחות ופרטי לקוח בודד.
customers:writeיצירה ועדכון של לקוחות.
webhooks:manageניהול subscriptions של webhooks — יצירה, עדכון, מחיקה, ושליחת test event.

עקרון הרשאה מינימלית

תנו לכל מפתח רק את ה-scopes שהוא באמת צריך. למשל — סקריפט שמייצא משלוחים לדוח חודשי צריך רק shipments:read. כך, אם המפתח דלף, ההיקף הנזק מוגבל.

מפתח שהונפק מפורטל הלקוח — מוגבל ללקוח שלכם

מפתח שנוצר ב-/portal/settings/api מצומצם אוטומטית ללקוח שהנפיק אותו (ולסאב-לקוחות המקושרים שלו בלבד). כל ה-endpoints מחזירים ומעדכנים אך ורק את הנתונים של אותה קבוצת לקוחות — GET /shipments ו-GET /customers מסננים אליה, יצירת לקוח דרך POST /customers נוצרת כסאב-לקוח, ומנויי webhook מקבלים רק events של אותם לקוחות. מפתחות ברמת ה-Tenant (שמונפקים על ידי הצוות) ממשיכים לראות את כל נתוני ה-Tenant. אם המשתמש שיצר את המפתח מוגבל בעצמו לחלק מהקבוצה המקושרת — המפתח יורש את אותה הגבלה ולעולם לא רחב ממנה.
פורמט תשובה

Response Envelope

כל תשובה — הצלחה או כישלון — עוטפת את התוכן באובייקט אחיד:

json
// הצלחה
{
  "success": true,
  "data": { ... },
  "meta": { "count": 25, "nextCursor": 18452, "hasMore": true }
}

// שגיאה
{
  "success": false,
  "error": "Missing X-Timestamp header. Send the current Unix time (seconds or ms since epoch) — used to bound replay attacks.",
  "code": "MISSING_TIMESTAMP"
}
  • data מכיל את התוכן — אובייקט בודד, או מערך בעמודי list. בקריאה ל-endpoint יחיד, יהיה אובייקט; בקריאה ל-list, יהיה מערך.
  • meta מופיע בקריאות list עם count (מספר הפריטים בעמוד הנוכחי), nextCursor (cursor לעמוד הבא — id של הפריט האחרון בעמוד: מספר במשלוחים, מחרוזת בלקוחות; null בעמוד האחרון) ו-hasMore. endpoints מסוימים מחזירים שדות meta ייעודיים — מתועד לצד כל endpoint (למשל meta.alreadyCancelled בביטול משלוח; GET /webhooks מחזיר count בלבד, ללא פגינציה).
  • error בתגובות שגיאה תמיד מחרוזת אחת מובנת. חלק מהשגיאות כוללות גם שדה code מכונה-קריא (כרגע: MISSING_TIMESTAMP / INVALID_TIMESTAMP / TIMESTAMP_OUT_OF_WINDOW, כולן 401) — הסתעפו לפי code ולא לפי טקסט ה-error, וקחו בחשבון שקודים חדשים יתווספו בעתיד.
  • כל גוף בקשה (POST / PATCH) עובר ולידציה קפדנית: שדות שאינם מתועדים נדחים עם 400 ("Unrecognized key") — אל תעבירו שדות נוספים מהמערכת שלכם.

Rate limiting

כברירת מחדל, כל endpoint מוגבל ל-60 בקשות לדקה לכל מפתח, לכל נתיב. חריג: POST /webhooks/{id}/test מוגבל ל-10 בקשות לדקה (כל קריאה פוגעת סינכרונית ב-URL חיצוני). כשהמכסה מוצתה — 429 Too Many Requests עם headers שמסבירים מתי לנסות שוב:

http
HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714838460
  • Retry-After — מספר שניות עד שהחלון מתאפס. כבדו אותו, אל תעשו spam.
  • צריכים קצב גבוה יותר? פנו אלינו — נוכל להגדיל מכסות בהתאמה (העדכון מיושם בפריסה מתוזמנת, לא מיידית).

קודי HTTP

קודי תשובה נפוצים
200 / 201הצלחה. 201 נשלח אחרי POST שיצר משאב חדש.
400קלט לא תקין — ולידציה נכשלה. השדה הספציפי בדרך כלל מצוין ב-error.
401חסר / לא תקף Bearer token, או X-Timestamp מחוץ לחלון הסטייה.
403המפתח קיים ותקף, אבל לא כולל את ה-scope הנדרש ל-endpoint.
404המשאב לא נמצא ב-Tenant הזה. שימו לב — לא חושפים אם המשאב קיים ב-Tenant אחר.
409קונפליקט — למשל ניסיון ליצור משלוח עם barcode שכבר קיים במערכת (בכל ה-Tenants — ראו הערה ב-POST /shipments).
429חריגה ממכסת הבקשות. ראו Retry-After.
500תקלת שרת אצלנו. אם זה חוזר — צרו קשר וצרפו את שעת הבקשה, ה-endpoint, ותחילית המפתח (ship_live_XXXX).

כל ה-endpoints נמצאים תחת /api/v1/. הכותרת של כל endpoint מציינת את ה-scope הנדרש.

משלוחים (Shipments)

GET/api/v1/shipmentsshipments:read

רשימת משלוחים, ממויינת מהחדש לישן. פגינציה דרך cursor.

Query parameters
limit
number
מספר תוצאות. ברירת מחדל 50, מקסימום 100.
cursor
string
id של המשלוח האחרון בעמוד הקודם (מתוך meta.nextCursor).
status
string
סינון לפי sendStatus. ערכים נפוצים: PENDING / SUCCESS / FAILURE; בתרחישים מסוימים יופיע גם DELIVERED. הערך מועבר כמו-שהוא — ערך לא מוכר מחזיר רשימה ריקה (לא שגיאה).
createdFrom
ISO date
סינון תחתון לפי createdAt. תאריך לא תקין מחזיר 400.
createdTo
ISO date
סינון עליון לפי createdAt. תאריך לא תקין מחזיר 400.
externalOrderId
string
התאמה מדויקת לפי מזהה ההזמנה במערכת המקור (למשל מספר הזמנת WooCommerce) — לאיתור המשלוח שנוצר עבור הזמנה נתונה.
bash
curl 'https://app.shipnest.example/api/v1/shipments?limit=20&status=SUCCESS' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400'

כל משלוח בתשובה כולל את הקישורים הציבוריים שלו, מוכנים לשימוש — כדי שתוכלו לשלב אותם בדיוור שלכם (מייל אישור הזמנה, SMS) בלי לבנות URL בעצמכם.

שדות קישור
publicId
string
מזהה המעקב האישי של הנמען. מפתח אקראי — לא הברקוד.
trackingUrl
string
עמוד המעקב האישי. מציג לנמען את הכתובת המלאה, המפה וצפי ההגעה.
validationUrl
string
עמוד עדכון פרטי המסירה (קומה, דירה, הערות לשליח).
json
{
  "barcode": "ABC-1001",
  "publicId": "C8PKR9GBMZ",
  "trackingUrl":   "https://app.shipnest.example/tracking/C8PKR9GBMZ",
  "validationUrl": "https://app.shipnest.example/validate/C8PKR9GBMZ",
  "customerName": "ישראל ישראלי",
  "…": "…"
}

אל תבנו את הקישור מהברקוד

קיים גם /tracking/{barcode} — קישור גנרי שאפשר להרכיב לבד ממספר ההזמנה, אבל הוא מציג מצב, עיר יעד וצפי בלבד: בלי שם, כתובת, מפה או אפשרות לעדכן פרטים. הסיבה — ברקוד המשלוח הוא מספר רץ, וכל אחד יכול לנחש ברקודים סמוכים. השתמשו ב-trackingUrlשמוחזר כאן כדי לתת לנמען את התצוגה המלאה.
GET/api/v1/shipments/{barcode}shipments:read

פרטי משלוח יחיד לפי ברקוד, כולל עד 20 ה-visits האחרונים שלו.

bash
curl 'https://app.shipnest.example/api/v1/shipments/ABC-1001' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400'
GET/api/v1/shipments/{barcode}/labelshipments:read

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

  • תגובת הצלחה היא קובץ בינארי (Content-Type: application/pdf) — לא מעטפת JSON. שגיאות (404 וכו') כן חוזרות במעטפת ה-JSON הרגילה.
  • משלוח עם כמה חבילות מחזיר עמוד מדבקה לכל חבילה (לפי הגדרת ה-Tenant).
  • כל הורדה נרשמת ביומן הפעולות של המשלוח כהדפסת מדבקה דרך API.
  • מוגבל ל-30 בקשות לדקה (רינדור PDF כבד מ-endpoints רגילים).
bash
curl 'https://app.shipnest.example/api/v1/shipments/ABC-1001/label' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400' \
  -o label-ABC-1001.pdf
POST/api/v1/shipmentsshipments:write

יצירת משלוח חדש. כאשר המשלוח משויך ללקוח מקושר (customerId — נקבע אוטומטית במפתח מוגבל-לקוח), המשלוח משוגר אוטומטית למערך ההפצה כמו משלוח שנוצר מהממשק: מערך ההפצה מנפיק את ברקוד המעקב האמיתי, והברקוד ששלחתם נשמר כ-externalOrderId לחיפוש עתידי. אם ה-Tenant אינו מסונכרן למערך הפצה או שהלקוח אינו מקושר — המשלוח נוצר במערכת המקומית בלבד עם הברקוד ששלחתם.

שדות חובה
barcode
string
חובה
המזהה שלכם למשלוח (למשל מספר הזמנה), ייחודי בכל מערכת Shipnest — לא רק בתוך ה-Tenant שלכם. מומלץ prefix ייחודי לעסק (למשל ABC-). שימו לב: במשלוח שמשוגר למערך ההפצה, שדה barcode בתגובה יכיל את ברקוד המעקב שהנפיק מערך ההפצה (שונה מהערך ששלחתם), והערך ששלחתם יישמר ב-externalOrderId.
phone
string
חובה
מספר טלפון של הנמען (פורמט בינלאומי או ישראלי).
customerName
string
חובה
שם הנמען.
sourceName
string
חובה
שם השולח (העסק שלכם).
destinationCity
string
חובה
עיר היעד.
destinationStreet
string
חובה
רחוב היעד.
destinationNumber
string
חובה
מספר בית.
שדות אופציונליים
destinationFloor / destinationApartment / destinationNotes / destinationEmailפרטי מסירה נוספים. destinationNotes נראית לשליח.
sourceCity / sourceStreet / sourceNumber / sourcePhoneכתובת איסוף — אם משלוח לאיסוף ולא ממחסן ברירת המחדל.
message
string
ההודעה שתישלח אוטומטית ללקוח (אם הוגדרה תבנית).
packages
number
מספר חבילות. אם לא נשלח — יישמר ויוחזר null, והמערכת מתייחסת לכך כחבילה אחת.
price
number
מחיר המשלוח בשקלים (חיוב הלקוח; נשמר פנימית באגורות ומוחזר בשקלים ב-GET). זה אינו שדה גוביינא — COD לא ניתן להגדרה דרך ה-API הציבורי כרגע.
weight
number
משקל בק"ג.
urgency
string
REGULAR / EXPRESS / URGENT.
leaveNextToDoor
boolean
להשאיר ליד הדלת אם הלקוח לא ענה.
shipmentNote / orgNoteהערות פנימיות (לא נשלחות ללקוח).
customerId / externalOrderIdקישור ללקוח קיים, או ID של ההזמנה במערכת המקור. customerId שלא קיים ב-Tenant מחזיר 400; לקוח מושעה או בארכיון מחזיר 403. במפתח מוגבל-לקוח (פורטל): customerId מחוץ לקבוצה מחזיר 403, ואם הושמט — המשלוח משויך אוטומטית ללקוח של המפתח.
regionCodeקוד אזור (לסיווג ידני / דוחות).

ברירות מחדל בעת יצירה: sendStatus = "PENDING", sourceProvider = "api".
תגובה: 201 עם המשלוח שנוצר (במשלוח משוגר — עם ברקוד המעקב של מערך ההפצה); 409 אם הברקוד כבר קיים במערכת — כברקוד או כ-externalOrderId של משלוח קודם ב-Tenant שלכם, כך שקריאה חוזרת עם אותו מזהה לא תיצור כפילות; 502 אם השיגור למערך ההפצה נכשל (המשלוח לא נוצר — בדקו את הודעת השגיאה ונסו שוב).

bash
curl -X POST 'https://app.shipnest.example/api/v1/shipments' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400' \
  -H 'Content-Type: application/json' \
  -d '{
    "barcode": "ABC-1001",
    "phone": "+972501234567",
    "customerName": "ישראל ישראלי",
    "sourceName": "החנות שלי",
    "destinationCity": "תל אביב",
    "destinationStreet": "אבן גבירול",
    "destinationNumber": "10",
    "packages": 2,
    "price": 35,
    "urgency": "REGULAR"
  }'
PATCH/api/v1/shipments/{barcode}shipments:write

עדכון שדות של משלוח קיים. שדות שלא נשלחו יישארו ללא שינוי. שדות פנימיים (id, tenantId, barcode, נתוני AI, ערכים שמגיעים מ-Lionwheel) אינם ניתנים לעדכון דרך ה-API.

שדות נתמכים
phone / customerName / sourceNameפרטי שולח/נמען.
destinationCity / destinationStreet / destinationNumber / destinationFloor / destinationApartment / destinationNotes / destinationEmailכתובת ופרטי מסירה.
sourceCity / sourceStreet / sourceNumber / sourcePhoneכתובת איסוף.
message / packages / price / weight / urgency / leaveNextToDoorפרטי המשלוח.
shipmentNote / orgNote / customerId / externalOrderIdהערות וקישורים.
sendStatus / errorMessageסטטוס שליחה (יישומים שמנהלים שליחה מבחוץ).

חובה לשלוח לפחות שדה אחד — גוף ריק מחזיר 400. customerId ו-errorMessage מקבלים גם null לניתוק/ניקוי (שימו לב: במפתח מוגבל-לקוח, customerId: null יסתיר את המשלוח מהמפתח מכאן ואילך).
התגובה מחזירה תקציר בלבד (id, barcode, sendStatus, errorMessage, createdAt, customerName, phone, destinationCity, destinationStreet, price) — לרשומה המלאה קראו ל-GET /api/v1/shipments/{barcode}.

bash
curl -X PATCH 'https://app.shipnest.example/api/v1/shipments/ABC-1001' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400' \
  -H 'Content-Type: application/json' \
  -d '{ "destinationNotes": "פעמון 3", "urgency": "EXPRESS" }'
POST/api/v1/shipments/{barcode}/cancelshipments:write

ביטול משלוח ב-Shipnest בלבד (לא מועבר לספק ההפצה). הביטול מסמן canceledAt על המשלוח (מוחזר בתגובה; sendStatus לא משתנה) ופולט shipment.status_changed עם newStatus: "canceled" (באותיות קטנות) ו-canceledAt.

הפעולה idempotent: ביטול של משלוח שכבר בוטל מחזיר 200 עם המצב הנוכחי, בלי לפלוט את האירוע שוב ובלי לשנות את canceledAt. התגובה כוללת meta.alreadyCancelled (false בביטול ראשון, true בחוזר).

Body (אופציונלי)
reason
string
סיבת הביטול — נשמרת ביומן הפעולות, נשלחת ל-webhook, ומצורפת ל-orgNote של המשלוח עם prefix [cancelled via API] (נראית בממשק). מתעלמים ממנה אם המשלוח כבר מבוטל.
bash
curl -X POST 'https://app.shipnest.example/api/v1/shipments/ABC-1001/cancel' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400' \
  -H 'Content-Type: application/json' \
  -d '{ "reason": "הלקוח ביטל את ההזמנה" }'

לקוחות (Customers)

GET/api/v1/customerscustomers:read

רשימת לקוחות, ממויינת לפי id בסדר עולה (סדר יציב לפגינציה — שונה מהמשלוחים שממויינים מהחדש לישן). פגינציה לפי cursor. כל אובייקט לקוח (כאן וב-GET/POST/PATCH הבודדים) מחזיר: id, name, email, phone, address, isActive, createdAt, updatedAt, pickingType, pickingCategory, agentId.

Query parameters
limit
number
ברירת מחדל 100, מקסימום 200.
cursor
string
id של הלקוח האחרון בעמוד הקודם.
isActive
boolean
סינון לפי סטטוס פעיל.
search
string
חיפוש חופשי לפי שם, טלפון או דוא"ל.
GET/api/v1/customers/{id}customers:read

פרטי לקוח יחיד. id הוא ה-id הפנימי של Shipnest (נוצר ביצירה).

POST/api/v1/customerscustomers:write

יצירת לקוח חדש.

שדות
name
string
חובה
שם הלקוח (חברה / אדם). עד 200 תווים.
email
string
דוא"ל לקשר. פורמט תקין, עד 254 תווים.
phone
string
טלפון לקשר. עד 30 תווים — ספרות / + / - / רווחים / סוגריים בלבד.
address
string
כתובת חופשית. עד 500 תווים.
isActive
boolean
ברירת מחדל true.
bash
curl -X POST 'https://app.shipnest.example/api/v1/customers' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400' \
  -H 'Content-Type: application/json' \
  -d '{ "name": "חברה לדוגמה בעמ", "phone": "03-1234567" }'
PATCH/api/v1/customers/{id}customers:write

עדכון לקוח קיים. שדות שלא נשלחו יישארו ללא שינוי.

שדות
name
string
שם הלקוח — לא ניתן לאיפוס ב-null. עד 200 תווים.
email
string | null
null מנקה את הערך. אותם אילוצים כמו ב-POST.
phone
string | null
null מנקה את הערך (כולל הטלפון המנורמל).
address
string | null
null מנקה את הערך.
isActive
boolean
הפעלה / השבתה.

חובה לשלוח לפחות שדה אחד — גוף ריק מחזיר 400 ("Provide at least one field to update").

ניהול subscriptions של Webhooks

ה-endpoints כאן מנהלים את ה-subscriptions של ה-webhooks — היכן Shipnest שולח אירועים. הם לא ה-webhooks עצמם (אלו נשלחים מ-Shipnest אליכם, ראו סעיף Webhooks).

GET/api/v1/webhookswebhooks:manage

רשימת כל ה-subscriptions של ה-Tenant (ללא פגינציה; meta מכיל count בלבד).

POST/api/v1/webhookswebhooks:manage

יצירת subscription חדש. subscription נוצר תמיד פעיל; השבתה — דרך PATCH.

שדות
name
string
חובה
שם לזיהוי ה-subscription ב-UI. 1–100 תווים.
url
string
חובה
ה-URL שאליו נשלחים ה-payloads. חייב להיות כתובת ציבורית (http או https; HTTPS מומלץ מאוד) — כתובות פנימיות / שמורות (localhost, 10.x, 192.168.x וכו') נדחות עם 400 וגם נחסמות בזמן המשלוח. עד 500 תווים.
events
string[]
חובה
מערך לא-ריק של שמות אירועים מפורשים מתוך קטלוג האירועים. אין wildcard — ערך לא מוכר (כולל "*") נדחה עם 400; כדי להירשם להכל, פרטו את כל האירועים.
bash
curl -X POST 'https://app.shipnest.example/api/v1/webhooks' \
  -H 'Authorization: Bearer ship_live_…' \
  -H 'X-Timestamp: 1714838400' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "ERP sync",
    "url": "https://erp.example.com/hooks/shipnest",
    "events": ["shipment.delivered", "shipment.failed"]
  }'

התגובה כוללת secret שנוצר ביצירה — שמרו אותו, הוא הסוד שמשמש לחתימת ה-HMAC. הוא מוצג פעם אחת בלבד.

GET/api/v1/webhooks/{id}webhooks:manage

פרטי subscription יחיד (ללא ה-secret).

PATCH/api/v1/webhooks/{id}webhooks:manage

עדכון של subscription — name, url, events, או isActive (הפעלה/השבתה). אותם אילוצים כמו ביצירה.

DELETE/api/v1/webhooks/{id}webhooks:manage

מחיקת subscription. הפעולה idempotent — מחיקה של ID שכבר נמחק מחזירה 200.

POST/api/v1/webhooks/{id}/testwebhooks:manage

שליחת אירוע test ל-subscription. שימושי כדי לוודא שה-URL שלכם מקבל בקשות, מאמת חתימות נכון, ומחזיר 2xx בזמן.

  • נשלח עם X-Webhook-Event: ping ו-payload במעטפת הרגילה: { "event": "ping", "tenantId", "occurredAt", "data": { "message" } }. האירוע ping אינו חלק מהקטלוג — קבלו אירועים לא מוכרים עם 2xx במקום 4xx.
  • חתום ב-HMAC עם ה-secret האמיתי של ה-subscription — מתרגל אימות חתימה מלא.
  • סינכרוני, ללא retry, ולא מופיע בלוג המסירות (X-Webhook-Delivery-Id בפורמט test-…).
  • התגובה: { "success": true, "data": { "ok", "statusCode", "message" } }ok משקף האם ה-URL שלכם ענה 2xx.
  • מוגבל ל-10 בקשות לדקה.
אירועים יוצאים

Outbound Webhooks

Shipnest שולחת POST ל-URLs שהגדרתם כשמתרחש אירוע מתאים. ה-payload חתום ב-HMAC-SHA256 כדי שתוכלו לוודא שהבקשה הגיעה מאיתנו.

קטלוג האירועים

אירועים נתמכים
shipment.createdמשלוח חדש נכנס למערכת (UI / API / Lionwheel webhook).
shipment.status_changedסטטוס המשלוח השתנה. נפלט מ: (1) סנכרון סטטוסים מספק ההפצה, (2) PATCH /shipments/[barcode] כששונה sendStatus, (3) ביטול דרך POST /shipments/[barcode]/cancel (עם newStatus: "canceled" באותיות קטנות). שינוי סטטוס ידני בממשק ה-web לא נפלט ישירות — במשלוחים מסונכרנים הוא יגיע דרך הד הסנכרון.
shipment.assignedמשלוח שויך לשליח. ה-payload כולל action: "assigned" (שיוך ראשון) או "transferred" (העברה משליח לשליח). נפלט כרגע משיוך באפליקציית מנהל-ההפצה בלבד — שיוך מממשק ה-web אינו פולט את האירוע.
shipment.deliveredהמשלוח נמסר. נפלט גם מאפליקציית הנהג וגם מחיבור המוביל. שדות oldStatus/newStatus באירוע shipment.status_changed ממשיכים לשאת את ערכי החוט הקיימים (ROUNDTRIP_DELIVERED, FINAL_FAILED) כדי לא לשבור מנויים קיימים; הם יוחלפו בגרסת API הבאה עם הודעה מראש.
shipment.failedשליח סימן את הביקור ככשל. ה-payload כולל reasonCode (no_answer / wrong_address / refused / closed / other) ו-reason טקסטואלי.
visit.completedביקור הושלם (נמסר בהצלחה) — ברמת ה-Visit. נפלט רק ממסירה באפליקציית הנהג; מסירות שמסונכרנות מהספק פולטות shipment.delivered בלבד — אם אתם צריכים כל מסירה, הירשמו ל-shipment.delivered.
cod.collectedגוביינא נגבתה לראשונה (מעבר מ-PENDING לסטטוס שאינו PENDING) — מסימון גבייה בממשק או מגבייה באפליקציית הנהג.
customer.createdלקוח חדש נוצר (דרך API ציבורי או UI ניהול).

Headers בכל delivery

http
Content-Type: application/json
User-Agent: Shipnest-Webhooks/1.0
X-Webhook-Event: shipment.created
X-Webhook-Delivery-Id: a1b2c3d4e5f6...
X-Webhook-Signature: t=1714838400,v1=hex-digest...
X-Webhook-Timestamp: 1714838400

מבנה ה-payload

json
{
  "event": "shipment.created",
  "tenantId": "shipping-001",
  "occurredAt": "2026-05-04T12:00:00.000Z",
  "data": {
    "shipmentId": 42,
    "barcode": "ABC-1001",
    "customerName": "חברה לדוגמה",
    "phone": "+972501234567",
    "destinationCity": "תל אביב",
    "destinationStreet": "אבן גבירול",
    "status": "UNASSIGNED",
    "trackingUrl": "https://app.shipnest.example/tracking/C8PKR9GBMZ"
  }
}

trackingUrl (ב-shipment.created) הוא הקישור האישי של הנמען — אפשר לשלוח אותו ישירות במייל "ההזמנה נשלחה" מהחנות שלכם. ראו קישורי מעקב בתשובה.

אימות חתימה

ה-signature הוא בפורמט t=<unix>,v1=<hex>. לאימות, חשבו מחדש HMAC-SHA256 על המחרוזת `${t}.${rawBody}` והשוו ב-constant-time (כדי למנוע timing attacks).

ts
import crypto from "crypto";

export function verify(rawBody: string, headerSig: string, secret: string): boolean {
  const parts = Object.fromEntries(
    headerSig.split(",").map((p) => p.split("="))
  );
  const t = parts.t;
  const sent = Buffer.from(parts.v1, "hex");
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest();

  if (sent.length !== expected.length) return false;
  return crypto.timingSafeEqual(sent, expected);
}

מאמתים על ה-raw body

חתימת ה-HMAC מחושבת על ה-body כמחרוזת raw, לא על אובייקט JSON מפוענח. אם ה-framework שלכם (Express, Hono, Next.js route handler) מפענח JSON אוטומטית — קראו את ה-body כ-string לפני, או אחרת תקבלו חתימה לא תואמת.

ניסיונות חוזרים

תגובה עם status ב-2xx נחשבת להצלחה. כל תגובה אחרת או timeout (10 שניות) נחשבים לכישלון ומפעילים backoff מעריכי:

  • ניסיון 1 → 1 דקה
  • ניסיון 2 → 5 דקות
  • ניסיון 3 → 30 דקות
  • ניסיון 4 → שעתיים
  • ניסיון 5 → 12 שעות

לאחר 6 כישלונות סך הכל, ה-delivery יסומן exhausted ולא יישלח שוב. לוג המסירות זמין בלוח הבקרה של ה-Tenant.

Idempotency חובה

ייתכן ואותו אירוע יישלח יותר מפעם אחת (retry שהצליח מצד שני אבל לא הצלחנו לרשום זאת). השתמשו ב-X-Webhook-Delivery-Id כ-key למניעת עיבוד כפול. לעולם אל תזהו אירוע ייחודי לפי occurredAt או תוכן ה-payload בלבד.
שיטות עבודה

המלצות

ניהול secrets

  • שמרו את הטוקן (ship_live_…) וה-webhook secret ב-secret manager. לעולם אל תכניסו ב-Git.
  • מפתח לכל סקריפט / שירות — לא לחלוק. אם דליפה תקרה, ביטול נקודתי לא יפיל את כל החיבורים שלכם.
  • סבבו (rotate) טוקנים פעם בשנה לפחות. צרו חדש → בדקו → בטלו את הישן.

Polling או Webhooks?

  • Webhooks — עדיפים. תגובה בזמן אמת, ללא עומס מיותר. השקיעו ב-idempotency וב-queue אצלכם.
  • Polling — רלוונטי רק לסנכרון חוזר של מצב מלא (למשל דוח יומי). אל תעשו polling כדי "לדעת אם משהו השתנה" — זה לא יעיל ולא יציב.

טיפול בשגיאות

  • 5xx ו-429 — תמיד retry עם exponential backoff (בערך כמו ה-backoff של ה-webhooks שלנו).
  • 4xx (חוץ מ-429) — לא לחזור על אותה בקשה. אלו שגיאות לוגיות (ולידציה, scope חסר, משאב לא קיים). תקנו את הקלט.
  • תיעדו כל בקשה שנכשלה ב-X-Webhook-Delivery-Id / response body שלנו — זה מאפשר תמיכה לסייע במהירות.
כלים נלווים

כלים

  • /openapi.yaml — ספק OpenAPI 3.1 סטטי לייבוא ב-Postman / Insomnia / n8n / Make.
  • /portal/settings/api — ניהול מפתחות, רשימת endpoints מותאמת ל-Base URL שלכם, ופרומפט AI מוכן ל-Claude / ChatGPT / Cursor.
  • /docs/changelog — שינויים אחרונים, כולל הרחבות של קטלוג ה-events ושינויי scopes.