---
title: "Patikrinkite žiniatinklio kabliukų parašus"
description: "Kiekviena ThunderPhone siunčiama žiniatinklio kabliuko ir įrankio užklausa yra pasirašyta. Vieną kartą patikrinkite parašą pagal čia pateiktą receptą, tada tą patį tikrinimą naudokite kiekviename vykdomame galiniame taške."
---

Kiekvienoje užklausoje, kurią siunčiame į jūsų serverį — webhook pristatymuose ir
įrankių galinių taškų iškvietimuose — antraštėje
`X-ThunderPhone-Signature` pateikiamas HMAC-SHA256 parašas. Vieną kartą
teisingai įgyvendinkite tikrinimą ir įtraukite tą patį pagalbinį metodą į kiekvieną apdorojimo funkciją.

## Algoritmas

1. Nuskaitykite **neapdorotą** užklausos turinį — tikslius baitus, kuriuos jums išsiuntėme POST užklausa.
2. Apskaičiuokite `hmac_sha256(secret, body).hexdigest()`.
3. Palyginkite **pastoviu laiku** su `X-ThunderPhone-Signature`.
   (Paprastas eilučių palyginimas atskleidžia laiko informaciją.)

Pasirašome būtent tuos baitus, kuriuos perduodame, todėl neapdoroto turinio
tikrinimas visada veikia. Šie baitai taip pat yra **kanoninis JSON serializavimas**
naudingojo krūvio — raktai surūšiuoti abėcėlės tvarka, glausti skirtukai
(`,` ir `:` be tarpų), UTF-8. Tai suteikia jums antrą, visiškai
lygiavertį būdą, kai jūsų sistema pateikia tik išanalizuotą JSON:
iš naujo kanoniškai serializuokite ir apskaičiuokite jo HMAC.

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

Pirmenybę teikite neapdorotam turiniui — tai vienu žingsniu mažiau ir jis
atsparus JSON skaičių pakartotinio konvertavimo ypatumams kai kuriose kalbose.

## Kurį slaptąjį raktą naudoti?

| Šaltinis | Slaptasis raktas |
|--------|--------|
| [Webhook galinis taškas](/lt/webhooks/endpoints) (`/v1/developer/webhook-endpoints`) | Kiekvieno galinio taško `secret` (48 šešioliktainiai simboliai), grąžinamas tik vieną kartą sukuriant |
| [Senas vieno URL webhook](/api-reference/organizations#legacy-single-url-webhook) | Organizacijos `secret`, grąžinamas naudojant `GET /v1/webhook` |
| [Įrankio galinio taško iškvietimas](/lt/tools/overview) (tiesioginis iškvietimas į jūsų `endpoint.url`) | **Organizacijos lygmens webhook slaptasis raktas** (tas pats, kaip seno vieno URL webhook) — ne atskiro galinio taško slaptasis raktas |

Saugokite slaptąjį raktą slaptųjų raktų tvarkytuvėje arba aplinkos kintamajame — niekada jo neįtraukite į versijų valdymą.

## Pavyzdinės įgyvendinimo versijos

Visos keturios tikrina neapdorotą užklausos turinį:

<CodeGroup>
```python Python
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
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
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
require "openssl"

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

## Konkrečioms sistemoms skirtas prijungimas

<CodeGroup>
```python FastAPI
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
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
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>

## Įrankių iškvietimų tikrinimas

Kai agentas tiesiogiai iškviečia vieną iš jūsų
[funkcijų įrankių](/lt/tools/overview) (įrankis turi
`endpoint`), užklausoje kartu su jūsų sukonfigūruotais `endpoint.headers`
pateikiamos dvi ThunderPhone antraštės:

- `X-ThunderPhone-Call-ID` — vykstančio skambučio skaitinis identifikatorius.
- `X-ThunderPhone-Signature` — HMAC-SHA256, kurio raktas yra jūsų
  **organizacijos lygio žiniatinklio kabliuko paslaptis**, apskaičiuotas pagal
  tikslius užklausos turinio baitus.

Tas pats `verify()` pagalbinis metodas veikia be pakeitimų, tačiau yra du niuansai:

1. **`GET` / `DELETE` įrankiai neturi turinio.** Argumentai perduodami kaip užklausos
   parametrai, o parašas apskaičiuojamas pagal **tuščią baitų eilutę** — todėl
   `verify(b"", sig, secret)` (Python) arba
   `verify(Buffer.alloc(0), sig, secret)` (Node). **Neskaičiuokite** užklausos
   eilutės maišos.
2. **Organizacijos, kuriose nesukonfigūruotas ankstesnysis žiniatinklio kabliukas, neturi organizacijos paslapties.** Tokiu
   atveju įrankių iškvietimuose pateikiama tik `X-ThunderPhone-Call-ID`, bet nėra
   parašo antraštės. Sukonfigūruokite ankstesnįjį žiniatinklio kabliuką
   (`PUT /v1/webhook`), kad gautumėte pasirašymo paslaptį, arba autentifikuokite
   įrankių iškvietimus naudodami savo antraštę per `endpoint.headers`.

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

Įrankių iškvietimų nukreipimas žiniatinklio kabliuko **režimu** (įrankiai be `endpoint`, pateikiami
į jūsų organizacijos žiniatinklio kabliuką kaip `telephony.tool` / `web.tool`) yra įprastas
pasirašytas žiniatinklio kabliukas — taikomas pirmiau pateiktas standartinis būdas. Abiejų
užklausų formų aprašą rasite [Funkcijų įrankiuose](/lt/tools/overview).

## Dažnos klaidos

<AccordionGroup>
  <Accordion title="Pakartotinis serializavimas naudojant numatytąjį formatavimą">
    Išanalizavus turinį ir vėl jį išvedus naudojant numatytuosius JSON bibliotekos
    nustatymus (tarpai po `,` / `:`, raktai įterpimo tvarka), gaunami
    kiti baitai ir HMAC nebeveikia. Tikrinkite neapdorotą turinį — arba, jei
    turite jį pakartotinai serializuoti, tiksliai atkartokite mūsų kanoninę formą:
    surikiuoti raktai, kompaktiški skirtukai, UTF-8.
  </Accordion>

  <Accordion title="Karkasas automatiškai analizuoja JSON">
    Express `express.json()` tarpinė programinė įranga sunaudoja turinio srautą,
    todėl prarandate neapdorotus baitus. Konkrečiai webhook maršrutui naudokite
    `express.raw()` arba išsaugokite neapdorotą turinį prieš tarpinę programinę įrangą.
    Tas pats taikoma NestJS / Koa — peržiūrėkite jų „neapdoroto turinio“ dokumentaciją.
  </Accordion>

  <Accordion title="Nesaugus palyginimas pagal vykdymo laiką">
    `expected === signature` JS kalboje arba `expected == signature`
    Python kalboje yra nuo vykdymo laiko priklausantys palyginimai. Atitinkamai naudokite `crypto.timingSafeEqual`
    arba `hmac.compare_digest`. Našumo skirtumo nėra.
  </Accordion>

  <Accordion title="Neteisinga paslaptis įrankių galiniams taškams">
    Tiesioginiai įrankių galinių taškų iškvietimai pasirašomi naudojant **organizacijos lygio webhook
    paslaptį** (`GET /v1/webhook`) — ne jokia konkretaus galinio taško paslaptimi
    iš `/v1/developer/webhook-endpoints`. Pakartotinai naudokite tą pačią `verify()`
    funkciją, tačiau įsitikinkite, kad įrankių maršrutuose jai perduodate organizacijos paslaptį.
  </Accordion>

  <Accordion title="Užklausos eilutės maišos skaičiavimas GET/DELETE įrankiams">
    Įrankių metodams be turinio parašas apima tuščią baitų
    eilutę, todėl išlieka vienas universalus būdas: apskaičiuokite HMAC pagal neapdorotą užklausos turinį,
    kad ir koks jis būtų. URL arba užklausos eilutės maiša niekada nesutaps.
  </Accordion>

  <Accordion title="Negrąžinamas 401 neatitikimo atveju">
    Grąžinus 200, kai patikra nepavyksta, apdorojimo funkcija tampa pakartotinio siuntimo
    atakų taikiniu. Jei patikra nepavyksta, visada atsakykite ne 2xx kodu.
  </Accordion>
</AccordionGroup>

---

## Tolesni veiksmai

<CardGroup cols={2}>
  <Card title="Webhook apžvalga" icon="bolt" href="/lt/webhooks/overview">
    Pristatymo semantika, pakartotiniai bandymai, šaltinio IP adresai.
  </Card>
  <Card title="Webhook galiniai taškai" icon="plug" href="/lt/webhooks/endpoints">
    Tvarkykite kelis URL, keiskite paslaptis.
  </Card>
  <Card title="Funkcijų įrankiai" icon="screwdriver-wrench" href="/lt/tools/overview">
    Du įrankių iškvietimo keliai ir jų užklausų formatai.
  </Card>
  <Card title="Įrankių integracijos" icon="wrench" href="/lt/guides/build-tool-integration">
    Sukurkite visapusišką integraciją, paremtą įrankiais.
  </Card>
</CardGroup>
