---
title: "Thibitisha sahihi za webhook"
description: "Kila ombi la webhook na zana linalotumwa na ThunderPhone husainiwa. Thibitisha sahihi mara moja kwa kutumia utaratibu ulio hapa, kisha tumia tena ukaguzi huo huo kwenye kila endpoint unayoendesha."
---

Kila ombi tunalotuma kwa seva yako — uwasilishaji wa webhook na
uitishaji wa endpoint za zana — hubeba sahihi ya HMAC-SHA256 katika
kichwa cha `X-ThunderPhone-Signature`. Fanya uthibitishaji kwa usahihi mara moja na
utumie kisadizi hichohicho katika kila kidhibiti.

## Algoriti

1. Soma mwili **ghafi** wa ombi — baiti kamili tulizotuma kwako kwa POST.
2. Kokotoa `hmac_sha256(secret, body).hexdigest()`.
3. Linganisha kwa **muda usiobadilika** na `X-ThunderPhone-Signature`.
   (Ulinganishaji wa kawaida wa mifuatano huvuja taarifa za muda.)

Tunatia sahihi baiti kamili tunazotuma, kwa hivyo kuthibitisha mwili ghafi
hufanya kazi kila wakati. Baiti hizo pia ni **uundaji sanifu wa JSON**
wa mzigo — funguo zimepangwa kialfabeti, vitenganishi vifupi
(`,` na `:` bila nafasi), UTF-8. Hilo hukupa mbinu ya pili iliyo sawa
kabisa wakati fremu yako hutoa JSON iliyochanganuliwa pekee:
unda upya kwa muundo sanifu kisha utumie HMAC kwa huo.

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

Pendelea mwili ghafi — ni hatua moja pungufu na hauathiriwi na
changamoto za ubadilishaji wa namba za JSON kwenda na kurudi katika baadhi ya lugha.

## Siri ipi?

| Chanzo | Siri |
|--------|--------|
| [Endpoint ya webhook](/sw/webhooks/endpoints) (`/v1/developer/webhook-endpoints`) | `secret` ya kila endpoint (herufi 48 za heksadesimali) inayorejeshwa mara moja wakati wa kuunda |
| [Webhook ya zamani ya URL moja](/api-reference/organizations#legacy-single-url-webhook) | `secret` ya kila shirika inayorejeshwa kwenye `GET /v1/webhook` |
| [Uitishaji wa endpoint ya zana](/sw/tools/overview) (mwito wa moja kwa moja kwa `endpoint.url` yako) | **Siri ya webhook ya kiwango cha shirika** (sawa na ile ya webhook ya zamani ya URL moja) — si siri ya kila endpoint |

Hifadhi siri katika kidhibiti chako cha siri au kigeu cha mazingira — usiiweke kamwe kwenye commit.

## Utekelezaji wa marejeleo

Zote nne huthibitisha mwili ghafi wa ombi:

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

## Uunganishaji mahususi wa framework

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

## Kuthibitisha miito ya zana

Ejenti inapoitisha moja kwa moja mojawapo ya
[zana zako za function](/sw/tools/overview) (zana ina
`endpoint`), ombi hubeba header mbili za ThunderPhone pamoja na
`endpoint.headers` ulizosanidi:

- `X-ThunderPhone-Call-ID` — kitambulisho cha nambari cha simu inayoendelea.
- `X-ThunderPhone-Signature` — HMAC-SHA256, yenye ufunguo wa
  **siri ya webhook ya kiwango cha shirika**, juu ya baiti halisi za
  mwili wa ombi.

Kisaidizi kilekile cha `verify()` hufanya kazi bila mabadiliko, kwa mambo mawili ya kuzingatia:

1. **Zana za `GET` / `DELETE` hazina mwili.** Hoja hupitishwa kama vigezo vya query,
   na sahihi huhesabiwa juu ya **mfuatano tupu wa baiti**
   — kwa hivyo `verify(b"", sig, secret)` (Python) au
   `verify(Buffer.alloc(0), sig, secret)` (Node). **Usihesabu hash ya**
   mfuatano wa query.
2. **Mashirika yasiyo na webhook ya zamani iliyosanidiwa hayana siri ya shirika.** Katika
   hali hiyo miito ya zana hubeba `X-ThunderPhone-Call-ID` pekee na haina
   header ya sahihi. Sanidi webhook ya zamani
   (`PUT /v1/webhook`) ili kupata siri ya kutia sahihi, au thibitisha miito ya zana
   kwa header yako mwenyewe kupitia `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)
    ...
```

Usambazaji wa zana katika **hali ya webhook** (zana zisizo na `endpoint`, zinazoletwa
kwenye webhook ya shirika lako kama `telephony.tool` / `web.tool`) ni webhook ya kawaida
iliyotiwa sahihi — utaratibu wa kawaida hapo juu unatumika. Tazama
[Zana za Function](/sw/tools/overview) kwa miundo yote miwili ya ombi.

## Changamoto za kawaida

<AccordionGroup>
  <Accordion title="Kusajili upya kwa uumbizaji chaguo-msingi">
    Kuchanganua mwili na kuutoa tena kwa chaguo-msingi za maktaba yako ya JSON
    (nafasi baada ya `,` / `:`, funguo zilizo kwa mpangilio wa uingizaji) huzalisha
    baiti tofauti na kuharibu HMAC. Thibitisha mwili ghafi — au ikiwa ni lazima
    uusajili upya, linganisha muundo wetu wa kanoni hasa: funguo zilizopangwa,
    vitenganishi fupi, UTF-8.
  </Accordion>

  <Accordion title="Framework huchanganua JSON kiotomatiki">
    Middleware ya `express.json()` ya Express hutumia mtiririko wa mwili
    na unapoteza baiti ghafi. Tumia `express.raw()` mahsusi kwenye njia ya webhook,
    au hifadhi mwili ghafi kwenye pre-middleware.
    Hali ni sawa kwa NestJS / Koa — angalia nyaraka zao za "raw body".
  </Accordion>

  <Accordion title="Ulinganisho usio salama kwa muda">
    `expected === signature` katika JS au `expected == signature` katika
    Python ni ulinganisho unaobadilika kulingana na muda. Tumia `crypto.timingSafeEqual`
    au `hmac.compare_digest` mtawalia. Tofauti ya utendaji haipo.
  </Accordion>

  <Accordion title="Siri isiyo sahihi kwa endpoint za zana">
    Miito ya moja kwa moja ya endpoint za zana husainiwa kwa **siri ya webhook
    ya kiwango cha shirika** (`GET /v1/webhook`) — si kwa siri yoyote ya kila endpoint
    kutoka `/v1/developer/webhook-endpoints`. Tumia tena kitendakazi kilekile cha `verify()`,
    lakini hakikisha unakipa siri ya shirika kwenye njia za zana.
  </Accordion>

  <Accordion title="Kuheshi mfuatano wa hoja kwenye zana za GET/DELETE">
    Kwa mbinu za zana zisizo na mwili, sahihi hufunika mfuatano tupu wa baiti,
    ikidumisha utaratibu mmoja wa jumla: HMAC mwili ghafi wa ombi,
    wowote ulivyo. Kuheshi URL au mfuatano wa hoja hakutalingana kamwe.
  </Accordion>

  <Accordion title="Kutorejesha 401 kunapokuwa na kutolingana">
    Kurejesha 200 uthibitishaji unaposhindwa hufanya kishughulikiaji kuwa
    lengo la mashambulizi ya kurudia ombi. Daima jibu kwa hali isiyo ya 2xx
    uthibitishaji unaposhindwa.
  </Accordion>
</AccordionGroup>

---

## Hatua zinazofuata

<CardGroup cols={2}>
  <Card title="Muhtasari wa webhook" icon="bolt" href="/sw/webhooks/overview">
    Semantiki za uwasilishaji, majaribio tena, anwani za IP za chanzo.
  </Card>
  <Card title="Endpoint za webhook" icon="plug" href="/sw/webhooks/endpoints">
    Dhibiti URL nyingi, badilisha siri.
  </Card>
  <Card title="Zana za vitendaji" icon="screwdriver-wrench" href="/sw/tools/overview">
    Njia mbili za kuita zana na miundo yake ya maombi.
  </Card>
  <Card title="Miunganisho ya zana" icon="wrench" href="/sw/guides/build-tool-integration">
    Jenga muunganisho kamili unaotumia zana kutoka mwanzo hadi mwisho.
  </Card>
</CardGroup>
