> ## 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 signatures सत्यापित करें

> ThunderPhone से आने वाले हर webhook और tool अनुरोध पर हस्ताक्षर होते हैं। एक बार सत्यापित करें; हर जगह पुन: उपयोग करें।

हम आपके सर्वर को भेजने वाले हर रिक्वेस्ट — webhook डिलीवरी और
tool-endpoint इनवोकेशन — में
`X-ThunderPhone-Signature` हेडर में एक HMAC-SHA256 सिग्नेचर होता है। वेरिफिकेशन को एक बार सही करें और
उसी हेल्पर को हर हैंडलर में प्लग करें।

## एल्गोरिदम

1. **रॉ** रिक्वेस्ट बॉडी पढ़ें — वे सटीक बाइट्स जिन्हें हमने आपको POST किया था।
2. `hmac_sha256(secret, body).hexdigest()` कंप्यूट करें।
3. `X-ThunderPhone-Signature` के साथ **कॉन्स्टेंट टाइम** में तुलना करें।
   (साधारण स्ट्रिंग तुलना टाइमिंग जानकारी लीक करती है।)

हम ठीक उन्हीं बाइट्स पर साइन करते हैं जिन्हें हम ट्रांसमिट करते हैं, इसलिए रॉ बॉडी को
वेरिफाई करना हमेशा काम करता है। वे बाइट्स payload का **कैनॉनिकल JSON सीरियलाइज़ेशन**
भी हैं — कुंजियाँ वर्णानुक्रम में सॉर्ट की गई, कॉम्पैक्ट सेपरेटर
(बिना स्पेस के `,` और `:`), UTF-8। इससे आपको दूसरा, पूरी तरह
समकक्ष तरीका मिलता है जब आपका फ्रेमवर्क केवल पार्स किया हुआ JSON एक्सपोज़ करता है:
कैनॉनिकली री-सीरियलाइज़ करें और उसका HMAC करें।

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

रॉ बॉडी को प्राथमिकता दें — यह एक स्टेप कम है और कुछ भाषाओं में JSON नंबर
राउंड-ट्रिपिंग की विचित्रताओं से सुरक्षित है।

## कौन-सा सीक्रेट?

| स्रोत                                                                                | सीक्रेट                                                                                              |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| [Webhook endpoint](/hi/webhooks/endpoints) (`/v1/developer/webhook-endpoints`)       | हर endpoint का `secret` (48 hex कैरेक्टर), जो create करते समय एक बार लौटाया जाता है                  |
| [लीगेसी single-URL webhook](/api-reference/organizations#legacy-single-url-webhook)  | हर संगठन का `secret`, जो `GET /v1/webhook` पर लौटाया जाता है                                         |
| [Tool-endpoint invocation](/hi/tools/overview) (आपके `endpoint.url` पर डायरेक्ट कॉल) | **संगठन-स्तरीय webhook सीक्रेट** (लीगेसी single-URL webhook वाला ही) — endpoint-विशिष्ट सीक्रेट नहीं |

सीक्रेट को अपने सीक्रेट मैनेजर या env var में स्टोर करें — इसे कभी commit न करें।

## रेफरेंस इम्प्लीमेंटेशन

चारों रॉ रिक्वेस्ट बॉडी को वेरिफाई करते हैं:

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

## फ़्रेमवर्क-विशिष्ट वायरिंग

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

## टूल कॉल सत्यापित करना

जब एजेंट आपके किसी [फ़ंक्शन टूल](/hi/tools/overview) को सीधे इनवोक करता है
(टूल में `endpoint` होता है), तो अनुरोध में आपके कॉन्फ़िगर किए गए
`endpoint.headers` के साथ दो ThunderPhone हेडर होते हैं:

* `X-ThunderPhone-Call-ID` — लाइव कॉल की न्यूमेरिक id।
* `X-ThunderPhone-Signature` — सटीक रिक्वेस्ट-बॉडी बाइट्स पर आपके
  **org-level webhook secret** से की गई HMAC-SHA256।

वही `verify()` हेल्पर बिना बदलाव के काम करता है, दो अंतर के साथ:

1. **`GET` / `DELETE` टूल में कोई बॉडी नहीं होती।** आर्ग्यूमेंट्स क्वेरी
   पैरामीटर्स के रूप में जाते हैं, और सिग्नेचर की गणना **खाली बाइट
   स्ट्रिंग** पर होती है — इसलिए `verify(b"", sig, secret)` (Python) या
   `verify(Buffer.alloc(0), sig, secret)` (Node) का उपयोग करें। क्वेरी
   स्ट्रिंग को हैश **न करें**।
2. **जिन संगठनों में लेगेसी webhook कॉन्फ़िगर नहीं है, उनमें org secret नहीं होता।**
   उस स्थिति में टूल कॉल में केवल `X-ThunderPhone-Call-ID` होता है और कोई
   सिग्नेचर हेडर नहीं होता। साइनिंग secret पाने के लिए लेगेसी webhook
   (`PUT /v1/webhook`) कॉन्फ़िगर करें, या `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-**मोड** टूल डिस्पैच (`endpoint` के बिना टूल, जो आपके org webhook पर
`telephony.tool` / `web.tool` के रूप में डिलीवर होते हैं) एक सामान्य साइन किया
हुआ webhook है — ऊपर दी गई स्टैंडर्ड रेसिपी लागू होती है। दोनों रिक्वेस्ट
शेप के लिए [फ़ंक्शन टूल्स](/hi/tools/overview) देखें।

## सामान्य समस्याएँ

<AccordionGroup>
  <Accordion title="डिफ़ॉल्ट फ़ॉर्मैटिंग के साथ दोबारा सीरियलाइज़ करना">
    बॉडी को पार्स करके अपनी JSON लाइब्रेरी की डिफ़ॉल्ट सेटिंग्स के साथ
    दोबारा डंप करने पर (`,` / `:` के बाद स्पेस, इंसर्शन-ऑर्डर किए गए कीज़) अलग
    बाइट्स बनते हैं और HMAC टूट जाता है। रॉ बॉडी को वेरिफ़ाई करें — या यदि
    आपको दोबारा सीरियलाइज़ करना ही है, तो हमारे कैनॉनिकल फ़ॉर्म से बिल्कुल मिलान करें: सॉर्ट किए गए
    कीज़, कॉम्पैक्ट सेपरेटर्स, UTF-8।
  </Accordion>

  <Accordion title="फ़्रेमवर्क JSON को अपने-आप पार्स करता है">
    Express का `express.json()` मिडलवेयर बॉडी स्ट्रीम को कंज़्यूम कर लेता है
    और रॉ बाइट्स खो जाते हैं। वेबहुक रूट पर विशेष रूप से `express.raw()` का उपयोग करें,
    या प्री-मिडलवेयर में रॉ बॉडी को बफ़र करें।
    NestJS / Koa के लिए भी यही बात लागू होती है — उनके "raw body" डॉक्यूमेंटेशन देखें।
  </Accordion>

  <Accordion title="टाइमिंग-असुरक्षित तुलना">
    JS में `expected === signature` या
    Python में `expected == signature` टाइमिंग-वेरिएबल तुलनाएँ हैं। क्रमशः `crypto.timingSafeEqual`
    या `hmac.compare_digest` का उपयोग करें। परफ़ॉर्मेंस का अंतर
    शून्य है।
  </Accordion>

  <Accordion title="टूल एंडपॉइंट्स के लिए गलत सीक्रेट">
    डायरेक्ट टूल-एंडपॉइंट कॉल्स पर **संगठन-स्तरीय वेबहुक
    सीक्रेट** (`GET /v1/webhook`) से साइन किया जाता है — न कि
    `/v1/developer/webhook-endpoints` के किसी प्रति-एंडपॉइंट सीक्रेट से। वही `verify()`
    फ़ंक्शन दोबारा उपयोग करें, लेकिन सुनिश्चित करें कि टूल रूट्स पर आप उसे संगठन सीक्रेट दें।
  </Accordion>

  <Accordion title="GET/DELETE टूल्स पर क्वेरी स्ट्रिंग को हैश करना">
    बिना बॉडी वाले टूल मेथड्स के लिए सिग्नेचर खाली बाइट
    स्ट्रिंग को कवर करता है, जिससे एक यूनिवर्सल रेसिपी बनी रहती है: रॉ रिक्वेस्ट बॉडी का HMAC करें,
    चाहे वह जो भी हो। URL या क्वेरी स्ट्रिंग को हैश करने पर कभी मिलान नहीं होगा।
  </Accordion>

  <Accordion title="मिसमैच पर 401 रिटर्न न करना">
    असफल वेरिफ़िकेशन पर 200 रिटर्न करने से हैंडलर रीप्ले
    टारगेट बन जाता है। वेरिफ़िकेशन विफल होने पर हमेशा non-2xx रिस्पॉन्स दें।
  </Accordion>
</AccordionGroup>

***

## अगले चरण

<CardGroup cols={2}>
  <Card title="वेबहुक ओवरव्यू" icon="bolt" href="/hi/webhooks/overview">
    डिलीवरी सेमांटिक्स, रिट्राइज़, सोर्स IPs।
  </Card>

  <Card title="वेबहुक एंडपॉइंट्स" icon="plug" href="/hi/webhooks/endpoints">
    कई URLs मैनेज करें, सीक्रेट्स रोटेट करें।
  </Card>

  <Card title="फ़ंक्शन टूल्स" icon="screwdriver-wrench" href="/hi/tools/overview">
    दो टूल-इनवोकेशन पाथ्स और उनके रिक्वेस्ट शेप्स।
  </Card>

  <Card title="टूल इंटीग्रेशंस" icon="wrench" href="/hi/guides/build-tool-integration">
    शुरू से अंत तक एक पूर्ण टूल-आधारित इंटीग्रेशन बनाएँ।
  </Card>
</CardGroup>
