API ציבורי של Shipnest
REST API לפי scopes, פורמט תשובה אחיד, ו-Outbound Webhooks חתומים ב-HMAC. כל הדוגמאות כאן הן curl — אפשר להשתמש בכל שפה / כלי שמדבר HTTP.
עודכן: 20 ביולי 2026
Base URL
ה-Base URL של ה-API שלכם הוא הדומיין של פורטל הלקוחות שלכם (אותו דומיין שעליו רץ ה-Tenant). למשל:
https://app.shipnest.exampleאת ה-Base URL המדויק שלכם תמצאו בכרטיס הגדרות → API ו-Webhooks בפורטל. כל הנתיבים כאן יחסיים ל-Base URL הזה.
זמנים, מטבע, ושיטת הקידוד
Z בסוף (UTC), גם אם המערכת מציגה ללקוח בשעון ישראל. סכומי כסף ב-API נשלחים בשקלים (decimal) — המערכת ממירה אגורות (integer) פנימית, כך שלא צריך לדאוג לכך מצדכם.אימות
כל בקשה ל-/api/v1/* חייבת לכלול שני headers:
Authorization: Bearer ship_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Timestamp: 1714838400- Authorization — מפתח API בפורמט
ship_live_…. מפתחות נוצרים ומבוטלים ב-self-service מתוך הפורטל, באותו דפוס של Stripe / GitHub / Vercel. - X-Timestamp — Unix time (שניות או מילישניות) של רגע הבקשה. הסטייה המותרת מול שעון השרת היא ±5 דקות. ההגבלה מצמצמת את חלון התקיפות מסוג Replay.
מחזור חיים של טוקן
- יצירה ב-/portal/settings/api — בוחרים שם, רשימת scopes, ותאריך תפוגה אופציונלי (ללא תפוגה / 30 / 90 / 365 ימים).
- הטוקן הגולמי מוצג פעם אחת בלבד בדיאלוג אחרי היצירה. שומרים אותו מיד ב-secret manager (1Password, AWS Secrets Manager, Vercel env, וכו'). המערכת שומרת רק SHA-256 hash — לא נוכל לשחזר.
- אם הטוקן אבד או דלף — מבטלים אותו בקליק ויוצרים חדש. הביטול תקף מיידית. כל בקשה עם טוקן מבוטל מקבלת
401 Unauthorized.
הטוקן מוצג פעם אחת בלבד
מגבלות חשבון
- עד 10 מפתחות פעילים בו-זמנית לכל Tenant.
- מפתחות שפג תוקפם או בוטלו לא נמחקים — הם נשמרים לתיעוד (מי יצר, מתי, מי ביטל), אבל לא נספרים במכסה.
- פעולות יצירה/ביטול נרשמות ביומני השרת ומופיעות בכרטיס "המפתחות שלי" עם זמני שימוש אחרון.
קבלת קישור מעקב לפי מספר משלוח
המשימה הנפוצה ביותר: יש לכם מספר משלוח (ברקוד), ואתם רוצים את הקישור האישי של הנמען למעקב — כדי לשלוח אותו ללקוח שלכם במייל, ב-SMS או בכל דיוור אחר. שלוש שורות:
- צרו מפתח API עם ה-scope
shipments:readב-הגדרות → API ו-Webhooks. - שלחו בקשת
GETל-/api/v1/shipments/{barcode}עם מספר המשלוח. - קחו את השדה
trackingUrlמהתשובה — זה הקישור המוכן לשליחה.
curl 'https://app.shipnest.example/api/v1/shipments/26157003' \
-H 'Authorization: Bearer ship_live_…' \
-H 'X-Timestamp: 1714838400'התשובה מכילה את שלושת שדות הקישור (שאר שדות המשלוח הושמטו לקיצור):
{
"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.
shipments:read | קריאת משלוחים, פרטי משלוח בודד, היסטוריית visits, והורדת מדבקת PDF. |
shipments:write | יצירה ועדכון של משלוחים, וביטול דרך POST /shipments/[barcode]/cancel. |
customers:read | קריאת רשימת לקוחות ופרטי לקוח בודד. |
customers:write | יצירה ועדכון של לקוחות. |
webhooks:manage | ניהול subscriptions של webhooks — יצירה, עדכון, מחיקה, ושליחת test event. |
עקרון הרשאה מינימלית
shipments:read. כך, אם המפתח דלף, ההיקף הנזק מוגבל.מפתח שהונפק מפורטל הלקוח — מוגבל ללקוח שלכם
GET /shipments ו-GET /customers מסננים אליה, יצירת לקוח דרך POST /customers נוצרת כסאב-לקוח, ומנויי webhook מקבלים רק events של אותם לקוחות. מפתחות ברמת ה-Tenant (שמונפקים על ידי הצוות) ממשיכים לראות את כל נתוני ה-Tenant. אם המשתמש שיצר את המפתח מוגבל בעצמו לחלק מהקבוצה המקושרת — המפתח יורש את אותה הגבלה ולעולם לא רחב ממנה.Response Envelope
כל תשובה — הצלחה או כישלון — עוטפת את התוכן באובייקט אחיד:
// הצלחה
{
"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 limits ושגיאות
Rate limiting
כברירת מחדל, כל endpoint מוגבל ל-60 בקשות לדקה לכל מפתח, לכל נתיב. חריג: POST /webhooks/{id}/test מוגבל ל-10 בקשות לדקה (כל קריאה פוגעת סינכרונית ב-URL חיצוני). כשהמכסה מוצתה — 429 Too Many Requests עם headers שמסבירים מתי לנסות שוב:
HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714838460Retry-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
כל ה-endpoints נמצאים תחת /api/v1/. הכותרת של כל endpoint מציינת את ה-scope הנדרש.
משלוחים (Shipments)
/api/v1/shipmentsshipments:readרשימת משלוחים, ממויינת מהחדש לישן. פגינציה דרך cursor.
limitnumber | מספר תוצאות. ברירת מחדל 50, מקסימום 100. |
cursorstring | id של המשלוח האחרון בעמוד הקודם (מתוך meta.nextCursor). |
statusstring | סינון לפי sendStatus. ערכים נפוצים: PENDING / SUCCESS / FAILURE; בתרחישים מסוימים יופיע גם DELIVERED. הערך מועבר כמו-שהוא — ערך לא מוכר מחזיר רשימה ריקה (לא שגיאה). |
createdFromISO date | סינון תחתון לפי createdAt. תאריך לא תקין מחזיר 400. |
createdToISO date | סינון עליון לפי createdAt. תאריך לא תקין מחזיר 400. |
externalOrderIdstring | התאמה מדויקת לפי מזהה ההזמנה במערכת המקור (למשל מספר הזמנת WooCommerce) — לאיתור המשלוח שנוצר עבור הזמנה נתונה. |
curl 'https://app.shipnest.example/api/v1/shipments?limit=20&status=SUCCESS' \
-H 'Authorization: Bearer ship_live_…' \
-H 'X-Timestamp: 1714838400'קישורי מעקב בתשובה
כל משלוח בתשובה כולל את הקישורים הציבוריים שלו, מוכנים לשימוש — כדי שתוכלו לשלב אותם בדיוור שלכם (מייל אישור הזמנה, SMS) בלי לבנות URL בעצמכם.
publicIdstring | מזהה המעקב האישי של הנמען. מפתח אקראי — לא הברקוד. |
trackingUrlstring | עמוד המעקב האישי. מציג לנמען את הכתובת המלאה, המפה וצפי ההגעה. |
validationUrlstring | עמוד עדכון פרטי המסירה (קומה, דירה, הערות לשליח). |
{
"barcode": "ABC-1001",
"publicId": "C8PKR9GBMZ",
"trackingUrl": "https://app.shipnest.example/tracking/C8PKR9GBMZ",
"validationUrl": "https://app.shipnest.example/validate/C8PKR9GBMZ",
"customerName": "ישראל ישראלי",
"…": "…"
}אל תבנו את הקישור מהברקוד
/tracking/{barcode} — קישור גנרי שאפשר להרכיב לבד ממספר ההזמנה, אבל הוא מציג מצב, עיר יעד וצפי בלבד: בלי שם, כתובת, מפה או אפשרות לעדכן פרטים. הסיבה — ברקוד המשלוח הוא מספר רץ, וכל אחד יכול לנחש ברקודים סמוכים. השתמשו ב-trackingUrlשמוחזר כאן כדי לתת לנמען את התצוגה המלאה./api/v1/shipments/{barcode}shipments:readפרטי משלוח יחיד לפי ברקוד, כולל עד 20 ה-visits האחרונים שלו.
curl 'https://app.shipnest.example/api/v1/shipments/ABC-1001' \
-H 'Authorization: Bearer ship_live_…' \
-H 'X-Timestamp: 1714838400'/api/v1/shipments/{barcode}/labelshipments:readהורדת מדבקת המשלוח כ-PDF — אותה מדבקה בדיוק שמודפסת מהממשק ומהפורטל (ברקוד, כתובת, אזור הפצה, גוביינא אם קיימת, לוגו הלקוח). מיועד לאינטגרציות צד-חנות שמדפיסות את המדבקה שלנו ממסך ההזמנה.
- תגובת הצלחה היא קובץ בינארי (
Content-Type: application/pdf) — לא מעטפת JSON. שגיאות (404 וכו') כן חוזרות במעטפת ה-JSON הרגילה. - משלוח עם כמה חבילות מחזיר עמוד מדבקה לכל חבילה (לפי הגדרת ה-Tenant).
- כל הורדה נרשמת ביומן הפעולות של המשלוח כהדפסת מדבקה דרך API.
- מוגבל ל-30 בקשות לדקה (רינדור PDF כבד מ-endpoints רגילים).
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/api/v1/shipmentsshipments:writeיצירת משלוח חדש. כאשר המשלוח משויך ללקוח מקושר (customerId — נקבע אוטומטית במפתח מוגבל-לקוח), המשלוח משוגר אוטומטית למערך ההפצה כמו משלוח שנוצר מהממשק: מערך ההפצה מנפיק את ברקוד המעקב האמיתי, והברקוד ששלחתם נשמר כ-externalOrderId לחיפוש עתידי. אם ה-Tenant אינו מסונכרן למערך הפצה או שהלקוח אינו מקושר — המשלוח נוצר במערכת המקומית בלבד עם הברקוד ששלחתם.
barcodestring חובה | המזהה שלכם למשלוח (למשל מספר הזמנה), ייחודי בכל מערכת Shipnest — לא רק בתוך ה-Tenant שלכם. מומלץ prefix ייחודי לעסק (למשל ABC-). שימו לב: במשלוח שמשוגר למערך ההפצה, שדה barcode בתגובה יכיל את ברקוד המעקב שהנפיק מערך ההפצה (שונה מהערך ששלחתם), והערך ששלחתם יישמר ב-externalOrderId. |
phonestring חובה | מספר טלפון של הנמען (פורמט בינלאומי או ישראלי). |
customerNamestring חובה | שם הנמען. |
sourceNamestring חובה | שם השולח (העסק שלכם). |
destinationCitystring חובה | עיר היעד. |
destinationStreetstring חובה | רחוב היעד. |
destinationNumberstring חובה | מספר בית. |
destinationFloor / destinationApartment / destinationNotes / destinationEmail | פרטי מסירה נוספים. destinationNotes נראית לשליח. |
sourceCity / sourceStreet / sourceNumber / sourcePhone | כתובת איסוף — אם משלוח לאיסוף ולא ממחסן ברירת המחדל. |
messagestring | ההודעה שתישלח אוטומטית ללקוח (אם הוגדרה תבנית). |
packagesnumber | מספר חבילות. אם לא נשלח — יישמר ויוחזר null, והמערכת מתייחסת לכך כחבילה אחת. |
pricenumber | מחיר המשלוח בשקלים (חיוב הלקוח; נשמר פנימית באגורות ומוחזר בשקלים ב-GET). זה אינו שדה גוביינא — COD לא ניתן להגדרה דרך ה-API הציבורי כרגע. |
weightnumber | משקל בק"ג. |
urgencystring | REGULAR / EXPRESS / URGENT. |
leaveNextToDoorboolean | להשאיר ליד הדלת אם הלקוח לא ענה. |
shipmentNote / orgNote | הערות פנימיות (לא נשלחות ללקוח). |
customerId / externalOrderId | קישור ללקוח קיים, או ID של ההזמנה במערכת המקור. customerId שלא קיים ב-Tenant מחזיר 400; לקוח מושעה או בארכיון מחזיר 403. במפתח מוגבל-לקוח (פורטל): customerId מחוץ לקבוצה מחזיר 403, ואם הושמט — המשלוח משויך אוטומטית ללקוח של המפתח. |
regionCode | קוד אזור (לסיווג ידני / דוחות). |
ברירות מחדל בעת יצירה: sendStatus = "PENDING", sourceProvider = "api".
תגובה: 201 עם המשלוח שנוצר (במשלוח משוגר — עם ברקוד המעקב של מערך ההפצה); 409 אם הברקוד כבר קיים במערכת — כברקוד או כ-externalOrderId של משלוח קודם ב-Tenant שלכם, כך שקריאה חוזרת עם אותו מזהה לא תיצור כפילות; 502 אם השיגור למערך ההפצה נכשל (המשלוח לא נוצר — בדקו את הודעת השגיאה ונסו שוב).
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"
}'/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}.
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" }'/api/v1/shipments/{barcode}/cancelshipments:writeביטול משלוח ב-Shipnest בלבד (לא מועבר לספק ההפצה). הביטול מסמן canceledAt על המשלוח (מוחזר בתגובה; sendStatus לא משתנה) ופולט shipment.status_changed עם newStatus: "canceled" (באותיות קטנות) ו-canceledAt.
הפעולה idempotent: ביטול של משלוח שכבר בוטל מחזיר 200 עם המצב הנוכחי, בלי לפלוט את האירוע שוב ובלי לשנות את canceledAt. התגובה כוללת meta.alreadyCancelled (false בביטול ראשון, true בחוזר).
reasonstring | סיבת הביטול — נשמרת ביומן הפעולות, נשלחת ל-webhook, ומצורפת ל-orgNote של המשלוח עם prefix [cancelled via API] (נראית בממשק). מתעלמים ממנה אם המשלוח כבר מבוטל. |
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)
/api/v1/customerscustomers:readרשימת לקוחות, ממויינת לפי id בסדר עולה (סדר יציב לפגינציה — שונה מהמשלוחים שממויינים מהחדש לישן). פגינציה לפי cursor. כל אובייקט לקוח (כאן וב-GET/POST/PATCH הבודדים) מחזיר: id, name, email, phone, address, isActive, createdAt, updatedAt, pickingType, pickingCategory, agentId.
limitnumber | ברירת מחדל 100, מקסימום 200. |
cursorstring | id של הלקוח האחרון בעמוד הקודם. |
isActiveboolean | סינון לפי סטטוס פעיל. |
searchstring | חיפוש חופשי לפי שם, טלפון או דוא"ל. |
/api/v1/customers/{id}customers:readפרטי לקוח יחיד. id הוא ה-id הפנימי של Shipnest (נוצר ביצירה).
/api/v1/customerscustomers:writeיצירת לקוח חדש.
namestring חובה | שם הלקוח (חברה / אדם). עד 200 תווים. |
emailstring | דוא"ל לקשר. פורמט תקין, עד 254 תווים. |
phonestring | טלפון לקשר. עד 30 תווים — ספרות / + / - / רווחים / סוגריים בלבד. |
addressstring | כתובת חופשית. עד 500 תווים. |
isActiveboolean | ברירת מחדל true. |
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" }'/api/v1/customers/{id}customers:writeעדכון לקוח קיים. שדות שלא נשלחו יישארו ללא שינוי.
namestring | שם הלקוח — לא ניתן לאיפוס ב-null. עד 200 תווים. |
emailstring | null | null מנקה את הערך. אותם אילוצים כמו ב-POST. |
phonestring | null | null מנקה את הערך (כולל הטלפון המנורמל). |
addressstring | null | null מנקה את הערך. |
isActiveboolean | הפעלה / השבתה. |
חובה לשלוח לפחות שדה אחד — גוף ריק מחזיר 400 ("Provide at least one field to update").
ניהול subscriptions של Webhooks
ה-endpoints כאן מנהלים את ה-subscriptions של ה-webhooks — היכן Shipnest שולח אירועים. הם לא ה-webhooks עצמם (אלו נשלחים מ-Shipnest אליכם, ראו סעיף Webhooks).
/api/v1/webhookswebhooks:manageרשימת כל ה-subscriptions של ה-Tenant (ללא פגינציה; meta מכיל count בלבד).
/api/v1/webhookswebhooks:manageיצירת subscription חדש. subscription נוצר תמיד פעיל; השבתה — דרך PATCH.
namestring חובה | שם לזיהוי ה-subscription ב-UI. 1–100 תווים. |
urlstring חובה | ה-URL שאליו נשלחים ה-payloads. חייב להיות כתובת ציבורית (http או https; HTTPS מומלץ מאוד) — כתובות פנימיות / שמורות (localhost, 10.x, 192.168.x וכו') נדחות עם 400 וגם נחסמות בזמן המשלוח. עד 500 תווים. |
eventsstring[] חובה | מערך לא-ריק של שמות אירועים מפורשים מתוך קטלוג האירועים. אין wildcard — ערך לא מוכר (כולל "*") נדחה עם 400; כדי להירשם להכל, פרטו את כל האירועים. |
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. הוא מוצג פעם אחת בלבד.
/api/v1/webhooks/{id}webhooks:manageפרטי subscription יחיד (ללא ה-secret).
/api/v1/webhooks/{id}webhooks:manageעדכון של subscription — name, url, events, או isActive (הפעלה/השבתה). אותם אילוצים כמו ביצירה.
/api/v1/webhooks/{id}webhooks:manageמחיקת subscription. הפעולה idempotent — מחיקה של ID שכבר נמחק מחזירה 200.
/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
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
{
"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).
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
ניסיונות חוזרים
תגובה עם status ב-2xx נחשבת להצלחה. כל תגובה אחרת או timeout (10 שניות) נחשבים לכישלון ומפעילים backoff מעריכי:
- ניסיון 1 → 1 דקה
- ניסיון 2 → 5 דקות
- ניסיון 3 → 30 דקות
- ניסיון 4 → שעתיים
- ניסיון 5 → 12 שעות
לאחר 6 כישלונות סך הכל, ה-delivery יסומן exhausted ולא יישלח שוב. לוג המסירות זמין בלוח הבקרה של ה-Tenant.
Idempotency חובה
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.