---
title: "वेबहुक स्वाक्षऱ्या सत्यापित करा"
description: "ThunderPhone पाठवत असलेल्या प्रत्येक वेबहुक आणि टूल विनंतीवर स्वाक्षरी केलेली असते. येथे दिलेल्या पद्धतीने स्वाक्षरी एकदा सत्यापित करा, त्यानंतर तुम्ही चालवत असलेल्या प्रत्येक एंडपॉइंटवर तीच तपासणी पुन्हा वापरा."
---

आम्ही तुमच्या सर्व्हरला पाठवणाऱ्या प्रत्येक विनंतीमध्ये — webhook वितरणे आणि
टूल-एंडपॉइंट इनव्होकेशन्समध्ये — `X-ThunderPhone-Signature` हेडरमध्ये
HMAC-SHA256 स्वाक्षरी असते. पडताळणी एकदाच योग्यरीत्या करा आणि
प्रत्येक हँडलरमध्ये तोच हेल्पर वापरा.

## अल्गोरिदम

1. **कच्चा** विनंती बॉडी वाचा — आम्ही तुम्हाला POST केलेले अचूक बाइट्स.
2. `hmac_sha256(secret, body).hexdigest()` गणना करा.
3. `X-ThunderPhone-Signature` शी **स्थिर वेळेत** तुलना करा.
   (साधी स्ट्रिंग तुलना वेळेची माहिती उघड करते.)

आम्ही पाठवतो तेच अचूक बाइट्स साइन करतो, त्यामुळे कच्च्या बॉडीची पडताळणी
नेहमी कार्य करते. हे बाइट्स पेलोडचे **प्रामाणिक JSON सीरियलायझेशन**
देखील आहेत — की वर्णानुक्रमाने क्रमबद्ध, कॉम्पॅक्ट विभाजक
(रिकाम्या जागांशिवाय `,` आणि `:`), UTF-8. तुमचे फ्रेमवर्क फक्त पार्स केलेला JSON
उपलब्ध करून देत असल्यास, यामुळे तुम्हाला दुसरी, पूर्णपणे समतुल्य पद्धत मिळते:
प्रामाणिक पद्धतीने पुन्हा सीरियलाइज करा आणि त्याचे HMAC करा.

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

कच्च्या बॉडीला प्राधान्य द्या — हा एक कमी टप्पा आहे आणि काही भाषांमधील JSON संख्या
राउंड-ट्रिपिंगच्या वैशिष्ट्यांपासून सुरक्षित आहे.

## कोणते गुप्त की?

| स्रोत | गुप्त की |
|--------|--------|
| [Webhook एंडपॉइंट](/mr/webhooks/endpoints) (`/v1/developer/webhook-endpoints`) | तयार करताना एकदाच परत मिळणारे प्रत्येक एंडपॉइंटसाठीचे `secret` (48 हेक्स अक्षरे) |
| [लेगसी सिंगल-URL webhook](/api-reference/organizations#legacy-single-url-webhook) | `GET /v1/webhook` वर परत मिळणारे प्रत्येक ऑर्गनायझेशनसाठीचे `secret` |
| [टूल-एंडपॉइंट इनव्होकेशन](/mr/tools/overview) (तुमच्या `endpoint.url` ला थेट कॉल) | **ऑर्गनायझेशन-स्तरीय webhook गुप्त की** (लेगसी सिंगल-URL webhook प्रमाणेच) — प्रत्येक एंडपॉइंटची गुप्त की नाही |

गुप्त की तुमच्या सीक्रेट मॅनेजरमध्ये किंवा env var मध्ये साठवा — ती कधीही कमिट करू नका.

## संदर्भ अंमलबजावण्या

सर्व चार कच्च्या विनंती बॉडीची पडताळणी करतात:

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

## फ्रेमवर्क-विशिष्ट जोडणी

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

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

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

- `X-ThunderPhone-Call-ID` — चालू कॉलचा संख्यात्मक आयडी.
- `X-ThunderPhone-Signature` — अचूक विनंती-बॉडी बाइट्सवर तुमच्या
  **संस्था-स्तरीय webhook गुपिताने** की केलेला HMAC-SHA256.

तोच `verify()` हेल्पर कोणताही बदल न करता कार्य करतो, पण दोन बाबी लक्षात घ्या:

1. **`GET` / `DELETE` टूल्सना बॉडी नसते.** आर्ग्युमेंट्स क्वेरी
   पॅरामीटर्स म्हणून जातात आणि स्वाक्षरीची गणना **रिकाम्या बाइट
   स्ट्रिंगवर** केली जाते — म्हणजे `verify(b"", sig, secret)` (Python) किंवा
   `verify(Buffer.alloc(0), sig, secret)` (Node). क्वेरी स्ट्रिंग हॅश करू
   नका.
2. **लेगसी webhook कॉन्फिगर नसलेल्या संस्थांकडे संस्था गुपित नसते.** अशा
   परिस्थितीत टूल कॉल्समध्ये फक्त `X-ThunderPhone-Call-ID` असतो आणि
   स्वाक्षरी हेडर नसतो. स्वाक्षरीकरण गुपित मिळवण्यासाठी लेगसी webhook
   (`PUT /v1/webhook`) कॉन्फिगर करा किंवा `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)
    ...
```

Webhook-**मोड** टूल डिस्पॅच (`endpoint` नसलेली आणि तुमच्या संस्था webhook वर
`telephony.tool` / `web.tool` म्हणून वितरित होणारी टूल्स) हा सामान्य स्वाक्षरीत
webhook आहे — वरील मानक पद्धत लागू होते. दोन्ही विनंती स्वरूपांसाठी
[फंक्शन टूल्स](/mr/tools/overview) पहा.

## सामान्य अडचणी

<AccordionGroup>
  <Accordion title="डीफॉल्ट स्वरूपनासह पुन्हा-सीरियलाइझ करणे">
    बॉडी पार्स करून ती तुमच्या JSON लायब्ररीच्या डीफॉल्ट सेटिंग्जसह
    पुन्हा डंप केल्यास (`,` / `:` नंतर स्पेस, समाविष्ट करण्याच्या क्रमातील की)
    वेगळे बाइट्स तयार होतात आणि HMAC अयशस्वी होते. रॉ बॉडी सत्यापित करा — किंवा
    तुम्हाला पुन्हा-सीरियलाइझ करणे आवश्यक असल्यास, आमच्या कॅनॉनिकल स्वरूपाशी अचूक जुळवा:
    सॉर्ट केलेल्या की, कॉम्पॅक्ट सेपरेटर, UTF-8.
  </Accordion>

  <Accordion title="फ्रेमवर्क JSON आपोआप पार्स करते">
    Express चे `express.json()` मिडलवेअर बॉडी स्ट्रीम वापरून टाकते
    आणि रॉ बाइट्स गमावले जातात. वेबहुक रूटवर विशेषतः `express.raw()` वापरा,
    किंवा प्री-मिडलवेअरमध्ये रॉ बॉडी बफर करा.
    NestJS / Koa साठीही तेच लागू होते — त्यांचे "रॉ बॉडी" दस्तऐवज तपासा.
  </Accordion>

  <Accordion title="टायमिंगसाठी असुरक्षित तुलना">
    JS मधील `expected === signature` किंवा Python मधील `expected == signature`
    या टायमिंग-व्हेरिएबल तुलना आहेत. अनुक्रमे `crypto.timingSafeEqual`
    किंवा `hmac.compare_digest` वापरा. कार्यक्षमतेतील फरक
    नगण्य आहे.
  </Accordion>

  <Accordion title="टूल एंडपॉइंट्ससाठी चुकीचा सीक्रेट">
    थेट टूल-एंडपॉइंट कॉल्सवर **org-स्तरीय वेबहुक
    सीक्रेट** (`GET /v1/webhook`) सह साइन केले जाते — 
    `/v1/developer/webhook-endpoints` मधील कोणत्याही प्रति-एंडपॉइंट सीक्रेटसह नाही. तीच `verify()`
    फंक्शन पुन्हा वापरा, पण टूल रूट्सवर तुम्ही त्याला org सीक्रेट देता याची खात्री करा.
  </Accordion>

  <Accordion title="GET/DELETE टूल्सवरील क्वेरी स्ट्रिंग हॅश करणे">
    बॉडी नसलेल्या टूल मेथड्ससाठी सिग्नेचर रिकाम्या बाइट
    स्ट्रिंगला कव्हर करते, त्यामुळे एकच सार्वत्रिक पद्धत कायम राहते: रॉ रिक्वेस्ट बॉडीचे HMAC करा,
    ती काहीही असो. URL किंवा क्वेरी स्ट्रिंग हॅश केल्यास ते कधीही जुळणार नाही.
  </Accordion>

  <Accordion title="न जुळल्यास 401 परत न करणे">
    सत्यापन अयशस्वी झाल्यावर 200 परत केल्यास हँडलर रिप्ले
    टार्गेट बनतो. सत्यापन अयशस्वी झाल्यास नेहमी non-2xx प्रतिसाद द्या.
  </Accordion>
</AccordionGroup>

---

## पुढील पायऱ्या

<CardGroup cols={2}>
  <Card title="वेबहुक विहंगावलोकन" icon="bolt" href="/mr/webhooks/overview">
    वितरण अर्थविज्ञान, पुनर्प्रयत्न, स्रोत IP.
  </Card>
  <Card title="वेबहुक एंडपॉइंट्स" icon="plug" href="/mr/webhooks/endpoints">
    अनेक URL व्यवस्थापित करा, सीक्रेट्स रोटेट करा.
  </Card>
  <Card title="फंक्शन टूल्स" icon="screwdriver-wrench" href="/mr/tools/overview">
    टूल-इनव्होकेशनचे दोन मार्ग आणि त्यांचे रिक्वेस्ट स्वरूप.
  </Card>
  <Card title="टूल इंटिग्रेशन्स" icon="wrench" href="/mr/guides/build-tool-integration">
    सुरुवातीपासून शेवटपर्यंत संपूर्ण टूल-आधारित इंटिग्रेशन तयार करा.
  </Card>
</CardGroup>
