> ## 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.

# Webhook-aláírások ellenőrzése

> A ThunderPhone minden webhook- és eszközkérést aláír. Ellenőrizze egyszer, használja újra mindenhol.

Minden, az Ön szerverére küldött kérés — webhook-kézbesítés és
eszközvégpont-meghívás — HMAC-SHA256-aláírást tartalmaz a
`X-ThunderPhone-Signature` fejlécben. Állítsa be egyszer helyesen az
ellenőrzést, majd használja ugyanazt a segédfüggvényt minden kezelőben.

## Az algoritmus

1. Olvassa be a **nyers** kérési törzset — pontosan azokat a bájtokat, amelyeket POST kéréssel küldtünk Önnek.
2. Számítsa ki: `hmac_sha256(secret, body).hexdigest()`.
3. Hasonlítsa össze **konstans időben** az `X-ThunderPhone-Signature` értékével.
   (A naiv karakterlánc-összehasonlítás időzítési információkat szivárogtat.)

Pontosan azokat a bájtokat írjuk alá, amelyeket továbbítunk, ezért a nyers törzs
ellenőrzése mindig működik. Ezek a bájtok a hasznos teher **kanonikus JSON-szerializációját**
is jelentik — a kulcsok ábécésorrendben, tömör elválasztókkal
(szóköz nélkül `,` és `:`), UTF-8 kódolással. Ez egy második, teljesen
egyenértékű megoldást ad arra az esetre, ha a keretrendszere csak elemzett JSON-t tesz elérhetővé:
szerializálja újra kanonikusan, és azon számítson HMAC-et.

```python theme={null}
# Equivalent to hashing the raw body:
import json
canonical = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
```

Részesítse előnyben a nyers törzset — ez eggyel kevesebb lépés, és nem érintik
egyes nyelvek JSON-számok oda-vissza szerializálásával kapcsolatos sajátosságai.

## Melyik titkos kulcs?

| Forrás                                                                                     | Titkos kulcs                                                                                                                            |
| ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| [Webhook-végpont](/hu/webhooks/endpoints) (`/v1/developer/webhook-endpoints`)              | Végpontonkénti `secret` (48 hexadecimális karakter), amelyet létrehozáskor egyszer adunk vissza                                         |
| [Örökölt, egyetlen URL-es webhook](/api-reference/organizations#legacy-single-url-webhook) | Szervezetenkénti `secret`, amelyet a `GET /v1/webhook` ad vissza                                                                        |
| [Eszközvégpont-meghívás](/hu/tools/overview) (közvetlen hívás az Ön `endpoint.url` címére) | A **szervezetszintű webhook-titkos kulcs** (ugyanaz, mint az örökölt, egyetlen URL-es webhook esetén) — nem végpontonkénti titkos kulcs |

Tárolja a titkos kulcsot titkoskulcs-kezelőben vagy környezeti változóban — soha ne commitolja.

## Referenciamegvalósítások

Mind a négy a nyers kérési törzset ellenőrzi:

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


  def verify(body: bytes, signature: str, secret: str) -> bool:
      """Constant-time HMAC-SHA256 verification."""
      expected = hmac.new(
          secret.encode("utf-8"),
          body,
          hashlib.sha256,
      ).hexdigest()
      return hmac.compare_digest(expected, signature or "")
  ```

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

  export function verify(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),
    );
  }
  ```

  ```go Go theme={null}
  package webhook

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
  )

  func Verify(body []byte, signature, secret string) bool {
      mac := hmac.New(sha256.New, []byte(secret))
      mac.Write(body)
      expected := hex.EncodeToString(mac.Sum(nil))
      return hmac.Equal([]byte(expected), []byte(signature))
  }
  ```

  ```ruby Ruby theme={null}
  require "openssl"

  def verify(body, signature, secret)
    expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
    Rack::Utils.secure_compare(expected, signature.to_s)
  end
  ```
</CodeGroup>

## Keretrendszerspecifikus bekötés

<CodeGroup>
  ```python FastAPI theme={null}
  from fastapi import FastAPI, HTTPException, Request

  app = FastAPI()

  @app.post("/thunderphone-webhook")
  async def hook(request: Request):
      body = await request.body()           # raw bytes, NOT request.json()
      sig = request.headers.get("X-ThunderPhone-Signature", "")
      if not verify(body, sig, SECRET):
          raise HTTPException(status_code=401)

      import json
      event = json.loads(body)
      # … dispatch on event["type"] …
      return {"ok": True}
  ```

  ```javascript Express theme={null}
  import express from "express";

  const app = express();

  app.post(
    "/thunderphone-webhook",
    // IMPORTANT: parse as raw; do NOT use express.json() here.
    express.raw({ type: "application/json" }),
    (req, res) => {
      const sig = req.header("X-ThunderPhone-Signature") || "";
      if (!verify(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);
    },
  );
  ```

  ```python Django theme={null}
  import json

  from django.http import JsonResponse, HttpResponseForbidden
  from django.views.decorators.csrf import csrf_exempt
  from django.views.decorators.http import require_POST


  @csrf_exempt
  @require_POST
  def hook(request):
      body = request.body  # raw bytes
      sig = request.headers.get("X-ThunderPhone-Signature", "")
      if not verify(body, sig, SECRET):
          return HttpResponseForbidden("invalid signature")
      event = json.loads(body)
      # … dispatch on event["type"] …
      return JsonResponse({"ok": True})
  ```
</CodeGroup>

## Eszközhívások ellenőrzése

Amikor az ügynök közvetlenül meghívja valamelyik
[függvényeszközét](/hu/tools/overview) (az eszköz rendelkezik
`endpoint` mezővel), a kérés két ThunderPhone-fejlécet tartalmaz az
Ön által konfigurált `endpoint.headers` mellett:

* `X-ThunderPhone-Call-ID` — az élő hívás numerikus azonosítója.
* `X-ThunderPhone-Signature` — HMAC-SHA256, amely az Ön
  **szervezeti szintű webhooktitkával** van kulcsolva, és a kérés törzsének
  pontos bájtjaira van számítva.

Ugyanaz a `verify()` segédfüggvény változtatás nélkül működik, két
sajátossággal:

1. A **`GET` / `DELETE` eszközöknek nincs törzsük.** Az argumentumok
   lekérdezési paraméterekként érkeznek, az aláírás pedig az **üres bájtsorozatra**
   van kiszámítva — tehát `verify(b"", sig, secret)` (Python) vagy
   `verify(Buffer.alloc(0), sig, secret)` (Node). **Ne** hashelje a
   lekérdezési karakterláncot.
2. **Az örökölt webhookot nem konfiguráló szervezeteknek nincs szervezeti titkuk.** Ebben
   az esetben az eszközhívások csak `X-ThunderPhone-Call-ID` fejlécet
   tartalmaznak, aláírási fejlécet nem. Konfigurálja az örökölt webhookot
   (`PUT /v1/webhook`) aláírási titok beszerzéséhez, vagy hitelesítse az
   eszközhívásokat saját fejléccel az `endpoint.headers` használatával.

```python theme={null}
@app.post("/tools/search-appointments")
async def tool(request: Request):
    body = await request.body()  # b"" for GET/DELETE tools
    sig = request.headers.get("X-ThunderPhone-Signature", "")
    call_id = request.headers.get("X-ThunderPhone-Call-ID", "")
    if not verify(body, sig, ORG_WEBHOOK_SECRET):
        raise HTTPException(status_code=401)
    args = json.loads(body)
    ...
```

A webhook-**módú** eszközirányítás (az `endpoint` nélküli eszközök,
amelyek `telephony.tool` / `web.tool` formában érkeznek az Ön szervezeti
webhookjára) egy szokványos aláírt webhook — a fenti standard eljárás
alkalmazható. Mindkét kérésformátumról lásd:
[Függvényeszközök](/hu/tools/overview).

## Gyakori buktatók

<AccordionGroup>
  <Accordion title="Újraszerializálás alapértelmezett formázással">
    A törzs feldolgozása, majd a JSON-könyvtár alapértelmezett
    beállításaival történő újrakiírása (szóközök a `,` / `:` után,
    beillesztési sorrendben rendezett kulcsok) eltérő bájtokat eredményez,
    és érvényteleníti a HMAC-et. Ellenőrizze a nyers törzset — vagy ha
    újra kell szerializálnia, pontosan egyezzen a kanonikus formánkkal:
    rendezett kulcsok, tömör elválasztók, UTF-8.
  </Accordion>

  <Accordion title="A keretrendszer automatikusan feldolgozza a JSON-t">
    Az Express `express.json()` middleware-e felhasználja a törzsfolyamot,
    így elveszíti a nyers bájtokat. Kifejezetten a webhook útvonalán
    használja az `express.raw()` függvényt, vagy pufferelje a nyers törzset
    egy előzetes middleware-ben. Ugyanez vonatkozik a NestJS-re / Koára is —
    tekintse meg a „raw body” dokumentációjukat.
  </Accordion>

  <Accordion title="Időzítés szempontjából nem biztonságos összehasonlítás">
    A JS-ben használt `expected === signature`, illetve a Pythonban használt `expected == signature`
    időzítésfüggő összehasonlítás. Használja rendre a `crypto.timingSafeEqual`
    vagy a `hmac.compare_digest` függvényt. A teljesítménybeli különbség
    elhanyagolható.
  </Accordion>

  <Accordion title="Helytelen titok az eszközvégpontokhoz">
    A közvetlen eszközvégpont-hívások aláírásához a **szervezeti szintű webhooktitkot**
    használjuk (`GET /v1/webhook`) — nem pedig a
    `/v1/developer/webhook-endpoints` egyes végpontjaihoz tartozó titkok valamelyikét.
    Használja újra ugyanazt a `verify()` függvényt, de ügyeljen arra, hogy az
    eszközútvonalakon a szervezeti titkot adja át neki.
  </Accordion>

  <Accordion title="A lekérdezési karakterlánc hashelése GET/DELETE eszközöknél">
    A törzs nélküli eszközmetódusok esetében az aláírás az üres bájtsorozatra
    vonatkozik, így egyetlen univerzális módszer használható: a nyers kérési
    törzs HMAC-e, bármi is legyen az. Az URL vagy a lekérdezési karakterlánc
    hashelése soha nem fog egyezni.
  </Accordion>

  <Accordion title="Nem 401-es válasz visszaadása eltérés esetén">
    Ha sikertelen ellenőrzés esetén 200-as választ ad vissza, a kezelő
    visszajátszásos támadások célpontjává válik. Sikertelen ellenőrzés esetén
    mindig nem 2xx választ adjon.
  </Accordion>
</AccordionGroup>

***

## Következő lépések

<CardGroup cols={2}>
  <Card title="Webhookok áttekintése" icon="bolt" href="/hu/webhooks/overview">
    Kézbesítési szemantika, újrapróbálkozások, forrás IP-címek.
  </Card>

  <Card title="Webhookvégpontok" icon="plug" href="/hu/webhooks/endpoints">
    Több URL kezelése, titkok rotálása.
  </Card>

  <Card title="Funkcióeszközök" icon="screwdriver-wrench" href="/hu/tools/overview">
    A két eszközmeghívási útvonal és a hozzájuk tartozó kérésformátumok.
  </Card>

  <Card title="Eszközintegrációk" icon="wrench" href="/hu/guides/build-tool-integration">
    Készítsen teljes, eszközökre épülő integrációt elejétől a végéig.
  </Card>
</CardGroup>
