---
title: "വെബ്ഹുക്ക് സിഗ്നേച്ചറുകൾ പരിശോധിക്കുക"
description: "ThunderPhone അയയ്ക്കുന്ന എല്ലാ വെബ്ഹുക്ക്, ടൂൾ അഭ്യർത്ഥനകളും സൈൻ ചെയ്തതാണ്. ഇവിടെ നൽകിയിരിക്കുന്ന റെസിപ്പി ഉപയോഗിച്ച് ഒരിക്കൽ സിഗ്നേച്ചർ പരിശോധിക്കുക, തുടർന്ന് നിങ്ങൾ പ്രവർത്തിപ്പിക്കുന്ന എല്ലാ എൻഡ്പോയിന്റുകളിലും അതേ പരിശോധന വീണ്ടും ഉപയോഗിക്കുക."
---

നിങ്ങളുടെ സെർവറിലേക്ക് ഞങ്ങൾ അയയ്ക്കുന്ന ഓരോ അഭ്യർത്ഥനയ്ക്കും — webhook ഡെലിവറികൾക്കും
tool-endpoint ഇൻവോക്കേഷനുകൾക്കും — `X-ThunderPhone-Signature` ഹെഡറിൽ ഒരു HMAC-SHA256 ഒപ്പുണ്ടാകും. ഒരിക്കൽ ശരിയായി പരിശോധിച്ച്
അതേ helper എല്ലാ handler-കളിലും ചേർക്കുക.

## അൽഗോരിതം

1. **raw** request body വായിക്കുക — ഞങ്ങൾ നിങ്ങൾക്ക് POST ചെയ്ത കൃത്യമായ bytes.
2. `hmac_sha256(secret, body).hexdigest()` കണക്കാക്കുക.
3. `X-ThunderPhone-Signature`-നോട് **constant time**-ൽ താരതമ്യം ചെയ്യുക.
   (സാധാരണ string താരതമ്യം timing വിവരങ്ങൾ ചോർത്തും.)

ഞങ്ങൾ കൈമാറുന്ന കൃത്യമായ bytes-ലാണ് ഒപ്പിടുന്നത്, അതിനാൽ raw body
പരിശോധിക്കുന്നത് എല്ലായ്പ്പോഴും പ്രവർത്തിക്കും. ആ bytes payload-ന്റെ **canonical JSON serialization** കൂടിയാണ് —
കീകൾ അക്ഷരമാലാക്രമത്തിൽ sort ചെയ്തത്, compact separators
(സ്പേസുകളില്ലാത്ത `,`, `:`), UTF-8. നിങ്ങളുടെ framework parsed JSON മാത്രം നൽകുന്നുണ്ടെങ്കിൽ,
ഇതിന് പൂർണമായും തുല്യമായ രണ്ടാമത്തെ രീതിയുണ്ട്:
canonical ആയി വീണ്ടും serialize ചെയ്ത് അതിൽ HMAC നടത്തുക.

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

raw body മുൻഗണന നൽകുക — ഇത് ഒരു ഘട്ടം കുറവാണ്, കൂടാതെ ചില ഭാഷകളിലെ JSON number
round-tripping പ്രത്യേകതകളിൽ നിന്ന് സംരക്ഷിതവുമാണ്.

## ഏത് secret?

| ഉറവിടം | Secret |
|--------|--------|
| [Webhook endpoint](/ml/webhooks/endpoints) (`/v1/developer/webhook-endpoints`) | സൃഷ്ടിക്കുമ്പോൾ ഒരിക്കൽ നൽകുന്ന ഓരോ endpoint-നുമുള്ള `secret` (48 hex chars) |
| [Legacy single-URL webhook](/api-reference/organizations#legacy-single-url-webhook) | `GET /v1/webhook`-ൽ ലഭിക്കുന്ന ഓരോ org-നുമുള്ള `secret` |
| [Tool-endpoint invocation](/ml/tools/overview) (നിങ്ങളുടെ `endpoint.url`-ലേക്കുള്ള നേരിട്ടുള്ള കോൾ) | **org-level webhook secret** (legacy single-URL webhook-ലേതിന് സമാനം) — endpoint-നുള്ള secret അല്ല |

secret നിങ്ങളുടെ secret manager-ലോ env var-ലോ സൂക്ഷിക്കുക — ഒരിക്കലും commit ചെയ്യരുത്.

## റഫറൻസ് ഇംപ്ലിമെന്റേഷനുകൾ

നാലും raw request body പരിശോധിക്കുന്നു:

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

## ടൂൾ കോളുകൾ പരിശോധിക്കൽ

ഏജന്റ് നിങ്ങളുടെ
[ഫംഗ്ഷൻ ടൂളുകളിൽ](/ml/tools/overview) ഒന്ന് നേരിട്ട് വിളിക്കുമ്പോൾ (ടൂളിന് ഒരു
`endpoint` ഉണ്ടെങ്കിൽ), നിങ്ങളുടെ കോൺഫിഗർ ചെയ്ത `endpoint.headers`-നൊപ്പം
റിക്വസ്റ്റിൽ രണ്ട് ThunderPhone ഹെഡറുകൾ ഉണ്ടാകും:

- `X-ThunderPhone-Call-ID` — ലൈവ് കോളിന്റെ സംഖ്യാ ഐഡി.
- `X-ThunderPhone-Signature` — കൃത്യമായ റിക്വസ്റ്റ്-ബോഡി ബൈറ്റുകൾക്കുമേൽ,
  നിങ്ങളുടെ **ഓർഗ്-തല വെബ്ഹുക്ക് സീക്രട്ട്** കീ ആയി ഉപയോഗിക്കുന്ന HMAC-SHA256.

അതേ `verify()` ഹെൽപ്പർ മാറ്റമില്ലാതെ പ്രവർത്തിക്കും, എന്നാൽ രണ്ട് പ്രത്യേകതകളുണ്ട്:

1. **`GET` / `DELETE` ടൂളുകൾക്ക് ബോഡി ഇല്ല.** ആർഗ്യുമെന്റുകൾ ക്വറി
   പാരാമീറ്ററുകളായി എത്തും, കൂടാതെ സിഗ്നേച്ചർ **ശൂന്യമായ ബൈറ്റ്
   സ്ട്രിങ്ങിന്** മേലാണ് കണക്കാക്കുന്നത് — അതിനാൽ `verify(b"", sig, secret)`
   (Python) അല്ലെങ്കിൽ `verify(Buffer.alloc(0), sig, secret)` (Node) ഉപയോഗിക്കുക.
   ക്വറി സ്ട്രിങ് ഹാഷ് ചെയ്യരുത്.
2. **ലെഗസി വെബ്ഹുക്ക് കോൺഫിഗർ ചെയ്തിട്ടില്ലാത്ത ഓർഗുകൾക്ക് ഓർഗ് സീക്രട്ട് ഇല്ല.**
   ആ സാഹചര്യത്തിൽ ടൂൾ കോളുകളിൽ `X-ThunderPhone-Call-ID` മാത്രമേ ഉണ്ടാകൂ;
   സിഗ്നേച്ചർ ഹെഡർ ഉണ്ടാകില്ല. സൈനിംഗ് സീക്രട്ട് ലഭിക്കാൻ ലെഗസി വെബ്ഹുക്ക്
   (`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)
    ...
```

വെബ്ഹുക്ക്-**മോഡ്** ടൂൾ ഡിസ്പാച്ച് (`endpoint` ഇല്ലാത്തതും നിങ്ങളുടെ ഓർഗ്
വെബ്ഹുക്കിലേക്ക് `telephony.tool` / `web.tool` ആയി എത്തുന്നതുമായ ടൂളുകൾ)
ഒരു സാധാരണ സൈൻ ചെയ്ത വെബ്ഹുക്കാണ് — മുകളിലുള്ള സ്റ്റാൻഡേർഡ് രീതി ബാധകമാണ്.
രണ്ട് റിക്വസ്റ്റ് രൂപങ്ങൾക്കും [ഫംഗ്ഷൻ ടൂളുകൾ](/ml/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="ടൂൾ എൻഡ്പോയിന്റുകൾക്കായി തെറ്റായ സീക്രട്ട്">
    നേരിട്ടുള്ള ടൂൾ-എൻഡ്പോയിന്റ് കോളുകൾ **ഓർഗ്-തല വെബ്ഹുക്ക്
    സീക്രട്ട്** (`GET /v1/webhook`) ഉപയോഗിച്ചാണ് സൈൻ ചെയ്യപ്പെടുന്നത് —
    `/v1/developer/webhook-endpoints`-ലെ ഓരോ എൻഡ്പോയിന്റിനുമുള്ള സീക്രട്ടുകൾ ഉപയോഗിച്ചല്ല. അതേ `verify()`
    ഫംഗ്ഷൻ പുനരുപയോഗിക്കുക, എന്നാൽ ടൂൾ റൂട്ടുകളിൽ അതിന് ഓർഗ് സീക്രട്ട് നൽകുന്നുവെന്ന് ഉറപ്പാക്കുക.
  </Accordion>

  <Accordion title="GET/DELETE ടൂളുകളിൽ ക്വറി സ്ട്രിംഗ് ഹാഷ് ചെയ്യുന്നത്">
    ബോഡിയില്ലാത്ത ടൂൾ മെത്തേഡുകൾക്കായി സിഗ്നേച്ചർ ശൂന്യമായ ബൈറ്റ്
    സ്ട്രിംഗിനെ ഉൾക്കൊള്ളുന്നു, അതിനാൽ ഒരു സാർവത്രിക രീതി നിലനിൽക്കും: റോ റിക്വസ്റ്റ് ബോഡി എന്തുതന്നെയായാലും അതിന്റെ HMAC നിർമിക്കുക.
    URL അല്ലെങ്കിൽ ക്വറി സ്ട്രിംഗ് ഹാഷ് ചെയ്യുന്നത് ഒരിക്കലും പൊരുത്തപ്പെടില്ല.
  </Accordion>

  <Accordion title="പൊരുത്തക്കേടിൽ 401 തിരികെ നൽകാത്തത്">
    പരിശോധന പരാജയപ്പെട്ടിട്ടും 200 തിരികെ നൽകുന്നത് ഹാൻഡ്‌ലറെ റീപ്ലേ
    ലക്ഷ്യമാക്കുന്നു. പരിശോധന പരാജയപ്പെട്ടാൽ എപ്പോഴും 2xx-അല്ലാത്ത പ്രതികരണം നൽകുക.
  </Accordion>
</AccordionGroup>

---

## അടുത്ത ഘട്ടങ്ങൾ

<CardGroup cols={2}>
  <Card title="വെബ്ഹുക്കുകളുടെ അവലോകനം" icon="bolt" href="/ml/webhooks/overview">
    ഡെലിവറി സെമാന്റിക്സ്, റീട്രൈകൾ, ഉറവിട IP-കൾ.
  </Card>
  <Card title="വെബ്ഹുക്ക് എൻഡ്പോയിന്റുകൾ" icon="plug" href="/ml/webhooks/endpoints">
    ഒന്നിലധികം URL-കൾ കൈകാര്യം ചെയ്യുക, സീക്രട്ടുകൾ റൊട്ടേറ്റ് ചെയ്യുക.
  </Card>
  <Card title="ഫംഗ്ഷൻ ടൂളുകൾ" icon="screwdriver-wrench" href="/ml/tools/overview">
    രണ്ട് ടൂൾ-ഇൻവൊക്കേഷൻ പാതകളും അവയുടെ റിക്വസ്റ്റ് രൂപങ്ങളും.
  </Card>
  <Card title="ടൂൾ ഇന്റഗ്രേഷനുകൾ" icon="wrench" href="/ml/guides/build-tool-integration">
    ടൂൾ പിന്തുണയുള്ള ഒരു സമ്പൂർണ ഇന്റഗ്രേഷൻ തുടക്കം മുതൽ അവസാനം വരെ നിർമ്മിക്കുക.
  </Card>
</CardGroup>
