---
title: "वेबहुक सिग्नेचर सत्यापित करें"
description: "ThunderPhone द्वारा भेजे जाने वाले हर वेबहुक और टूल अनुरोध पर टाइमस्टैम्प के साथ सिग्नेचर होता है। यहां दिए गए रेसिपी या SDK हेल्पर से इसे सत्यापित करें और पांच मिनट से पुराने दोहराए गए अनुरोधों को अस्वीकार करें।"
---

हम आपके सर्वर को भेजने वाली हर रिक्वेस्ट (वेबहुक डिलीवरी, लेगेसी
सिंगल-URL वेबहुक और टूल-एंडपॉइंट कॉल) में दो सिग्नेचर
हेडर होते हैं। दोनों एक ही सीक्रेट के साथ HMAC-SHA256 हैं।

| हेडर | किन पर साइन करता है | रीप्ले प्रोटेक्शन |
|--------|-------|-------------------|
| `X-ThunderPhone-Signature-V2` (अनुशंसित) | टाइमस्टैम्प, मेथड, पाथ, क्वेरी और बॉडी | 5 मिनट से अधिक पुराने रिक्वेस्ट अस्वीकार करें |
| `X-ThunderPhone-Signature` | केवल बॉडी | कोई नहीं: कैप्चर की गई रिक्वेस्ट हमेशा वेरिफ़ाई होती है |

नए इंटीग्रेशन में `X-ThunderPhone-Signature-V2` वेरिफ़ाई करें और
मौजूदा इंटीग्रेशन को इस पर माइग्रेट करें। `X-ThunderPhone-Signature` अब भी हर
रिक्वेस्ट पर बिना किसी बदलाव के भेजा जाता है, इसलिए इस पर बने वेरिफ़ायर काम करते रहते हैं। हम इसे भेजना बंद करने से पहले एक तारीख की घोषणा करेंगे।

## हस्ताक्षर V2

```http
X-ThunderPhone-Signature-V2: t=1767225600,v1=03a89bec521f78d55727237ca4713bb0b8dcdf2e09e450865b5b5637b80c1c4e
```

- `t` वह Unix समय है, सेकंड में, जब हमने अनुरोध भेजा था। दोबारा की गई
  डिलीवरी भेजे जाने पर फिर से साइन की जाती है, उसके अपने `t` के साथ।
- `v1` नीचे दी गई हस्ताक्षरित स्ट्रिंग का हेक्स HMAC-SHA256 है, जिसे आपके
  सीक्रेट से की किया गया है।

वैल्यू, कॉमा से अलग किए गए `key=value` पेयर की सूची है। जिन कीज़ को आप
नहीं पहचानते उन्हें अनदेखा करें, और यदि कोई `v1` मेल खाता है तो अनुरोध स्वीकार करें: हम
भविष्य के किसी बदलाव के दौरान दूसरा हस्ताक्षर जोड़ सकते हैं।

### हस्ताक्षरित स्ट्रिंग

चार फ़ील्ड, प्रत्येक के बाद एक न्यूलाइन (`\n`, बाइट `0x0A`), अंतिम फ़ील्ड को
छोड़कर:

```text
<t>\n<METHOD>\n<path and query>\n<body>
```

| फ़ील्ड | वैल्यू |
|-------|-------|
| `t` | हेडर से `t` वैल्यू |
| `METHOD` | अपर केस में HTTP मेथड: `POST`, `GET`, `PUT`, `PATCH` या `DELETE` |
| path and query | HTTP अनुरोध लाइन से अनुरोध टारगेट: पाथ, फिर `?` और मौजूद होने पर क्वेरी स्ट्रिंग। कोई स्कीम, होस्ट या फ़्रैगमेंट नहीं। इसे डीकोड किए बिना या पैरामीटर का क्रम बदले बिना, ठीक उसी तरह उपयोग करें जैसे यह प्राप्त हुआ था |
| `body` | रॉ अनुरोध बॉडी। `GET` और `DELETE` टूल कॉल में कोई बॉडी नहीं होती, इसलिए उनकी हस्ताक्षरित स्ट्रिंग पाथ के बाद न्यूलाइन पर समाप्त होती है |

वेरिफ़ाई करने के लिए:

1. किसी भी पार्सिंग से पहले, हेडर, मेथड, रॉ पाथ और
   क्वेरी, तथा रॉ बॉडी पढ़ें।
2. हेडर से `t` और प्रत्येक `v1` लें।
3. यदि `t` आपके क्लॉक से पहले या बाद में **300 सेकंड** से अधिक है, तो
   अनुरोध अस्वीकार करें।
4. अपने सीक्रेट के साथ हस्ताक्षरित स्ट्रिंग का हेक्स HMAC-SHA256 कंप्यूट करें।
5. इसे कॉन्स्टेंट टाइम में `v1` से कंपेयर करें। कुछ भी मेल न खाए तो `401` रिस्पॉन्ड करें।

### कार्य किया गया उदाहरण

सीक्रेट `whsec_example_4c1f2a9b`। हम यह अनुरोध
`t = 1767225600` (2026-01-01 00:00:00 UTC) पर भेजते हैं:

```http
POST /tools/lookup?region=us HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature-V2: t=1767225600,v1=03a89bec521f78d55727237ca4713bb0b8dcdf2e09e450865b5b5637b80c1c4e

{"customer_id":"cust_123"}
```

हस्ताक्षरित स्ट्रिंग ये चार लाइनें हैं, बॉडी के बाद कोई न्यूलाइन नहीं है:

```text
1767225600
POST
/tools/lookup?region=us
{"customer_id":"cust_123"}
```

इसका HMAC-SHA256
`03a89bec521f78d55727237ca4713bb0b8dcdf2e09e450865b5b5637b80c1c4e` है।
इसके विरुद्ध अपने इम्प्लीमेंटेशन की जांच करें, या इसे `openssl` से दोबारा बनाएं:

```bash
printf '1767225600\nPOST\n/tools/lookup?region=us\n{"customer_id":"cust_123"}' \
  | openssl dgst -sha256 -hmac 'whsec_example_4c1f2a9b'
```

उसी सेकंड में `/tools/lookup?customer_id=cust_123` के लिए `GET` टूल कॉल की
बॉडी खाली होती है। इसकी हस्ताक्षरित स्ट्रिंग
`1767225600\nGET\n/tools/lookup?customer_id=cust_123\n` है और इसका हस्ताक्षर
`3ce214cb0b8f2bf39b1d19d4e7265df9dbaff5a5f838d3cc6c753a8f7a4ad280` है।

### SDKs के साथ

[Python और TypeScript SDKs](/api-reference/sdks) में एक हेल्पर शामिल है
(`thunderphone` 0.2.0 या उसके बाद का, `@thunderphone/sdk` 0.2.0 या उसके बाद का)। यह
5-मिनट की विंडो जांचता है, कॉन्स्टेंट टाइम में कंपेयर करता है, और सीक्रेट्स की सूची
स्वीकार करता है ताकि आप बिना डाउनटाइम के रोटेट कर सकें।

<CodeGroup>
```python Python
from thunderphone import verify_signature

ok = verify_signature(
    header,            # the X-ThunderPhone-Signature-V2 value
    secret=SECRET,     # or [NEW_SECRET, OLD_SECRET] while rotating
    method=method,     # "POST"
    path=path,         # "/tools/lookup?region=us", exactly as received
    body=raw_body,     # bytes, before any JSON parsing
    tolerance=300,     # optional; seconds
)
```

```javascript Node.js
import { verifySignature } from "@thunderphone/sdk";

const ok = verifySignature({
  header,               // the X-ThunderPhone-Signature-V2 value
  secret: SECRET,       // or [NEW_SECRET, OLD_SECRET] while rotating
  method,               // "POST"
  path,                 // "/tools/lookup?region=us", exactly as received
  body: rawBody,        // Buffer or string, before any JSON parsing
  toleranceSeconds: 300, // optional
});
```
</CodeGroup>

### SDK के बिना

<CodeGroup>
```python Python
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 300


def verify_v2(header: str, secret: str, method: str, path: str, body: bytes) -> bool:
    """path is the path plus ?query exactly as received; body is the raw body."""
    timestamp, signatures = None, []
    for item in (header or "").split(","):
        key, _, value = item.strip().partition("=")
        if key == "t" and value.isdigit() and len(value) <= 15:
            timestamp = int(value)
        elif key == "v1":
            signatures.append(value)
    if timestamp is None or abs(time.time() - timestamp) > TOLERANCE_SECONDS:
        return False
    signed = f"{timestamp}\n{method.upper()}\n{path}\n".encode() + body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected.encode(), s.encode()) for s in signatures)
```

```javascript Node.js
import crypto from "node:crypto";

const TOLERANCE_SECONDS = 300;

// path is the path plus ?query exactly as received; body is the raw Buffer.
export function verifyV2(header, secret, method, path, body) {
  let timestamp;
  const signatures = [];
  for (const item of (header || "").split(",")) {
    const [key, value = ""] = item.trim().split("=", 2);
    if (key === "t" && /^\d+$/.test(value)) timestamp = Number(value);
    else if (key === "v1") signatures.push(value);
  }
  if (timestamp === undefined) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
  const expected = Buffer.from(
    crypto
      .createHmac("sha256", secret)
      .update(`${timestamp}\n${method.toUpperCase()}\n${path}\n`)
      .update(body)
      .digest("hex"),
  );
  return signatures.some((sig) => {
    const candidate = Buffer.from(sig);
    return (
      candidate.length === expected.length &&
      crypto.timingSafeEqual(candidate, expected)
    );
  });
}
```
</CodeGroup>

### फ्रेमवर्क वायरिंग

हर उदाहरण रॉ पाथ और क्वेरी तथा रॉ बॉडी पढ़ता है, फिर SDK हेल्पर को कॉल करता है।

<CodeGroup>
```python FastAPI
import json

from fastapi import FastAPI, HTTPException, Request
from thunderphone import verify_signature

app = FastAPI()


def raw_path_and_query(request: Request) -> str:
    path = (request.scope.get("raw_path") or request.url.path.encode()).decode("latin-1")
    query = request.scope.get("query_string", b"").decode("latin-1")
    return f"{path}?{query}" if query else path


@app.post("/thunderphone-webhook")
async def hook(request: Request):
    body = await request.body()  # raw bytes, NOT request.json()
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=SECRET,
        method=request.method,
        path=raw_path_and_query(request),
        body=body,
    ):
        raise HTTPException(status_code=401)
    event = json.loads(body)
    # … dispatch on event["type"] …
    return {"ok": True}
```

```javascript Express
import express from "express";
import { verifySignature } from "@thunderphone/sdk";

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 ok = verifySignature({
      header: req.header("X-ThunderPhone-Signature-V2"),
      secret: process.env.WEBHOOK_SECRET,
      method: req.method,
      path: req.originalUrl, // path and query as received
      body: req.body,
    });
    if (!ok) 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 HttpResponseForbidden, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from thunderphone import verify_signature


@csrf_exempt
@require_POST
def hook(request):
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=SECRET,
        method=request.method,
        path=request.get_full_path(),
        body=request.body,  # raw bytes
    ):
        return HttpResponseForbidden("invalid signature")
    event = json.loads(request.body)
    # … dispatch on event["type"] …
    return JsonResponse({"ok": True})
```
</CodeGroup>

`req.originalUrl` (Express) और ASGI के `raw_path` और `query_string`
(FastAPI) टारगेट को ठीक वैसे ही रखते हैं जैसा वह प्राप्त हुआ था। Django का
`get_full_path()` और Flask का `request.path` पहले पाथ को डिकोड करते हैं, जिससे
सामान्य पाथ के लिए वही स्ट्रिंग मिलती है। यदि आपके एंडपॉइंट पाथ में
प्रतिशत-एन्कोडेड या रिज़र्व्ड कैरेक्टर, जैसे `%2F`, `;` या `=`, शामिल हैं, तो
इसके बजाय आपके सर्वर द्वारा उपलब्ध कराया गया रॉ टारगेट पढ़ें (उदाहरण के लिए,
Gunicorn पर `request.META["RAW_URI"]`)।

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

एक ही सीक्रेट दोनों हेडर्स पर साइन करता है।

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

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

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

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

- `X-ThunderPhone-Call-ID` — लाइव कॉल की संख्यात्मक आईडी।
- `X-ThunderPhone-Signature-V2` — ऊपर दिया गया टाइमस्टैम्प वाला सिग्नेचर, जो
  आपके **संगठन-स्तरीय वेबहुक सीक्रेट** से की किया गया है।
- `X-ThunderPhone-Signature` — लेगसी, केवल-बॉडी सिग्नेचर, उसी की के साथ।

वेबहुक से दो बातें अलग हैं:

1. **`GET` / `DELETE` टूल्स में कोई बॉडी नहीं होती।** आर्ग्युमेंट क्वेरी
   पैरामीटर के रूप में जाते हैं। V2 सिग्नेचर उन्हें पाथ और क्वेरी के माध्यम से
   कवर करता है, और बॉडी फ़ील्ड खाली होता है। रॉ क्वेरी स्ट्रिंग से वेरिफ़ाई करें:
   पैरामीटर पार्स करने और नई क्वेरी स्ट्रिंग बनाने से उनका क्रम या एन्कोडिंग बदल
   सकता है। लेगसी हेडर खाली बाइट स्ट्रिंग पर साइन करता है।
2. **जिन संगठनों में लेगसी वेबहुक कॉन्फ़िगर नहीं है, उनके पास संगठन सीक्रेट नहीं होता।** उस
   स्थिति में टूल कॉल्स में केवल `X-ThunderPhone-Call-ID` होता है और कोई
   सिग्नेचर हेडर नहीं होता। साइनिंग सीक्रेट पाने के लिए लेगसी वेबहुक
   (`PUT /v1/webhook`) कॉन्फ़िगर करें, या `endpoint.headers` के माध्यम से अपने
   हेडर के साथ टूल कॉल्स को ऑथेंटिकेट करें।

```python
@app.api_route("/tools/search-appointments", methods=["GET", "POST"])
async def tool(request: Request):
    body = await request.body()  # b"" for GET/DELETE tools
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=ORG_WEBHOOK_SECRET,
        method=request.method,
        path=raw_path_and_query(request),
        body=body,
    ):
        raise HTTPException(status_code=401)
    call_id = request.headers.get("X-ThunderPhone-Call-ID", "")
    args = json.loads(body) if body else dict(request.query_params)
    ...
```

Express में, बॉडी पार्सर `GET` पर `req.body` को खाली ऑब्जेक्ट के रूप में छोड़ देता है,
इसलिए `Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0)` पास करें।

वेबहुक-**मोड** टूल डिस्पैच (वे टूल जिनमें `endpoint` नहीं होता और जिन्हें
आपके संगठन वेबहुक पर `telephony.tool` / `web.tool` के रूप में डिलीवर किया जाता है) एक सामान्य
साइन्ड वेबहुक है — ऊपर दिया गया मानक तरीका लागू होता है। दोनों रिक्वेस्ट स्वरूपों के लिए
[फंक्शन टूल](/hi/tools/overview) देखें।

## लेगेसी सिग्नेचर: `X-ThunderPhone-Signature`

मूल हेडर केवल रॉ बॉडी का हेक्स HMAC-SHA256 है, उसी सीक्रेट के साथ।
इसमें कोई टाइमस्टैम्प नहीं होता, इसलिए जो भी एक साइन की गई रिक्वेस्ट को कैप्चर करता है, वह उसे रीप्ले कर सकता है। इंटीग्रेशन के V2 पर माइग्रेट होने तक इसे अब भी हर रिक्वेस्ट पर भेजा जाता है।

1. **रॉ** रिक्वेस्ट बॉडी पढ़ें — ठीक वही बाइट्स जो हमने आपको भेजी हैं।
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 नंबर
राउंड-ट्रिपिंग की विचित्रताओं से सुरक्षित है। यही बात V2 के बॉडी
फ़ील्ड पर भी लागू होती है।

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

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

<AccordionGroup>
  <Accordion title="क्लॉक स्क्यू">
    5-मिनट की विंडो आपके सर्वर की घड़ी के आधार पर मापी जाती है। इसे NTP के साथ
    सिंक्रोनाइज़ रखें; 5 मिनट से अधिक गलत घड़ी हर
    रिक्वेस्ट को अस्वीकार कर देती है।
  </Accordion>

  <Accordion title="एक प्रॉक्सी पाथ को फिर से लिखती है">
    साइन किया गया पाथ वही है जिसका हमने अनुरोध किया था: उस URL का पाथ जिसे आपने
    ThunderPhone में कॉन्फ़िगर किया है। यदि आपकी एप्लिकेशन के रिक्वेस्ट देखने से पहले कोई रिवर्स प्रॉक्सी या API गेटवे
    प्रीफ़िक्स हटाता या जोड़ता है, तो वेरिफ़ाई करने से पहले
    हमारे भेजे गए पाथ को फिर से बनाएँ (उदाहरण के लिए, प्रीफ़िक्स वापस जोड़ें)।
  </Accordion>

  <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` के किसी प्रति-एंडपॉइंट सीक्रेट से। उसी हेल्पर का पुनः उपयोग करें,
    लेकिन सुनिश्चित करें कि टूल रूट्स पर आप उसे संगठन का सीक्रेट देते हैं।
  </Accordion>

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

  <Accordion title="विंडो के भीतर डुप्लिकेट">
    टाइमस्टैम्प 5 मिनट से पुराने रीप्ले को रोकता है, उस विंडो के भीतर आने वाली दूसरी कॉपी को नहीं।
    एंडपॉइंट डिलीवरी कम-से-कम-एक-बार होती हैं, इसलिए उसी इवेंट का
    रिट्राई भी आ सकता है; एनवेलप के `event_id` पर डी-डुप्लिकेट करें।
  </Accordion>

  <Accordion title="मिसमैच पर 401 रिटर्न न करना">
    वेरिफ़िकेशन विफल होने पर 200 रिटर्न करना हैंडलर को रीप्ले
    टारगेट बना देता है। वेरिफ़िकेशन विफल होने पर हमेशा नॉन-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>
