> ## 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 imzalarını doğrulayın

> ThunderPhone'dan gelen her webhook ve araç isteği imzalanır. Bir kez doğrulayın; her yerde yeniden kullanın.

Sunucunuza gönderdiğimiz her istek — webhook teslimatları ve
araç uç noktası çağrıları — `X-ThunderPhone-Signature` başlığında bir
HMAC-SHA256 imzası taşır. Doğrulamayı bir kez doğru şekilde uygulayın ve
aynı yardımcıyı her işleyiciye ekleyin.

## Algoritma

1. **Ham** istek gövdesini okuyun — size POST ettiğimiz baytların tam hâlini.
2. `hmac_sha256(secret, body).hexdigest()` hesaplayın.
3. `X-ThunderPhone-Signature` ile **sabit zamanda** karşılaştırın.
   (Basit dize karşılaştırması zamanlama bilgilerini sızdırır.)

İlettiğimiz baytların tam olarak kendisini imzalarız; bu nedenle ham gövdeyi
doğrulamak her zaman çalışır. Bu baytlar aynı zamanda yükün **kanonik JSON serileştirmesidir** —
anahtarlar alfabetik olarak sıralanır, ayırıcılar sıkıştırılır
(boşluksuz `,` ve `:`), UTF-8 kullanılır. Bu, çerçeveniz yalnızca ayrıştırılmış JSON'u sunduğunda
size ikinci ve tamamen eşdeğer bir yöntem sağlar:
kanonik olarak yeniden serileştirin ve bunun HMAC'ini hesaplayın.

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

Ham gövdeyi tercih edin — bir adım daha azdır ve bazı dillerdeki JSON sayı
gidiş-dönüş dönüştürme tuhaflıklarına karşı dayanıklıdır.

## Hangi gizli anahtar?

| Kaynak                                                                                   | Gizli anahtar                                                                                                                  |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| [Webhook uç noktası](/tr/webhooks/endpoints) (`/v1/developer/webhook-endpoints`)         | Oluşturma sırasında bir kez döndürülen, uç nokta başına `secret` (48 onaltılık karakter)                                       |
| [Eski tek URL webhook'u](/api-reference/organizations#legacy-single-url-webhook)         | `GET /v1/webhook` isteğinde döndürülen, kuruluş başına `secret`                                                                |
| [Araç uç noktası çağrısı](/tr/tools/overview) (`endpoint.url` adresinize doğrudan çağrı) | **Kuruluş düzeyindeki webhook gizli anahtarı** (eski tek URL webhook'undakiyle aynı) — uç nokta başına bir gizli anahtar değil |

Gizli anahtarı gizli anahtar yöneticinizde veya ortam değişkeninde saklayın — asla commit etmeyin.

## Referans uygulamalar

Dördü de ham istek gövdesini doğrular:

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

## Çerçeveye özgü entegrasyon

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

## Araç çağrılarını doğrulama

Ajan, [fonksiyon araçlarınızdan](/tr/tools/overview) birini doğrudan
çağırdığında (aracın bir `endpoint` değeri olduğunda), istek,
yapılandırdığınız `endpoint.headers` ile birlikte iki ThunderPhone
başlığı taşır:

* `X-ThunderPhone-Call-ID` — canlı çağrının sayısal kimliği.
* `X-ThunderPhone-Signature` — tam istek gövdesi baytları üzerinden,
  **kuruluş düzeyindeki webhook gizli anahtarınız** ile anahtarlanmış
  HMAC-SHA256.

Aynı `verify()` yardımcısı, iki farkla değişmeden çalışır:

1. **`GET` / `DELETE` araçlarının gövdesi yoktur.** Bağımsız değişkenler
   sorgu parametreleri olarak iletilir ve imza **boş bayt dizisi**
   üzerinden hesaplanır — yani `verify(b"", sig, secret)` (Python) veya
   `verify(Buffer.alloc(0), sig, secret)` (Node). Sorgu dizesini
   karmalamayın.
2. **Eski webhook yapılandırması olmayan kuruluşların kuruluş gizli anahtarı yoktur.**
   Bu durumda araç çağrıları yalnızca `X-ThunderPhone-Call-ID` taşır ve
   imza başlığı içermez. İmzalama gizli anahtarı almak için eski webhook'u
   (`PUT /v1/webhook`) yapılandırın veya araç çağrılarını
   `endpoint.headers` üzerinden kendi başlığınızla doğrulayın.

```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-**modu** araç yönlendirmesi (`endpoint` değeri olmayan,
kuruluş webhook'unuza `telephony.tool` / `web.tool` olarak iletilen
araçlar) normal imzalı bir webhook'tur — yukarıdaki standart yöntem
geçerlidir. Her iki istek biçimi için [Fonksiyon Araçları](/tr/tools/overview)
sayfasına bakın.

## Yaygın sorunlar

<AccordionGroup>
  <Accordion title="Varsayılan biçimlendirmeyle yeniden serileştirme">
    Gövdeyi ayrıştırıp JSON kitaplığınızın varsayılanlarıyla yeniden
    dışa aktarmak (`,` / `:` sonrasında boşluklar, ekleme sırasına göre anahtarlar)
    farklı baytlar üretir ve HMAC'i bozar. Ham gövdeyi doğrulayın — veya
    yeniden serileştirmeniz gerekiyorsa kanonik biçimimizle tam olarak eşleşin:
    sıralanmış anahtarlar, sıkıştırılmış ayırıcılar, UTF-8.
  </Accordion>

  <Accordion title="Çerçeve JSON'u otomatik ayrıştırıyor">
    Express'in `express.json()` ara yazılımı gövde akışını tüketir
    ve ham baytları kaybedersiniz. Webhook rotasında özellikle `express.raw()` kullanın
    veya ham gövdeyi bir ön ara yazılımda arabelleğe alın.
    NestJS / Koa için de durum aynıdır — "raw body" belgelerini inceleyin.
  </Accordion>

  <Accordion title="Zamanlama açısından güvenli olmayan karşılaştırma">
    JS'de `expected === signature` veya Python'da `expected == signature`
    zamanlamaya bağlı karşılaştırmalardır. Sırasıyla `crypto.timingSafeEqual`
    veya `hmac.compare_digest` kullanın. Performans farkı
    yok denecek kadar azdır.
  </Accordion>

  <Accordion title="Araç uç noktaları için yanlış gizli anahtar">
    Doğrudan araç uç noktası çağrıları, **kuruluş düzeyindeki webhook
    gizli anahtarı** (`GET /v1/webhook`) ile imzalanır —
    `/v1/developer/webhook-endpoints` içindeki uç noktaya özel herhangi bir gizli anahtarla değil.
    Aynı `verify()` işlevini yeniden kullanın, ancak araç rotalarında
    kuruluş gizli anahtarını verdiğinizden emin olun.
  </Accordion>

  <Accordion title="GET/DELETE araçlarında sorgu dizesini karma işlemeden geçirmek">
    Gövdesi olmayan araç yöntemlerinde imza, boş bayt
    dizesini kapsar ve tek bir evrensel yaklaşım korunur: ne olursa olsun ham istek
    gövdesine HMAC uygulayın. URL'yi veya sorgu dizesini karma işlemden geçirmek hiçbir zaman eşleşmez.
  </Accordion>

  <Accordion title="Eşleşme olmadığında 401 döndürmemek">
    Doğrulama başarısız olduğunda 200 döndürmek, işleyiciyi bir yeniden oynatma
    hedefi hâline getirir. Doğrulama başarısız olursa her zaman 2xx dışında yanıt verin.
  </Accordion>
</AccordionGroup>

***

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="Webhook genel bakışı" icon="bolt" href="/tr/webhooks/overview">
    Teslimat anlambilimi, yeniden denemeler, kaynak IP'ler.
  </Card>

  <Card title="Webhook uç noktaları" icon="plug" href="/tr/webhooks/endpoints">
    Birden fazla URL'yi yönetin, gizli anahtarları döndürün.
  </Card>

  <Card title="İşlev Araçları" icon="screwdriver-wrench" href="/tr/tools/overview">
    İki araç çağırma yolu ve istek biçimleri.
  </Card>

  <Card title="Araç entegrasyonları" icon="wrench" href="/tr/guides/build-tool-integration">
    Araç destekli eksiksiz bir entegrasyonu uçtan uca oluşturun.
  </Card>
</CardGroup>
