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

# Pārbaudiet tīmekļa aizķeru parakstus

> Katrs tīmekļa aizķeres un rīka pieprasījums no ThunderPhone ir parakstīts. Pārbaudiet vienreiz; izmantojiet atkārtoti visur.

Katram pieprasījumam, ko nosūtām uz jūsu serveri — tīmekļa āķa piegādēm un
rīku galapunktu izsaukumiem — galvenē
`X-ThunderPhone-Signature` ir HMAC-SHA256 paraksts. Pareizi ieviesiet pārbaudi vienreiz un
izmantojiet to pašu palīgfunkciju katrā apstrādātājā.

## Algoritms

1. Nolasiet **neapstrādāto** pieprasījuma pamattekstu — precīzus baitus, ko jums nosūtījām ar POST.
2. Aprēķiniet `hmac_sha256(secret, body).hexdigest()`.
3. Salīdziniet **konstantā laikā** ar `X-ThunderPhone-Signature`.
   (Naiva virkņu salīdzināšana atklāj laika informāciju.)

Mēs parakstām tieši baitus, ko pārsūtām, tāpēc neapstrādātā pamatteksta
pārbaude vienmēr darbojas. Šie baiti ir arī slodzes **kanoniskā JSON serializācija** —
atslēgas sakārtotas alfabētiski, kompakti atdalītāji
(`,` un `:` bez atstarpēm), UTF-8. Tas sniedz otru, pilnībā
ekvivalentu pieeju gadījumam, ja jūsu ietvars nodrošina tikai parsētu JSON:
veiciet kanonisku atkārtotu serializāciju un aprēķiniet tam HMAC.

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

Dodiet priekšroku neapstrādātajam pamattekstam — tas ir par vienu soli mazāk un novērš
JSON skaitļu atkārtotas pārveidošanas īpatnības dažās valodās.

## Kurš noslēpums?

| Avots                                                                                      | Noslēpums                                                                                                                           |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| [Tīmekļa āķa galapunkts](/lv/webhooks/endpoints) (`/v1/developer/webhook-endpoints`)       | Katram galapunktam paredzētais `secret` (48 heksadecimālās rakstzīmes), kas tiek atgriezts vienreiz izveides laikā                  |
| [Mantotais viena URL tīmekļa āķis](/api-reference/organizations#legacy-single-url-webhook) | Katras organizācijas `secret`, kas tiek atgriezts, izpildot `GET /v1/webhook`                                                       |
| [Rīka galapunkta izsaukums](/lv/tools/overview) (tiešs izsaukums uz jūsu `endpoint.url`)   | **Organizācijas līmeņa tīmekļa āķa noslēpums** (tas pats, kas mantotajam viena URL tīmekļa āķim) — nevis katra galapunkta noslēpums |

Glabājiet noslēpumu savā noslēpumu pārvaldniekā vai vides mainīgajā — nekad to neiekļaujiet repozitorijā.

## Atsauces realizācijas

Visas četras pārbauda neapstrādāto pieprasījuma pamattekstu:

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

## Integrācija konkrētiem ietvariem

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

## Rīku izsaukumu verificēšana

Kad balss aģents tieši izsauc kādu no jūsu
[funkciju rīkiem](/lv/tools/overview) (rīkam ir
`endpoint`), pieprasījumā līdzās jūsu konfigurētajiem
`endpoint.headers` tiek iekļautas divas ThunderPhone galvenes:

* `X-ThunderPhone-Call-ID` — aktīvā zvana cipariskā id.
* `X-ThunderPhone-Signature` — HMAC-SHA256, kurā kā atslēga izmantots jūsu
  **organizācijas līmeņa webhook noslēpums**, aprēķināts no precīzajiem
  pieprasījuma pamatteksta baitiem.

Tas pats `verify()` palīgs darbojas bez izmaiņām, ar divām niansēm:

1. **`GET` / `DELETE` rīkiem nav pamatteksta.** Argumenti tiek nodoti kā
   vaicājuma parametri, un paraksts tiek aprēķināts no **tukšas baitu
   virknes** — tātad `verify(b"", sig, secret)` (Python) vai
   `verify(Buffer.alloc(0), sig, secret)` (Node). **Neveidojiet** jaucējvērtību
   no vaicājuma virknes.
2. **Organizācijām bez konfigurēta mantotā webhook nav organizācijas noslēpuma.**
   Šādā gadījumā rīku izsaukumi ietver tikai `X-ThunderPhone-Call-ID`, bet ne
   paraksta galveni. Konfigurējiet mantoto webhook
   (`PUT /v1/webhook`), lai iegūtu parakstīšanas noslēpumu, vai autentificējiet
   rīku izsaukumus ar savu galveni, izmantojot `endpoint.headers`.

```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)
    ...
```

Webhook-**režīma** rīku nosūtīšana (rīki bez `endpoint`, kas tiek piegādāti
uz jūsu organizācijas webhook kā `telephony.tool` / `web.tool`) ir parasts
parakstīts webhook — piemērojiet iepriekš norādīto standarta metodi. Abus
pieprasījumu formātus skatiet sadaļā
[Funkciju rīki](/lv/tools/overview).

## Biežākās kļūdas

<AccordionGroup>
  <Accordion title="Atkārtota serializēšana ar noklusējuma formatējumu">
    Parsējot pamattekstu un atkārtoti serializējot to ar jūsu JSON bibliotēkas
    noklusējuma iestatījumiem (atstarpes pēc `,` / `:`, ievietošanas secībā sakārtotas atslēgas), tiek iegūti
    atšķirīgi baiti un HMAC nedarbojas. Pārbaudiet neapstrādāto pamattekstu — vai arī, ja tas ir atkārtoti jāserializē,
    precīzi atbilstiet mūsu kanoniskajai formai: sakārtotas atslēgas, kompakti atdalītāji, UTF-8.
  </Accordion>

  <Accordion title="Framework automātiski parsē JSON">
    Express `express.json()` starpprogrammatūra patērē pamatteksta plūsmu,
    un jūs zaudējat neapstrādātos baitus. Izmantojiet `express.raw()` tieši webhook
    maršrutā vai saglabājiet neapstrādāto pamattekstu buferī pirms starpprogrammatūras.
    Tas pats attiecas uz NestJS / Koa — skatiet to dokumentāciju par “raw body”.
  </Accordion>

  <Accordion title="Laika ziņā nedroša salīdzināšana">
    `expected === signature` JS vai `expected == signature`
    Python vidē ir salīdzināšana ar mainīgu izpildes laiku. Izmantojiet `crypto.timingSafeEqual`
    vai attiecīgi `hmac.compare_digest`. Veiktspējas atšķirības
    nav.
  </Accordion>

  <Accordion title="Nepareizs noslēpums rīku galapunktiem">
    Tiešie rīku galapunktu izsaukumi tiek parakstīti ar **organizācijas līmeņa webhook
    noslēpumu** (`GET /v1/webhook`) — nevis ar kādu atsevišķa galapunkta noslēpumu
    no `/v1/developer/webhook-endpoints`. Atkārtoti izmantojiet to pašu `verify()`
    funkciju, bet pārliecinieties, ka rīku maršrutos tai nododat organizācijas noslēpumu.
  </Accordion>

  <Accordion title="Vaicājuma virknes jaucējkodēšana GET/DELETE rīkos">
    Rīku metodēm bez pamatteksta paraksts aptver tukšo baitu
    virkni, saglabājot vienu universālu pieeju: veidojiet HMAC neapstrādātajam pieprasījuma pamattekstam,
    lai kāds tas būtu. URL vai vaicājuma virknes jaucējkods nekad nesakritīs.
  </Accordion>

  <Accordion title="401 neatgriešana neatbilstības gadījumā">
    Atgriežot 200, ja verifikācija neizdodas, apstrādātājs kļūst par atkārtotas atskaņošanas
    uzbrukuma mērķi. Ja verifikācija neizdodas, vienmēr atbildiet ar kodu, kas nav 2xx.
  </Accordion>
</AccordionGroup>

***

## Nākamās darbības

<CardGroup cols={2}>
  <Card title="Webhook pārskats" icon="bolt" href="/lv/webhooks/overview">
    Piegādes semantika, atkārtoti mēģinājumi, avota IP adreses.
  </Card>

  <Card title="Webhook galapunkti" icon="plug" href="/lv/webhooks/endpoints">
    Pārvaldiet vairākus URL, mainiet noslēpumus.
  </Card>

  <Card title="Funkciju rīki" icon="screwdriver-wrench" href="/lv/tools/overview">
    Divi rīku izsaukšanas ceļi un to pieprasījumu formāti.
  </Card>

  <Card title="Rīku integrācijas" icon="wrench" href="/lv/guides/build-tool-integration">
    Izveidojiet pilnīgu, uz rīkiem balstītu integrāciju no sākuma līdz beigām.
  </Card>
</CardGroup>
