> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# סקירת Webhooks

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

ThunderPhone שולח בקשות HTTP מסוג `POST` לשרת שלכם כאשר מתרחשים אירועים
במהלך שיחה — שיחה נכנסת מתחילה, שיחה מסתיימת, הרצת דירוג מסתיימת,
התראה מופעלת וכן הלאה. קיימים **שני מודלי מסירה**:

<CardGroup cols={2}>
  <Card title="נקודות קצה של webhook (מומלץ)" icon="bolt" href="/he/webhooks/endpoints">
    כתובות URL מרובות, סודות לכל נקודת קצה, מסנני אירועים לכל נקודת קצה
    וניסיונות חוזרים אוטומטיים.
    ניהול באמצעות `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>

  <Card title="Webhook מדור קודם עם כתובת URL יחידה" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    כתובת URL אחת לכל ארגון. כולל את אירועי מחזור החיים של השיחה, לרבות
    חילופי התצורה **החוסמים**. מנוהל ב-`GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

כל עשרת סוגי האירועים ב[קטלוג האירועים](/he/webhooks/events) נמסרים
באמצעות נקודות קצה של webhook. ששת אירועי מחזור החיים של השיחה
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) נשלחים **גם** אל ה-webhook
מדור קודם עם כתובת URL יחידה — אם יש לכם גם כתובת מדור קודם וגם נקודת
קצה תואמת, תקבלו את האירוע בשני הנתיבים. ההתנהגות החוסמת (חילופי
[התצורה של `telephony.incoming` / `web.incoming`](/he/webhooks/call-incoming)
ושליחת [כלים](/he/tools/overview) במצב webhook)
מתקיימת אך ורק בנתיב המדור הקודם; כל מסירה לנקודת קצה היא התראה
ללא המתנה לתגובה.

## פורמט המטען

מסירות לנקודות קצה הן אובייקט JSON עם `data`, `event_id` ו-`type`:

```json theme={null}
{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}
```

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

ה-webhook מדור קודם עם כתובת URL יחידה שולח את אותם `type` ו-`data`,
אך **ללא** `event_id`:

```json theme={null}
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

בהעברה ברשת, כל גוף מסודר באופן קנוני — המפתחות ממוינים
בסדר אלפביתי, ללא רווחים וב-UTF-8. הדוגמאות המעוצבות במסמכים אלה
מיועדות לקריאות בלבד.

עיינו ב[קטלוג האירועים](/he/webhooks/events) לקבלת הרשימה המלאה של סוגי
האירועים ושדות המטען.

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

כל בקשה כוללת חתימת HMAC-SHA256 על **גוף הבקשה הגולמי** בכותרת `X-ThunderPhone-Signature`. מפתח החתימה הוא ה-`secret` של נקודת הקצה (או ה-`secret` של ה-webhook ברמת הארגון שלכם עבור מסירות מדור קודם).

### שלבים

1. קראו את גוף הבקשה הגולמי **לפני** כל ניתוח.
2. חשבו את `hmac_sha256(secret, body).hexdigest()`.
3. השוו בזמן קבוע לכותרת `X-ThunderPhone-Signature`.

אנו חותמים בדיוק על הבתים שאנו משדרים, ובתים אלה הם הסריאליזציה הקנונית של JSON (מפתחות ממוינים, מפרידים קומפקטיים). לכן אימות מול הגוף הגולמי תמיד עובד — ואם המסגרת שלכם מספקת לכם רק JSON מנותח, סריאליזציה מחדש שלו עם מפתחות ממוינים ומפרידים קומפקטיים מפיקה בתים זהים. שתי השיטות מתועדות ב[מדריך האימות](/he/guides/verify-webhook-signatures).

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_signature(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode("utf-8"),
          body,
          hashlib.sha256,
      ).hexdigest()
      return hmac.compare_digest(expected, signature or "")

  # Example Flask handler
  from flask import Flask, request, abort
  app = Flask(__name__)

  @app.post("/thunderphone-webhook")
  def handle():
      body = request.get_data()
      sig = request.headers.get("X-ThunderPhone-Signature", "")
      if not verify_signature(body, sig, WEBHOOK_SECRET):
          abort(401)
      event = request.get_json()
      # dispatch on event["type"] …
      return "", 204
  ```

  ```javascript Node.js (Express) theme={null}
  import crypto from "node:crypto";
  import express from "express";

  function verifySignature(body, signature, secret) {
    const expected = crypto
      .createHmac("sha256", secret)
      .update(body)
      .digest("hex");
    if (!signature || expected.length !== signature.length) return false;
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature),
    );
  }

  const app = express();
  app.post(
    "/thunderphone-webhook",
    express.raw({ type: "application/json" }),
    (req, res) => {
      const sig = req.header("X-ThunderPhone-Signature") || "";
      if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
        return res.sendStatus(401);
      }
      const event = JSON.parse(req.body.toString("utf8"));
      // dispatch on event.type …
      res.sendStatus(204);
    },
  );
  ```
</CodeGroup>

## סמנטיקת מסירה

כללים אלה חלים על מסירות ל**נקודות קצה**. ה-webhook הוותיק עם כתובת URL יחידה מבצע ניסיון סינכרוני יחיד ללא ניסיונות חוזרים.

<AccordionGroup>
  <Accordion title="ניסיונות חוזרים">
    כל אירוע נשלח פעם אחת באופן מיידי. כל תגובת `2xx` מאשרת את
    המסירה. בכל תוצאה אחרת (שאינה 2xx, שגיאת חיבור, פסק זמן), ננסה
    שוב **דקה, 5 דקות, 30 דקות, שעתיים, 6 שעות,
    12 שעות ו-24 שעות לאחר הניסיון הראשון** — 8 ניסיונות בפריסה על פני
    24 שעות. אם כל הניסיונות נכשלים, המסירה נפסקת ונקודת הקצה
    מסומנת כ-`status="failing"` ב[נקודות קצה של webhook](/he/webhooks/endpoints).
    החזירו `2xx` מיד כשהמטען התקבל באופן עמיד; עבדו אותו באופן אסינכרוני.
  </Accordion>

  <Accordion title="סדר">
    סדר המסירה הוא על בסיס מיטב המאמץ. בפועל, אנו מוסרים אירועים לפי
    הסדר שבו הם נפלטים, אך ניסיונות חוזרים עשויים לשנות את הסדר במקרה של כשל.
    תמיד הסירו כפילויות ובצעו התאמה לפי `call_id` / מזהה אובייקט.
  </Accordion>

  <Accordion title="כפילויות">
    המסירה היא **לפחות פעם אחת**: ניסיון חוזר לאחר תגובה שלא
    התקבלה אצלנו יכול ליצור כפילות של אירוע. כל ניסיון חוזר נושא את אותו
    `event_id`, לכן אחסנו מזהים שעובדו ודלגו על חזרות. `event_id` גם
    משותף בין נקודות קצה — שתי נקודות קצה שמנויות לאותו אירוע מקבלות את אותו `event_id`.
  </Accordion>

  <Accordion title="פסקי זמן">
    למסירות לנקודות קצה יש פסק זמן של **30 שניות** לכל ניסיון. בנתיב
    הוותיק, בקשות חוסמות שמנהלות התנהגות של שיחה חיה — חילופי תצורה של
    [`telephony.incoming` / `web.incoming`](/he/webhooks/call-incoming) — מסתיימות בפסק זמן לאחר **10 שניות**,
    אך תגובה איטית מעכבת את המענה לשיחה, לכן שאפו להשיב בתוך כמה
    שניות. שליחת [כלים](/he/tools/overview) במצב webhook מאפשרת 20 שניות.
  </Accordion>

  <Accordion title="כתובות IP מקור">
    webhooks יוצאים מגיעים מטווח כתובות ה-IP בענן של ThunderPhone.
    אם חומת האש שלכם דורשת רשימת היתרים, פנו לתמיכה ונשתף אתכם
    בטווחים העדכניים.
  </Accordion>
</AccordionGroup>

## בחירה בין webhooks ותיקים לבין webhooks מבוססי נקודות קצה

| תכונה               | ותיק (`/v1/webhook`)                                                     | נקודות קצה (`/v1/developer/webhook-endpoints`) |
| ------------------- | ------------------------------------------------------------------------ | ---------------------------------------------- |
| מספר כתובות URL     | 1 לכל ארגון                                                              | רבות לכל ארגון                                 |
| כיסוי אירועים       | `telephony.*` / `web.*` בלבד                                             | כל 10 סוגי האירועים                            |
| סינון אירועים       | —                                                                        | לכל נקודת קצה                                  |
| ניסיונות חוזרים     | אין                                                                      | 8 ניסיונות במשך 24 שעות                        |
| מעטפה               | `type` + `data`                                                          | `type` + `data` + `event_id`                   |
| החלפת סוד           | מחליפה סוד יחיד                                                          | סוד לכל נקודת קצה                              |
| השבתה ללא מחיקה     | —                                                                        | `status=disabled`                              |
| נראות סטטוס         | —                                                                        | `active` / `disabled` / `failing`              |
| חילופי תצורה חוסמים | כן ([`telephony.incoming` / `web.incoming`](/he/webhooks/call-incoming)) | לעולם לא — התראות בלבד                         |
| המתאים ביותר עבור   | תצורת שיחות דינמית                                                       | צריכת אירועים בייצור                           |

באינטגרציות חדשות, צרכו אירועים באמצעות webhooks מבוססי נקודות
קצה. השאירו (או הוסיפו) כתובת URL ותיקה רק אם אתם מגדירים שיחות
באופן דינמי בזמן המענה או משתמשים בשליחת כלים במצב webhook — חילופי
בקשה/תגובה אלה פועלים רק בנתיב הוותיק.

***

## קשורים

<CardGroup cols={2}>
  <Card title="קטלוג אירועים" icon="list" href="/he/webhooks/events">
    כל סוגי האירועים והמטענים שלהם.
  </Card>

  <Card title="נקודות קצה של webhook" icon="bolt" href="/he/webhooks/endpoints">
    נהלו נקודות קצה מרובות, מסנני אירועים וסודות.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/he/webhooks/call-incoming">
    הבקשה החוסמת שהשרת שלכם חייב להשיב לה כדי להגדיר שיחות.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/he/webhooks/call-complete">
    מטען לאחר השיחה עם תמליל, הקלטה ומדדים.
  </Card>
</CardGroup>
