# ה-API של Webhooks

> קבלו בשרת שלכם אירועי job.completed ו-job.failed חתומים, הריצו תהליכים דרך הכתובת של טריגר Webhook, צרו תזמונים ושלחו תוצאות החוצה.

Source: https://nodaro.ai/he/docs/developers/api/webhooks

**Webhooks** מחברים את Nodaro למערכות האחרות שלכם, בשני הכיוונים. **נקודת קצה של Webhook** שאתם רושמים מקבלת אירוע חתום בכל פעם שאחת המשימות שלכם מסתיימת, ולכן השרת שלכם לא צריך לבדוק את הסטטוס מדי כמה שניות. רכיב [טריגר Webhook](https://nodaro.ai/docs/nodes/automate/webhook-trigger) נותן לתהליך כתובת שכל מערכת יכולה לקרוא לה כדי להתחיל הרצה, רכיב [טריגר מתוזמן](https://nodaro.ai/docs/nodes/automate/schedule-trigger) מריץ את התהליך לפי לוח זמנים, ורכיב [פלט Webhook](https://nodaro.ai/docs/nodes/publish/webhook-output) שולח את התוצאות של הרצה לשרת שלכם.

Workflow: מערכת חיצונית קוראת לכתובת של רכיב טריגר Webhook, רכיב יצירת תמונה רץ, ורכיב פלט Webhook שולח את כתובת התמונה לשרת שלכם.

- טריגר Webhook → יצירת תמונה (פרומפט)
- יצירת תמונה → פלט Webhook

## נקודות קצה
הנתיבים האלה מנהלים את נקודות הקצה ש-Nodaro שולח אליהן אירועי משימות. ראו את הסעיף [קבלת אירועי משימות בשרת שלכם](#job-events).

| שיטה | נתיב | מה היא עושה |
| --- | --- | --- |
| `POST` | `/v1/webhook-endpoints` | רישום נקודת קצה. התגובה כוללת את סוד החתימה שלה, פעם אחת בלבד. |
| `GET` | `/v1/webhook-endpoints` | נקודות הקצה שלכם, מהחדש לישן. סוד אף פעם לא מופיע ברשימה. |
| `GET` | `/v1/webhook-endpoints/:id` | נקודת קצה אחת. |
| `PATCH` | `/v1/webhook-endpoints/:id` | שינוי של `url`, `events`, `description` או `isActive`. |
| `DELETE` | `/v1/webhook-endpoints/:id` | מחיקת נקודת הקצה ויומן המסירות שלה. |
| `POST` | `/v1/webhook-endpoints/:id/rotate-secret` | החלפת סוד החתימה בסוד חדש, עם תקופת חסד לסוד הישן. |
| `GET` | `/v1/webhook-endpoints/:id/deliveries` | יומן המסירות, מהחדש לישן. |
| `GET` | `/v1/webhook-endpoints/:id/deliveries/:deliveryId` | מסירה אחת. |
| `POST` | `/v1/webhook-endpoints/:id/test` | שליחת `ping` לבדיקה. |

הנתיבים האלה מריצים תהליכים ומנהלים את הטריגרים שלהם. הם לא נמצאים תחת `/v1/webhook-endpoints`.

| שיטה | נתיב | מה היא עושה |
| --- | --- | --- |
| `POST` | `/v1/webhooks/:token` | התחלת הרצה. ציבורית: האסימון שבנתיב הוא פרטי הגישה. |
| `GET` | `/v1/workflows/:id/triggers` | הטריגרים של תהליך, עם הכתובת והאסימון של כל Webhook. |
| `PATCH` | `/v1/workflow-triggers/:id` | השהיה או חידוש של טריגר עם `isActive`, או שינוי ה-`config` של תזמון. |
| `POST` | `/v1/workflow-triggers` | יצירה ידנית של טריגר שלא קשור לשום רכיב. |
| `POST` | `/v1/workflows/:id/sync-triggers` | רישום מחדש של רכיבי הטריגר של תהליך, מהגרסה השמורה שלו. |

## קבלת אירועי משימות בשרת שלכם
רשמו נקודת קצה עם כתובת `https`, ו-Nodaro שולח אליה בקשת `POST` בכל פעם שאחת המשימות שלכם מסתיימת. כך השרת שלכם יודע על יצירה שהסתיימה בלי לקרוא ל-`GET /v1/jobs/:id` מדי כמה שניות. כל בקשה נחתמת בסוד של נקודת הקצה עצמה, נשלחת שוב במרווחים הולכים וגדלים עד שהשרת שלכם עונה, ונרשמת ביומן המסירות של נקודת הקצה.

### רישום נקודת קצה
**curl**

```bash
curl -s -X POST "https://app.nodaro.ai/v1/webhook-endpoints" \
  -H "Authorization: Bearer $NODARO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/nodaro-webhook", "description": "order fulfilment"}'
```

**TypeScript SDK**

```ts
const { data: endpoint, secret } = await client.webhooks.create({
url: "https://example.com/nodaro-webhook",
description: "order fulfilment",
})
// Store `secret` (whsec_...) with your server's config: it is shown only once.

await client.webhooks.test(endpoint.id) // a ping, sent within seconds
```

**CLI**

```bash
nodaro webhooks create --url https://example.com/nodaro-webhook
nodaro webhooks test <id> --wait
```

התגובה היא `201` עם `{ data, secret }`. הסוד מתחיל ב-`whsec_` ומופיע רק בתגובה הזו, ולכן שמרו אותו מיד. השדה `events` אופציונלי: נקודת קצה מקבלת את שני אירועי המשימות, אלא אם השדה `events` מציין רק אחד מהם.

כך נראית נקודת קצה כשקוראים אותה:

```json
{
"id": "3f6c2a1e-8b4d-4c7a-9e1f-2d5b6a7c8e90",
"url": "https://example.com/nodaro-webhook",
"description": "order fulfilment",
"events": ["job.completed", "job.failed"],
"isActive": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"appId": null,
"secretCreatedAt": "2026-10-15T10:00:00Z",
"previousSecretExpiresAt": null,
"createdAt": "2026-10-15T10:00:00Z",
"updatedAt": "2026-10-15T10:00:00Z"
}
```

### אירועים
| אירוע | מתי הוא נשלח |
| --- | --- |
| `job.completed` | משימה שלכם הגיעה לסטטוס `completed`. משימה שממתינה לבדיקה (`pending_review`) שולחת את האירוע שלה כשהיא מאושרת. |
| `job.failed` | משימה שלכם הגיעה לסטטוס `failed`. |
| `ping` | רק מ-`POST /v1/webhook-endpoints/:id/test`. נקודת קצה אף פעם לא נרשמת אליו. |

כל משימה ברשימת המשימות שלכם נחשבת: יצירה בודדת, וגם המשימה של כל רכיב בתוך הרצת תהליך. משימה שבוטלה לא שולחת כלום, וגם הסטטוסים שבדרך, כגון `pending` ו-`processing`, לא שולחים אירוע. משימה שנכשלת ומסתיימת בהצלחה מאוחר יותר שולחת אירוע אחד על כל סיום. המשימות שבתוך ההרצה של רכיב שמור לא נמצאות ברשימת המשימות שלכם, ולכן הן לא שולחות כלום: הרכיב שהתחיל את ההרצה מדווח בשמה.

### הבקשה
Nodaro שולח בקשת `POST` עם `Content-Type: application/json` ועם הכותרות האלה:

| כותרת | ערך |
| --- | --- |
| `Nodaro-Signature` | `t=<unix seconds>,v1=<hex>`. בזמן החלפת סוד, `v1=` אחד לכל סוד. |
| `Nodaro-Event` | `job.completed`, `job.failed` או `ping` |
| `Nodaro-Delivery` | מזהה המסירה, זהה בכל ניסיון חוזר של האירוע הזה |
| `Nodaro-Webhook` | מזהה נקודת הקצה |
| `Nodaro-Attempt` | `1` בניסיון הראשון, ואחר כך `2`, `3` וכן הלאה |
| `User-Agent` | `Nodaro-Webhooks/1` |

רק חותמת הזמן והגוף חתומים. נתבו וסננו כפילויות לפי ה-`type` וה-`id` של הגוף המאומת, ואף פעם לא לפי הכותרות `Nodaro-Event` או `Nodaro-Delivery`.

```json
{
"id": "6f1d0c0e-1c7e-4d0a-9f2b-0a4e2f7b9c11",
"type": "job.completed",
"createdAt": "2026-10-15T10:00:04.512Z",
"data": {
"job": {
"id": "0b9a5c1d-2e3f-4a5b-8c6d-7e8f9a0b1c2d",
"status": "completed",
"progress": 100,
"job_type": "generate-image",
"input_data": { "type": "generate-image", "prompt": "a lighthouse at dusk" },
"output_data": { "imageUrl": "https://cdn.nodaro.ai/images/0b9a5c1d.png" },
"error_message": null,
"error_hint": null,
"credit_status": "committed",
"credits": 40,
"credits_charged": 40,
"credits_held": 40,
"credits_released": 0,
"source": "sdk",
"session_id": null,
"created_at": "2026-10-15T10:00:00.000Z",
"completed_at": "2026-10-15T10:00:04.512Z"
}
}
}
```

- **`id`** הוא מזהה המסירה. הוא ייחודי לכל צירוף של אירוע ונקודת קצה, וזהה בכל ניסיון חוזר, ולכן סננו כפילויות לפיו.
- **`createdAt`** הוא המועד שבו המשימה הגיעה לסטטוס הזה.
- **`data.job`** הוא המשימה במבנה ש-`GET /v1/jobs/:id` מחזיר, כמו שהיא הייתה בניסיון הראשון. ניסיונות חוזרים שולחים את אותו גוף, בייט אחר בייט. השדה `credits_charged` עדיין יכול להיות `null`, עם `credit_status: "reserved"`, כשעוד לא היה חיוב סופי. בדקו שוב את המשימה כדי לקבל את הסכום הסופי. כמו ב-`GET /v1/jobs/:id`, השדה `credits_charged` כולל את החיוב על השלבים שהמשימה הריצה, `credits_held` הוא השריון של ההרצה יחד עם השריונים של השלבים שלה, ו-`credits_released` הוא מה שחזר אחרי שכל השריונים בהרצה קיבלו חיוב סופי (`null` עד אז). ראו בדף ה-API [הקרדיטים של משימה](https://nodaro.ai/docs/developers/api/jobs#jobs-with-steps).
- **אירוע הוא לכל היותר 64 KB.** במשימה גדולה יותר, קודם מושמט `input_data`, ואחריו `output_data`, והשדה `data.omitted` מפרט מה הושמט. הנתיב `GET /v1/jobs/:id` מחזיר את המשימה כולה.
- **אירוע `ping`** כולל `"data": { "webhookId": "<endpoint id>" }`.

### אימות החתימה
הערך `v1` הוא ה-HMAC-SHA256 של `<t>.<raw body>`, עם הסוד המלא של נקודת הקצה כמפתח (`whsec_...`, ב-UTF-8). בדקו שלושה דברים לפני שאתם סומכים על בקשה:

1. החתימה תואמת בדיוק את הבייטים שקיבלתם. אף פעם אל תעשו סריאליזציה מחדש ל-JSON לפני הבדיקה.
2. חותמת הזמן עדכנית. חמש דקות הן מרווח הסובלנות המקובל. הבדיקה מונעת מצב שבו בקשה שיורטה נשלחת שוב מאוחר יותר.
3. עוד לא טיפלתם ב-`id` הזה.

עם ה-SDK, הפונקציה `verifyWebhookSignature` מ-`@nodaro/sdk` מבצעת את הבדיקות. היא משתמשת רק ב-Web Crypto, ולכן היא רצה ב-Node 20 ואילך, ב-Deno, ב-Bun, ב-Cloudflare Workers, ב-Vercel Edge ובדפדפנים.

```ts

const app = express()

app.post("/nodaro-webhook", express.raw({ type: "application/json" }), async (req, res) => {
try {
const event = await verifyWebhookSignature({
payload: req.body, // the raw bytes
header: req.headers["nodaro-signature"],
secret: process.env.NODARO_WEBHOOK_SECRET!,
toleranceSeconds: 300,
})
if (event.type === "job.completed") {
await saveResult(event.data.job.id, event.data.job.output_data)
}
res.sendStatus(204) // any 2xx within 10 seconds counts as delivered
} catch (err) {
if (err instanceof WebhookSignatureError) return res.status(400).send(err.code)
throw err
}
})
```

הפונקציה `verifyWebhookSignature` מחזירה את האירוע המפוענח, או זורקת `WebhookSignatureError`. ה-`code` של השגיאה הוא `missing_header`, `malformed_header`, `signature_mismatch`, `timestamp_outside_tolerance`, `invalid_payload`, `missing_secret` או `crypto_unavailable`.

בלי ה-SDK, ב-Node:

```ts

// rawBody: the request body as received (a Buffer or a string).
function verify(rawBody: Buffer | string, header: string, secret: string, toleranceSeconds = 300) {
const pairs = header.split(",").map((part) => part.trim().split("=", 2))
const t = pairs.find(([key]) => key === "t")?.[1]
const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest()
const matches = pairs.some(([key, value]) => {
if (key !== "v1") return false
const got = Buffer.from(value, "hex")
return got.length === expected.length && timingSafeEqual(got, expected)
})
return matches && Math.abs(Date.now() / 1000 - Number(t)) <= toleranceSeconds
}
```

בתקופת החסד של החלפת סוד, הכותרת כוללת `v1=` לכל סוד, והחדש ראשון. קבלו את הבקשה כשאחד מהם תואם. יומן המסירות שומר את הגוף המדויק של מסירה שלא הצליחה (`requestBody`), כדי שתוכלו לשחזר בדיקה שנכשלת, בייט אחר בייט.

### ניסיונות חוזרים
כל תגובת `2xx` בתוך 10 שניות נחשבת למסירה שהצליחה. כל דבר אחר מכשיל את הניסיון: `3xx` (Nodaro לא עוקב אחרי הפניות), `4xx`, `5xx`, פסק זמן או כתובת שלא עונה. אז המסירה נשלחת שוב:

| אחרי ניסיון | הניסיון הבא בעוד |
| --- | --- |
| 1 | 30 שנ׳ |
| 2 | 2 דק׳ |
| 3 | 10 דק׳ |
| 4 | שעה אחת |
| 5 | 6 שעות |
| 6 | 12 שעות |
| 7 | 24 שעות |

משך כל המתנה משתנה, עד 10% לכל כיוון. אחרי הניסיון ה-8, כ-43 שעות אחרי הראשון, המסירה נכשלת סופית. אירוע `ping` של בדיקה נשלח פעם אחת בלבד.

- **כל מסירה מגיעה לפחות פעם אחת.** ניסיון חוזר יכול להגיע אחרי שהשרת שלכם כבר טיפל בניסיון הראשון, למשל כשהוא ענה מאוחר מדי. סננו כפילויות לפי ה-`id` של המסירה.
- **אירועים יכולים להגיע שלא לפי הסדר.** עד 5 מסירות נשלחות במקביל לנקודת קצה אחת, ועד 10 לחשבון אחד. הסתמכו על `createdAt` ועל ה-`status` של המשימה עצמה, לא על סדר ההגעה.
- **שרת שלא עונה בכלל מעביר את נקודת הקצה להפסקה.** אחרי 5 ניסיונות ברצף בלי תגובת HTTP, נקודת הקצה נכנסת להפסקה של דקה, ובכל פעם שזה קורה שוב ההפסקה ארוכה פי שניים, עד שעה. מסירות שממתינות לא מאבדות ניסיונות, וכל תגובת HTTP מסיימת את ההפסקה.
- **בעיה בצד של Nodaro לא נספרת לחובתכם.** המסירה ממתינה כמה דקות ונשלחת שוב, בלי שהניסיון נספר. בינתיים, הרשומה שלה ביומן מציגה `signing_unavailable` או `job_unreadable`.

### כיבוי אוטומטי
כש-50 מסירות ברצף נכשלות סופית, Nodaro מכבה את נקודת הקצה: השדה `isActive` הופך ל-`false`, והשדה `disabledReason` הופך ל-`"consecutive_failures"`. תקנו את השרת המקבל, בדקו אותו עם `ping` של בדיקה, ואז הפעילו את נקודת הקצה עם `PATCH /v1/webhook-endpoints/:id` ועם `{ "isActive": true }`. ההפעלה גם מאפסת את הספירה, וכך גם כל מסירה שמצליחה.

כל עוד נקודת קצה כבויה, בין שאתם כיביתם אותה ובין ש-Nodaro כיבה אותה, לא נרשמים בשבילה אירועים. מסירות שכבר ממתינות מסתיימות ב-`failed` עם `error: "endpoint_disabled"`. אירוע `ping` של בדיקה עדיין נשלח.

### יומן המסירות
הנתיב `GET /v1/webhook-endpoints/:id/deliveries` מחזיר את המסירות של נקודת קצה, מהחדש לישן, 20 בכל עמוד כברירת מחדל ועד 100. סננו עם `status` (`pending`, `delivered` או `failed`), ועברו בין העמודים עם `limit` ו-`cursor`. התגובה היא `{ data, next }`. היומן נשמר 30 ימים.

כל מסירה כוללת את `event`, `jobId`, `status` ו-`attempts`, את `responseStatus` האחרון של השרת המקבל, את 2 ה-KB הראשונים של `responseBody` שלו, את `durationMs` ואת `error`, וכל עוד היא ממתינה, גם את `nextAttemptAt`. השדה `requestBody` הוא הגוף המדויק שנשלח. הוא נשמר כל עוד המסירה ממתינה או אחרי שהיא נכשלה, והוא `null` אחרי שהמסירה הצליחה. הנתיב `GET /v1/webhook-endpoints/:id/deliveries/:deliveryId` מחזיר מסירה אחת, למשל את ה-`ping` שבדיקה הכניסה לתור.

השדה `error` מקבל אחד מהערכים האלה:

| `error` | משמעות |
| --- | --- |
| `http_<status>` | השרת שלכם החזיר את הסטטוס הזה. |
| `timeout` | לא הגיעה תגובה בתוך 10 שניות. |
| `blocked_address` | הכתובת לא מצביעה על כתובת ציבורית באינטרנט. Nodaro דחה אותה לפני שהתחבר. |
| `connection_refused`, `connection_reset`, `tls_error`, `network_error` | החיבור נכשל. |
| `endpoint_disabled` | נקודת הקצה כובתה בזמן שהמסירה המתינה. |
| `app_revoked` | ביטלתם את הגישה של האפליקציה המחוברת שרשמה את נקודת הקצה. |
| `job_deleted` | המשימה נמחקה לפני שהיה אפשר לשלוח אותה. |
| `attempts_exhausted` | הניסיון האחרון נקטע בצד של Nodaro לפני שהתגובה אליו נרשמה. |
| `signing_unavailable`, `job_unreadable` | בעיה בצד של Nodaro. המסירה ממתינה ונשלחת שוב, בלי שהניסיון נספר. |

### החלפת הסוד
הנתיב `POST /v1/webhook-endpoints/:id/rotate-secret` מחזיר `{ secret, previousSecretExpiresAt }`, והסוד החדש מופיע רק בתגובה הזו. מאותו רגע, הסוד החדש חותם על כל מסירה, כולל ניסיונות חוזרים. הסוד שהוחלף ממשיך לחתום לצידו במשך `gracePeriodSeconds`: יום אחד כברירת מחדל, ו-7 ימים לכל היותר. כך יש לכם זמן לעדכן את השרת המקבל בלי פער בקבלת האירועים. שלחו `{ "gracePeriodSeconds": 0 }` כדי להפסיק מיד את הסוד הישן, למשל אחרי דליפה.

### כללים ומגבלות
- **`https` במארח ציבורי בלבד.** בלי `http://`, בלי `localhost`, בלי כתובת פרטית, loopback או link-local, ובלי שם שעובד רק בתוך רשת פרטית, כגון שם שמסתיים ב-`.internal` או ב-`.local`. הכתובת לא יכולה לכלול שם משתמש או סיסמה. שם המארח נבדק מחדש ב-DNS בכל ניסיון. כדי לקבל אירועים במחשב שלכם, השתמשו בטאנל ורשמו את כתובת ה-`https` שלו.
- **נקודת קצה אחת לכל כתובת.** רישום של אותה כתובת פעמיים נדחה עם `409 url_taken`. במקום זאת, רשמו נקודת קצה אחת לשני האירועים.
- **10 נקודות קצה לכל חשבון.** רישום של נקודת הקצה ה-11 נדחה עם `400 limit_reached`.
- **לפעולות כתיבה יש מגבלת קצב:** 30 בדקה ליצירה, לעדכון, למחיקה ולהחלפת סוד, ו-10 בקשות `ping` של בדיקה בדקה.
- **מי יכול לנהל נקודות קצה:** סשן מחובר, אסימון API אישי, או אסימון של אפליקציה מחוברת עם ההרשאה `jobs:read`. אסימון אישי שמוגבל לתהליכים מסוימים לא יכול.
- **נקודת קצה שאפליקציה מחוברת רושמת** (`appId` מוגדר) מקבלת רק את המשימות שהאפליקציה שלחה בעצמה, ורק כל עוד ההרשאה שנתתם לאפליקציה כוללת את `jobs:read`. המשימות של הרצת תהליך שהאפליקציה התחילה לא כלולות: בדקו את מצב ההרצה עם `GET /v1/workflow-executions/:id`. האפליקציה רואה רק את נקודות הקצה שהיא רשמה. אתם רואים את כולן ויכולים למחוק כל אחת מהן.
- **התקנות באירוח עצמי** שולחות מהשרת שלהן, וצריכות את `NODARO_ENCRYPTION_KEY`, שמערך השירותים של Docker Compose יוצר. בלעדיו, הנתיבים מחזירים `503 encryption_key_missing`.
- **לא זמין דרך MCP.** רישום נקודת קצה מחזיר סוד חתימה ומגדיר מסירה לכל החשבון, וזו פעולה לאפליקציה או ל-CLI, לא לסוכן AI.

שגיאות אחרות הן `400 validation_error` לכתובת או לגוף בקשה לא תקינים, `403 insufficient_scope`, `404 not_found` למזהה שלא שייך לכם ו-`409 url_invalid` לנקודת קצה שהכללים כבר לא מקבלים את הכתובת השמורה שלה. שנו את ה-`url` שלה לפני שאתם מפעילים או בודקים אותה. הקוד `409 secret_required` פירושו נקודת קצה שנרשמה לפני שהייתה חתימה: החליפו את הסוד שלה בסוד חדש לפני שאתם מפעילים או בודקים אותה.

## הרצת תהליך מקריאת HTTP
### הוספת רכיב טריגר Webhook
הוסיפו לתהליך רכיב [טריגר Webhook](https://nodaro.ai/docs/nodes/automate/webhook-trigger). תחת **פרמטרי פלט**, הוסיפו פרמטר אחד לכל ערך שהצד הקורא ישלח. לכל פרמטר יש **שם**, שתואם מפתח בגוף ה-JSON של הבקשה, ו**סוג**: `text`, `imageUrl`, `videoUrl` או `audioUrl`. חברו את הפרמטרים לרכיבים שצריכים להשתמש בהם.

### שמירת התהליך
השמירה יוצרת את נקודת הקצה: Nodaro מנפיק אסימון באורך 32 בייטים ורושם את `POST /v1/webhooks/<token>`. זה קורה בכל דרך שבה התהליך נשמר: מהעורך, מה-API, מה-SDK, מייבוא או מ-MCP. האסימון מונפק פעם אחת ואז נשמר, ולכן הכתובת שאתם נותנים למערכת חיצונית נשארת תקפה בכל השמירות הבאות. הסרת הרכיב מבטלת את הכתובת.

### קריאת הכתובת
הנתיב `GET /v1/workflows/<id>/triggers` מחזיר את הטריגרים של התהליך, כולל ה-`webhookUrl` וה-`webhookToken` של כל Webhook. הערך `webhookUrl` הוא נתיב, `/v1/webhooks/<token>`, ולכן הוסיפו לפניו את הכתובת של Nodaro שלכם. הטריגר שה-`config.nodeId` שלו הוא המזהה של הרכיב שייך לרכיב טריגר Webhook הזה. הרשימה מציגה רק טריגרים של תהליכים שבבעלותכם.

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

### קריאה לכתובת
שלחו בקשת `POST` עם גוף JSON. לא צריך כותרת `Authorization`: האסימון שבכתובת הוא פרטי הגישה.

```bash
curl -s -X POST "https://app.nodaro.ai/v1/webhooks/$WEBHOOK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a lighthouse in a storm", "imageUrl": "https://example.com/lighthouse.jpg"}'
```

הגוף הופך למטען של הטריגר. כל פרמטר קורא את המפתח בעל אותו שם, והסוג שלו קובע לאן הערך עובר: הסוג `imageUrl` מזין קלטים של תמונה, והסוג `videoUrl` מזין קלטים של וידאו.

כל טריגר Webhook מקבל עד 10 בקשות בדקה. המגבלה הזו נפרדת מהמגבלות של אסימוני ה-API שלכם.

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

### מה רץ בהרצה שטריגר התחיל
- **טריגר שמחובר לרכיבים מריץ רק את הענף שלו:** הרכיבים שאחרי הטריגר, וכל רכיב שהם צריכים כקלט.
- **טריגר שלא מחובר לשום דבר מריץ את התהליך כולו.**
- **לכן בתהליך אחד יכולים להיות כמה טריגרים**, וכל אחד מהם מתחיל ענף משלו.
- **״מחובר״ פירושו כל מה שמזין רכיב אחר:** חיבור שמצויר על הקנבס, רכיב בתוך רכיב קבוצה, או מיפוי שדות.
- **טריגרים לא מגבילים הרצות מהעורך, מה-API ומאפליקציות שפורסמו.** הן מריצות את מה שהן מריצות תמיד.

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

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

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

## ניהול טריגרים דרך ה-API
רכיבי טריגר נרשמים מעצמם כשהתהליך נשמר. ארבעה נתיבים מנהלים אותם ישירות:

- **`GET /v1/workflows/<id>/triggers`** מחזיר את הטריגרים של תהליך.
- **`PATCH /v1/workflow-triggers/<id>`** עם `{ "isActive": false }` משהה טריגר, והערך `true` מחזיר אותו לפעולה. בטריגר ששייך לרכיב, השמירה הבאה מחילה שוב את המתג של הרכיב עצמו, ולכן השתמשו במתג של הרכיב לכל שינוי שצריך להישאר. ערך `config` ששולחים ממוזג לתוך ה-`config` השמור: שלחו רק את מה שמשתנה. הקישור של הטריגר לרכיב שלו ומספר ההרצות שלו נשמרים.
- **`POST /v1/workflows/<id>/sync-triggers`** רושם מחדש את רכיבי הטריגר של התהליך מהגרסה השמורה שלו. העורך קורא לו אחרי כל שמירה, וכל מי שיש לו גישת עריכה יכול לקרוא לו. הנתיב מחזיר `{ "data": { "synced": …, "created": …, "updated": …, "removed": … } }`.
- **`POST /v1/workflow-triggers`** יוצר טריגר שלא שייך לשום רכיב, ולכן שמירת התהליך אף פעם לא משנה או מסירה אותו. כדי ליצור טריגר כזה צריך את אותה הרשאה שצריך כדי להריץ את התהליך, כי טריגר הוא הרצה שאף אחד לא משגיח עליה.

## תזמונים
רכיב [טריגר מתוזמן](https://nodaro.ai/docs/nodes/automate/schedule-trigger) מריץ תהליך לפי לוח זמנים. תזמון הוא רשימה של **כללים**, והתהליך רץ בכל פעם שכלל כלשהו מתאים לדקה הנוכחית.

### הפעלת תזמון
רכיב טריגר מתוזמן מתחיל הרצות רק כל עוד בנתונים שלו כתוב `"active": true`, כלומר כל עוד המתג של הרכיב מופעל. רכיב שנכתב דרך ה-API, ה-SDK או MCP בלי השדה `active` נרשם **מושהה**. המתג בעורך וכפתור התזמון בסרגל העליון של העורך (**תזמון פעיל** או **תזמון כבוי**) קובעים את אותו שדה. ייצוא כתבנית אף פעם לא כולל אותו, ולכן תזמון מיובא תמיד מתחיל מושהה.

### יצירת תזמון ידנית
```json
{
"workflowId": "8c2d7f1e-3a4b-4c5d-9e6f-7a8b9c0d1e2f",
"type": "schedule",
"config": {
"rules": [
{ "kind": "days", "every": 1, "hour": 9, "minute": 0 },
{ "kind": "weeks", "every": 2, "weekdays": [1, 3], "hour": 18, "minute": 30 }
],
"timezone": "Asia/Jerusalem"
}
}
```

שלחו אותו עם `POST /v1/workflow-triggers`. רכיב טריגר מתוזמן שומר `config` באותו מבנה.

| שדה ב-`config` | משמעות |
| --- | --- |
| `rules` | כלל אחד או יותר. התהליך רץ כשכלל כלשהו מתאים. |
| `timezone` | השעון שלפיו מחושבים הכללים, כשם של אזור זמן כגון `Asia/Jerusalem`. כשהשדה חסר, הכללים מחושבים לפי UTC. |
| `maxExecutions` | עצירה אחרי מספר ההרצות הזה. התזמון נשאר רשום: כדי להמשיך, הגדילו את המספר או מחקו אותו. |
| `retry` | אופציונלי. ההגדרה **ניסיון חוזר לאחר כישלון** של הרכיב: `{ afterMinutes, maxAttempts, cheapLanesOnly }`. הרצה מתוזמנת שמסתיימת ב-`failed` או ב-`timed_out` רצה שוב `afterMinutes` דקות אחרי שהסתיימה (5–1,440, ברירת המחדל 30), עד `maxAttempts` פעמים (1–5, ברירת המחדל 3). עם `cheapLanesOnly` (ברירת המחדל `true`), קריאות המודל של ההרצה ושל הניסיונות החוזרים שלה לא עוברות לשירות הישיר של יצרן המודל. שדה שלא נשלח מקבל את ברירת המחדל שלו. כדי שלא יהיה ניסיון חוזר, השמיטו את `retry`; בבקשת `PATCH`, הערך `"retry": null` מכבה אותו. ראו בדף [טריגר מתוזמן](https://nodaro.ai/docs/nodes/automate/schedule-trigger#retry-on-failure). |

| `kind` | שדות | מתי התהליך רץ |
| --- | --- | --- |
| `minutes` | `every` בין 1 ל-59 | בדקה 0, N, 2N וכן הלאה של כל שעה |
| `hours` | `every` בין 1 ל-23, `minute` | בשעה 0, N, 2N וכן הלאה של כל יום, באותה דקה |
| `days` | `every` בין 1 ל-31, `hour`, `minute` | כל N ימים בלוח השנה, באותה שעה |
| `weeks` | `every` בין 1 ל-52, `weekdays` (0 הוא יום ראשון, 6 הוא שבת), `hour`, `minute` | בימי השבוע האלה, כל N שבועות |
| `months` | `every` בין 1 ל-12, `dayOfMonth` בין 1 ל-31, `hour`, `minute` | באותו יום, או ביום האחרון של החודש כשהחודש קצר יותר, כל N חודשים |
| `cron` | `cron`, ביטוי עם 5 שדות | בכל פעם שהביטוי מתאים |

- **כל N ימים, שבועות או חודשים נספרים מנקודת התחלה קבועה**, והשבוע מתחיל ביום שני, ולכן שמירה חוזרת אף פעם לא מזיזה את הימים שבהם תזמון רץ.
- **בכלל `cron`, כל השדות חייבים להתאים**, גם היום בחודש וגם היום בשבוע, אף שבכלי cron מסוימים מספיק שאחד מהם יתאים. הערך `7` פירושו יום ראשון, כמו `0`.
- **ערכים מחוץ לטווח נדחים עם `400`**, וכך גם אזור זמן שהשרת לא מכיר או ערך `retry` מחוץ לטווח.
- **הרצה של ניסיון חוזר** מופיעה ב-`GET /v1/workflows/<id>/executions` עם `retry: { "attempt": 1, "maxAttempts": 3, "retryOf": "<failed execution id>" }`.
- **צורות ישנות יותר עדיין עובדות** בטריגרים שאתם יוצרים ידנית: ערך `interval` כגון `5m`, `1h` או `1d`, או מחרוזת `cron`. רכיב טריגר מתוזמן שנשמר איתן מומר לכללים.

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

## שליחת תוצאות לשרת שלכם
רכיב [פלט Webhook](https://nodaro.ai/docs/nodes/publish/webhook-output) שולח לכתובת שלכם, בבקשת `POST`, את התוצאה שמגיעה אליו, עם הפרמטרים שהגדרתם. לפרמטרים שלו יש אותם סוגים כמו לפרמטרים של הטריגר: `text`, `imageUrl`, `videoUrl` ו-`audioUrl`. ערכי מדיה הם כתובות URL של קבצים ש-Nodaro שומר, והשרת שלכם יכול להוריד אותם.

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

### שליחת מפתח עם הבקשה
נקודות קצה רבות מקבלות בקשה רק עם מפתח בכותרת. שמרו את שם הכותרת ואת המפתח פעם אחת כ**פרטי גישה** באפליקציית הווב, תחת **אינטגרציות › מפתחות גישה ל-Webhook**, ובחרו אותם ברכיב. המפתח אף פעם לא נכנס לתהליך, לייצוא או לתבנית.

כששומרים מפתח, בוחרים מי יכול להשתמש בו:

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

הרצות שהתחילו עם אסימון API או אסימון OAuth, מטריגר Webhook, מתזמון שנוצר דרך ה-API או מאפליקציה שפורסמה לא נחשבות להרצות שלכם, אף שהן רצות בשמכם. **בהרצות כאלה, נעלו את פרטי הגישה**: עם פרטי גישה רגילים, הרכיב נכשל עם הודעה ברורה במקום לשלוח. פרטי גישה נעולים נשלחים רק כשהכתובת של הרכיב תואמת לכתובת הנעולה, הרכיב לא עוקב אחרי הפניה לכתובת אחרת, והמפתח אף פעם לא נשלח על גבי `http` רגיל.

כשמצורפים פרטי גישה, הרכיב לא שומר ולא מציג את גוף התגובה של נקודת הקצה שלכם, רק את קוד הסטטוס שלה. ניהול פרטי הגישה נעשה רק באפליקציית הווב: הנתיבים של פרטי הגישה מחזירים `403 in_app_only` לאסימונים. פרסום אפליקציה או שיתוף תהליך כדי שאחרים יריצו אותו נדחים עם `409 credential_unbound`, עד שכל פרטי הגישה שהתהליך שולח איתם, כולל בתוך תת־תהליכים, נעולים לכתובת שהרכיב שולח אליה.

## קבלת התוצאה של הרצה בלי נקודת קצה
יש עוד שתי דרכים לדעת מה התוצאה של הרצה:

- **בדקו את הסטטוס מדי כמה שניות.** בדקו את מצב ההרצה עם `GET /v1/workflow-executions/:id`, או את מצב המשימה של רכיב יחיד עם `GET /v1/jobs/:id/status`. ראו את דפי ה-API [הרצות](https://nodaro.ai/docs/developers/api/executions) ו[משימות](https://nodaro.ai/docs/developers/api/jobs).
- **תנו לתהליך לדווח לכם.** סיימו את התהליך ברכיב פלט Webhook ששולח את התוצאות לשרת שלכם.

## שאלות נפוצות

### איך מריצים תהליך של Nodaro עם Webhook?

הוסיפו רכיב טריגר Webhook, הגדירו את הפרמטרים שלו ושמרו את התהליך. השמירה יוצרת כתובת מהצורה POST ‎/v1/webhooks/‎ עם אסימון בסוף. שלחו לכתובת הזו גוף JSON שהמפתחות שלו תואמים לשמות הפרמטרים, בלי שום אימות נוסף.

### האם Nodaro קורא לשרת שלי כשמשימה מסתיימת?

כן, אחרי שרושמים נקודת קצה של Webhook עם POST ‎/v1/webhook-endpoints. מאותו רגע, Nodaro שולח אירוע job.completed או job.failed חתום לכתובת ה-HTTPS שלכם בכל פעם שאחת המשימות שלכם מסתיימת, ומנסה שוב במשך כ-43 שעות.

### איך מאמתים אירוע Webhook של Nodaro?

חשבו HMAC-SHA256 של חותמת הזמן, נקודה והגוף הגולמי, עם הסוד של נקודת הקצה שלכם, והשוו אותו לערך v1 בכותרת Nodaro-Signature. בדקו גם שחותמת הזמן עדכנית. הפונקציה verifyWebhookSignature של ה-SDK מבצעת את שתי הבדיקות.

### האם בטוח לשתף כתובת Webhook?

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

### איך מתזמנים תהליך דרך ה-API?

שמרו רכיב טריגר מתוזמן שבנתונים שלו השדה active הוא true, או צרו תזמון עם POST ‎/v1/workflow-triggers ועם config של כללים ואזור זמן. Nodaro בודק כל תזמון פעם בדקה.

### למה רכיב טריגר מתוזמן שנוצר דרך ה-API לא רץ?

רכיב טריגר מתוזמן רץ רק כל עוד בנתונים שלו השדה active הוא true. רכיב שנכתב דרך ה-API, ה-SDK או MCP בלי השדה הזה נרשם מושהה. הגדירו את השדה ושמרו שוב.
