ThunderPhone 2.0 ਹੁਣ ਲਾਈਵ ਹੈ।ਸੈਲਫ਼-ਸਰਵਿਸ—2¢/ਮਿੰਟ ਤੋਂ।ਐਲਾਨ ਪੜ੍ਹੋ

Operations

ਵੈੱਬਹੁੱਕ ਦਸਤਖਤਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ

ThunderPhone ਵੱਲੋਂ ਭੇਜੀ ਹਰ ਵੈੱਬਹੁੱਕ ਅਤੇ ਟੂਲ ਬੇਨਤੀ ਨੂੰ ਟਾਈਮਸਟੈਂਪ ਨਾਲ ਸਾਈਨ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇੱਥੇ ਦਿੱਤੀ ਰੈਸਪੀ ਜਾਂ SDK ਹੈਲਪਰ ਨਾਲ ਇਸ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ ਅਤੇ ਪੰਜ ਮਿੰਟ ਤੋਂ ਪੁਰਾਣੀਆਂ ਦੁਹਰਾਈਆਂ ਗਈਆਂ ਬੇਨਤੀਆਂ ਨੂੰ ਅਸਵੀਕਾਰ ਕਰੋ।

ਤੁਹਾਡੇ ਸਰਵਰ ਨੂੰ ਭੇਜੀ ਜਾਣ ਵਾਲੀ ਹਰ ਬੇਨਤੀ (webhook ਡਿਲਿਵਰੀਆਂ, ਪੁਰਾਣਾ single-URL webhook ਅਤੇ ਟੂਲ-ਐਂਡਪੌਇੰਟ ਕਾਲਾਂ) ਵਿੱਚ ਦੋ ਸਿਗਨੇਚਰ ਹੈਡਰ ਹੁੰਦੇ ਹਨ। ਦੋਵੇਂ ਇੱਕੋ secret ਨਾਲ HMAC-SHA256 ਹਨ।

ਹੈਡਰਸਾਈਨ ਕਰਦਾ ਹੈਰੀਪਲੇ ਸੁਰੱਖਿਆ
X-ThunderPhone-Signature-V2 (ਸਿਫਾਰਸ਼ੀ)ਟਾਈਮਸਟੈਂਪ, ਮੈਥਡ, ਪਾਥ, ਕਵੇਰੀ ਅਤੇ ਬਾਡੀ5 ਮਿੰਟ ਤੋਂ ਵੱਧ ਪੁਰਾਣੀਆਂ ਬੇਨਤੀਆਂ ਰੱਦ ਕਰੋ
X-ThunderPhone-Signatureਸਿਰਫ਼ ਬਾਡੀਕੋਈ ਨਹੀਂ: ਕੈਪਚਰ ਕੀਤੀ ਬੇਨਤੀ ਹਮੇਸ਼ਾਂ ਲਈ ਵੈਰੀਫਾਈ ਹੋ ਜਾਂਦੀ ਹੈ

ਨਵੀਆਂ ਇੰਟੀਗ੍ਰੇਸ਼ਨਾਂ ਵਿੱਚ X-ThunderPhone-Signature-V2 ਦੀ ਤਸਦੀਕ ਕਰੋ ਅਤੇ ਮੌਜੂਦਾ ਇੰਟੀਗ੍ਰੇਸ਼ਨਾਂ ਨੂੰ ਇਸ ਉੱਤੇ ਲਿਆਓ। X-ThunderPhone-Signature ਹਰ ਬੇਨਤੀ ਨਾਲ, ਬਿਨਾਂ ਬਦਲਾਅ ਦੇ, ਅਜੇ ਵੀ ਭੇਜਿਆ ਜਾਂਦਾ ਹੈ, ਇਸ ਲਈ ਇਸ 'ਤੇ ਬਣੇ ਵੈਰੀਫਾਇਰ ਕੰਮ ਕਰਦੇ ਰਹਿੰਦੇ ਹਨ। ਇਸਨੂੰ ਭੇਜਣਾ ਬੰਦ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਅਸੀਂ ਇੱਕ ਤਾਰੀਖ ਦਾ ਐਲਾਨ ਕਰਾਂਗੇ।

ਹਸਤਾਖਰ V2

X-ThunderPhone-Signature-V2: t=1767225600,v1=03a89bec521f78d55727237ca4713bb0b8dcdf2e09e450865b5b5637b80c1c4e
  • t ਉਹ Unix ਸਮਾਂ ਹੈ, ਸਕਿੰਟਾਂ ਵਿੱਚ, ਜਦੋਂ ਅਸੀਂ ਬੇਨਤੀ ਭੇਜੀ ਸੀ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਡਿਲੀਵਰੀ ਨੂੰ ਭੇਜਣ ਵੇਲੇ ਇਸਦੇ ਆਪਣੇ t ਨਾਲ ਮੁੜ ਹਸਤਾਖਰਿਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।
  • v1 ਹੇਠਾਂ ਦਿੱਤੀ ਹਸਤਾਖਰਿਤ ਸਤਰ ਦਾ ਹੈਕਸ HMAC-SHA256 ਹੈ, ਜੋ ਤੁਹਾਡੇ ਸੀਕ੍ਰੇਟ ਨਾਲ ਕੀਡ ਹੈ।

ਮੁੱਲ key=value ਜੋੜਿਆਂ ਦੀ ਕਾਮਿਆਂ ਨਾਲ ਵੱਖ ਕੀਤੀ ਸੂਚੀ ਹੈ। ਜਿਨ੍ਹਾਂ ਕੁੰਜੀਆਂ ਨੂੰ ਤੁਸੀਂ ਨਹੀਂ ਪਛਾਣਦੇ, ਉਹਨਾਂ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰੋ, ਅਤੇ ਜੇ ਕੋਈ ਵੀ v1 ਮੇਲ ਖਾਂਦਾ ਹੈ ਤਾਂ ਬੇਨਤੀ ਸਵੀਕਾਰ ਕਰੋ: ਭਵਿੱਖੀ ਤਬਦੀਲੀ ਦੌਰਾਨ ਅਸੀਂ ਦੂਜਾ ਹਸਤਾਖਰ ਜੋੜ ਸਕਦੇ ਹਾਂ।

ਹਸਤਾਖਰਿਤ ਸਤਰ

ਚਾਰ ਫੀਲਡ, ਹਰ ਇੱਕ ਦੇ ਬਾਅਦ ਇੱਕ ਨਵੀਂ ਲਾਈਨ (\n, ਬਾਈਟ 0x0A), ਆਖਰੀ ਨੂੰ ਛੱਡ ਕੇ:

<t>\n<METHOD>\n<path and query>\n<body>
ਫੀਲਡਮੁੱਲ
tਹੈਡਰ ਤੋਂ t ਮੁੱਲ
METHODਵੱਡੇ ਅੱਖਰਾਂ ਵਿੱਚ HTTP ਮੈਥਡ: POST, GET, PUT, PATCH ਜਾਂ DELETE
ਪਾਥ ਅਤੇ ਕਵੇਰੀ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) 'ਤੇ ਭੇਜਦੇ ਹਾਂ:

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

ਹਸਤਾਖਰਿਤ ਸਤਰ ਇਹ ਚਾਰ ਲਾਈਨਾਂ ਹਨ, ਬਾਡੀ ਤੋਂ ਬਾਅਦ ਕੋਈ ਨਵੀਂ ਲਾਈਨ ਨਹੀਂ:

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

ਇਸਦਾ HMAC-SHA256 03a89bec521f78d55727237ca4713bb0b8dcdf2e09e450865b5b5637b80c1c4e ਹੈ। ਇਸਦੇ ਵਿਰੁੱਧ ਆਪਣੇ ਇੰਪਲੀਮੈਂਟੇਸ਼ਨ ਦੀ ਜਾਂਚ ਕਰੋ, ਜਾਂ ਇਸਨੂੰ openssl ਨਾਲ ਦੁਬਾਰਾ ਬਣਾਓ:

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 ਵਿੱਚ ਇੱਕ ਹੈਲਪਰ ਸ਼ਾਮਲ ਹੈ (thunderphone 0.2.0 ਜਾਂ ਬਾਅਦ ਵਾਲਾ, @thunderphone/sdk 0.2.0 ਜਾਂ ਬਾਅਦ ਵਾਲਾ)। ਇਹ 5 ਮਿੰਟ ਦੀ ਵਿੰਡੋ ਦੀ ਜਾਂਚ ਕਰਦਾ ਹੈ, ਕੌਂਸਟੈਂਟ ਟਾਈਮ ਵਿੱਚ ਤੁਲਨਾ ਕਰਦਾ ਹੈ, ਅਤੇ ਸੀਕ੍ਰੇਟਾਂ ਦੀ ਸੂਚੀ ਸਵੀਕਾਰ ਕਰਦਾ ਹੈ ਤਾਂ ਜੋ ਤੁਸੀਂ ਬਿਨਾਂ ਡਾਊਨਟਾਈਮ ਦੇ ਉਹਨਾਂ ਨੂੰ ਰੋਟੇਟ ਕਰ ਸਕੋ।

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
)
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
});

SDK ਤੋਂ ਬਿਨਾਂ

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)
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)
    );
  });
}

ਫਰੇਮਵਰਕ ਵਾਇਰਿੰਗ

ਹਰ ਉਦਾਹਰਨ ਕੱਚਾ ਪਾਥ ਅਤੇ ਕਵੇਰੀ ਅਤੇ ਕੱਚੀ ਬਾਡੀ ਪੜ੍ਹਦੀ ਹੈ, ਫਿਰ SDK ਹੈਲਪਰ ਨੂੰ ਕਾਲ ਕਰਦੀ ਹੈ।

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}
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);
  },
);
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})

req.originalUrl (Express) ਅਤੇ ASGI ਦੇ raw_path ਅਤੇ query_string (FastAPI) ਬਿਲਕੁਲ ਪ੍ਰਾਪਤ ਹੋਏ ਟਾਰਗੇਟ ਹਨ। Django ਦਾ get_full_path() ਅਤੇ Flask ਦਾ request.path ਪਹਿਲਾਂ ਪਾਥ ਨੂੰ ਡੀਕੋਡ ਕਰਦੇ ਹਨ, ਜਿਸ ਨਾਲ ਆਮ ਪਾਥਾਂ ਲਈ ਉਹੀ ਸਟਰਿੰਗ ਮਿਲਦੀ ਹੈ। ਜੇ ਤੁਹਾਡੇ ਐਂਡਪੌਇੰਟ ਪਾਥ ਵਿੱਚ ਪਰਸੈਂਟ-ਏਨਕੋਡ ਕੀਤੇ ਜਾਂ ਰਿਜ਼ਰਵਡ ਅੱਖਰ ਜਿਵੇਂ %2F, ; ਜਾਂ = ਹਨ, ਤਾਂ ਇਸਦੀ ਬਜਾਏ ਤੁਹਾਡਾ ਸਰਵਰ ਜੋ ਕੱਚਾ ਟਾਰਗੇਟ ਦਿੰਦਾ ਹੈ ਉਹ ਪੜ੍ਹੋ (ਉਦਾਹਰਨ ਲਈ Gunicorn ਵਿੱਚ request.META["RAW_URI"])।

ਕਿਹੜਾ ਸੀਕ੍ਰੇਟ?

ਇੱਕੋ secret ਦੋਵੇਂ headers ਨੂੰ ਸਾਈਨ ਕਰਦਾ ਹੈ।

ਸਰੋਤਸੀਕ੍ਰੇਟ
ਵੈੱਬਹੁੱਕ ਐਂਡਪੁਆਇੰਟ (/v1/developer/webhook-endpoints)ਬਣਾਉਣ ਵੇਲੇ ਇੱਕ ਵਾਰ ਵਾਪਸ ਕੀਤਾ ਗਿਆ ਪ੍ਰਤੀ-ਐਂਡਪੁਆਇੰਟ secret (48 ਹੈਕਸ ਅੱਖਰ)
ਪੁਰਾਣਾ ਸਿੰਗਲ-URL ਵੈੱਬਹੁੱਕGET /v1/webhook ਉੱਤੇ ਵਾਪਸ ਕੀਤਾ ਗਿਆ ਪ੍ਰਤੀ-ਸੰਗਠਨ secret
ਟੂਲ-ਐਂਡਪੁਆਇੰਟ ਇਨਵੋਕੇਸ਼ਨ (ਤੁਹਾਡੇ endpoint.url ਨੂੰ ਸਿੱਧੀ ਕਾਲ)ਸੰਗਠਨ-ਪੱਧਰ ਦਾ ਵੈੱਬਹੁੱਕ ਸੀਕ੍ਰੇਟ (ਪੁਰਾਣੇ ਸਿੰਗਲ-URL ਵੈੱਬਹੁੱਕ ਵਾਲਾ ਹੀ) — ਪ੍ਰਤੀ-ਐਂਡਪੁਆਇੰਟ ਸੀਕ੍ਰੇਟ ਨਹੀਂ

ਸੀਕ੍ਰੇਟ ਨੂੰ ਆਪਣੇ ਸੀਕ੍ਰੇਟ ਮੈਨੇਜਰ ਜਾਂ env var ਵਿੱਚ ਸਟੋਰ ਕਰੋ — ਇਸ ਨੂੰ ਕਦੇ ਵੀ ਕਮਿਟ ਨਾ ਕਰੋ।

ਟੂਲ ਕਾਲਾਂ ਦੀ ਪੁਸ਼ਟੀ ਕਰਨਾ

ਜਦੋਂ ਏਜੰਟ ਤੁਹਾਡੇ ਕਿਸੇ ਫੰਕਸ਼ਨ ਟੂਲ ਨੂੰ ਸਿੱਧਾ ਕਾਲ ਕਰਦਾ ਹੈ (ਟੂਲ ਵਿੱਚ ਇੱਕ 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 ਰਾਹੀਂ ਆਪਣੇ ਹੈਡਰ ਨਾਲ ਟੂਲ ਕਾਲਾਂ ਨੂੰ ਪ੍ਰਮਾਣਿਤ ਕਰੋ।
@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 ਵਜੋਂ ਭੇਜੇ ਜਾਂਦੇ ਹਨ) ਇੱਕ ਆਮ ਸਾਈਨ ਕੀਤਾ ਵੈੱਬਹੁੱਕ ਹੈ — ਉੱਪਰ ਦਿੱਤੀ ਮਿਆਰੀ ਵਿਧੀ ਲਾਗੂ ਹੁੰਦੀ ਹੈ। ਦੋਵੇਂ ਬੇਨਤੀ ਰੂਪਾਂ ਲਈ ਫੰਕਸ਼ਨ ਟੂਲ ਵੇਖੋ।

ਪੁਰਾਣਾ ਸਿਗਨੇਚਰ: X-ThunderPhone-Signature

ਮੂਲ ਹੈਡਰ ਸਿਰਫ਼ ਰਾਅ ਬਾਡੀ ਦਾ ਹੈਕਸ HMAC-SHA256 ਹੁੰਦਾ ਹੈ, ਉਹੀ ਸੀਕਰਟ ਵਰਤ ਕੇ। ਇਸ ਵਿੱਚ ਕੋਈ ਟਾਈਮਸਟੈਂਪ ਨਹੀਂ ਹੁੰਦਾ, ਇਸ ਲਈ ਜੋ ਵੀ ਇੱਕ ਸਾਈਨ ਕੀਤੀ ਰਿਕਵੇਸਟ ਕੈਪਚਰ ਕਰ ਲੈਂਦਾ ਹੈ, ਉਹ ਇਸਨੂੰ ਦੁਬਾਰਾ ਚਲਾ ਸਕਦਾ ਹੈ। ਇੰਟੀਗ੍ਰੇਸ਼ਨ V2 ਵੱਲ ਜਾਣ ਦੌਰਾਨ ਵੀ ਇਹ ਹਰ ਰਿਕਵੇਸਟ ਨਾਲ ਭੇਜਿਆ ਜਾਂਦਾ ਹੈ।

  1. ਰਾਅ ਰਿਕਵੇਸਟ ਬਾਡੀ ਪੜ੍ਹੋ — ਬਿਲਕੁਲ ਉਹੀ ਬਾਈਟ ਜੋ ਅਸੀਂ ਤੁਹਾਨੂੰ ਭੇਜੀਆਂ ਹਨ।
  2. hmac_sha256(secret, body).hexdigest() ਗਣਨਾ ਕਰੋ।
  3. X-ThunderPhone-Signature ਨਾਲ ਕਾਂਸਟੈਂਟ ਟਾਈਮ ਵਿੱਚ ਤੁਲਨਾ ਕਰੋ।

ਅਸੀਂ ਬਿਲਕੁਲ ਉਹੀ ਬਾਈਟ ਸਾਈਨ ਕਰਦੇ ਹਾਂ ਜੋ ਅਸੀਂ ਭੇਜਦੇ ਹਾਂ। ਇਹ ਬਾਈਟ ਪੇਲੋਡ ਦੀ ਕੈਨੋਨਿਕਲ JSON ਸੀਰੀਅਲਾਈਜ਼ੇਸ਼ਨ ਵੀ ਹਨ — ਕੁੰਜੀਆਂ ਵਰਣਮਾਲਾ ਅਨੁਸਾਰ ਕ੍ਰਮਬੱਧ, ਸੰਖੇਪ ਸੇਪਰੇਟਰ (ਬਿਨਾਂ ਖਾਲੀ ਥਾਵਾਂ ਦੇ , ਅਤੇ :), UTF-8। ਜੇ ਤੁਹਾਡਾ ਫ੍ਰੇਮਵਰਕ ਸਿਰਫ਼ ਪਾਰਸ ਕੀਤਾ JSON ਹੀ ਦਿੰਦਾ ਹੈ, ਤਾਂ ਇਸਨੂੰ ਕੈਨੋਨਿਕਲ ਤਰੀਕੇ ਨਾਲ ਦੁਬਾਰਾ ਸੀਰੀਅਲਾਈਜ਼ ਕਰੋ ਅਤੇ ਉਸ ਦਾ HMAC ਬਣਾਓ:

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

ਰਾਅ ਬਾਡੀ ਨੂੰ ਤਰਜੀਹ ਦਿਓ — ਇਹ ਇੱਕ ਕਦਮ ਘੱਟ ਹੈ ਅਤੇ ਕੁਝ ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ JSON ਨੰਬਰ ਰਾਊਂਡ-ਟ੍ਰਿਪਿੰਗ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਤੋਂ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦਾ। ਇਹੀ ਗੱਲ V2 ਦੇ ਬਾਡੀ ਫੀਲਡ ਲਈ ਵੀ ਲਾਗੂ ਹੁੰਦੀ ਹੈ।

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 "")
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
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
require "openssl"
 
def verify(body, signature, secret)
  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
  Rack::Utils.secure_compare(expected, signature.to_s)
end

ਆਮ ਸਮੱਸਿਆਵਾਂ

ਘੜੀ ਦਾ ਸਮਾਂ-ਅੰਤਰ

5-ਮਿੰਟ ਦੀ ਵਿੰਡੋ ਤੁਹਾਡੇ ਸਰਵਰ ਦੀ ਘੜੀ ਦੇ ਮੁਕਾਬਲੇ ਮਾਪੀ ਜਾਂਦੀ ਹੈ। ਇਸਨੂੰ NTP ਨਾਲ ਸਮਕਾਲੀ ਰੱਖੋ; 5 ਮਿੰਟ ਤੋਂ ਵੱਧ ਗਲਤ ਘੜੀ ਹਰ ਬੇਨਤੀ ਨੂੰ ਅਸਵੀਕਾਰ ਕਰ ਦਿੰਦੀ ਹੈ।

ਪ੍ਰੌਕਸੀ ਪਾਥ ਨੂੰ ਮੁੜ ਲਿਖਦੀ ਹੈ

ਸਾਈਨ ਕੀਤਾ ਪਾਥ ਉਹੀ ਹੈ ਜਿਸਦੀ ਅਸੀਂ ਬੇਨਤੀ ਕੀਤੀ ਸੀ: ਉਸ URL ਦਾ ਪਾਥ ਜੋ ਤੁਸੀਂ ThunderPhone ਵਿੱਚ ਸੰਰਚਿਤ ਕੀਤਾ ਹੈ। ਜੇ ਰਿਵਰਸ ਪ੍ਰੌਕਸੀ ਜਾਂ API ਗੇਟਵੇ ਤੁਹਾਡੀ ਐਪਲੀਕੇਸ਼ਨ ਵੱਲੋਂ ਬੇਨਤੀ ਦੇਖਣ ਤੋਂ ਪਹਿਲਾਂ ਕੋਈ ਪ੍ਰੀਫਿਕਸ ਹਟਾਉਂਦਾ ਜਾਂ ਜੋੜਦਾ ਹੈ, ਤਾਂ ਤਸਦੀਕ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਸਾਡੇ ਭੇਜੇ ਪਾਥ ਨੂੰ ਦੁਬਾਰਾ ਬਣਾਓ (ਉਦਾਹਰਨ ਲਈ, ਪ੍ਰੀਫਿਕਸ ਮੁੜ ਜੋੜੋ)।

ਡਿਫਾਲਟ ਫਾਰਮੈਟਿੰਗ ਨਾਲ ਮੁੜ ਸੀਰੀਅਲਾਈਜ਼ ਕਰਨਾ

ਬਾਡੀ ਨੂੰ ਪਾਰਸ ਕਰਨਾ ਅਤੇ ਤੁਹਾਡੀ JSON ਲਾਇਬ੍ਰੇਰੀ ਦੀਆਂ ਡਿਫਾਲਟ ਸੈਟਿੰਗਾਂ ਨਾਲ ਇਸਨੂੰ ਮੁੜ ਡੰਪ ਕਰਨਾ (, / : ਤੋਂ ਬਾਅਦ ਖਾਲੀ ਥਾਂਵਾਂ, ਇਨਸਰਸ਼ਨ-ਕ੍ਰਮਬੱਧ ਕੁੰਜੀਆਂ) ਵੱਖਰੇ ਬਾਈਟ ਬਣਾਉਂਦਾ ਹੈ ਅਤੇ HMAC ਨੂੰ ਤੋੜ ਦਿੰਦਾ ਹੈ। ਕੱਚੀ ਬਾਡੀ ਦੀ ਤਸਦੀਕ ਕਰੋ — ਜਾਂ ਜੇ ਤੁਹਾਨੂੰ ਮੁੜ ਸੀਰੀਅਲਾਈਜ਼ ਕਰਨਾ ਹੀ ਪਵੇ, ਤਾਂ ਸਾਡੇ ਕੈਨੋਨਿਕਲ ਰੂਪ ਨਾਲ ਬਿਲਕੁਲ ਮੇਲ ਕਰੋ: ਕ੍ਰਮਬੱਧ ਕੁੰਜੀਆਂ, ਸੰਖੇਪ ਵਿਭਾਜਕ, UTF-8।

ਫਰੇਮਵਰਕ ਆਪਣੇ ਆਪ JSON ਪਾਰਸ ਕਰਦਾ ਹੈ

Express ਦਾ express.json() ਮਿਡਲਵੇਅਰ ਬਾਡੀ ਸਟ੍ਰੀਮ ਨੂੰ ਵਰਤ ਲੈਂਦਾ ਹੈ ਅਤੇ ਤੁਸੀਂ ਰਾਅ ਬਾਈਟਾਂ ਗੁਆ ਬੈਠਦੇ ਹੋ। ਖਾਸ ਤੌਰ 'ਤੇ ਵੈੱਬਹੁੱਕ ਰੂਟ ਉੱਤੇ express.raw() ਵਰਤੋ, ਜਾਂ ਪ੍ਰੀ-ਮਿਡਲਵੇਅਰ ਵਿੱਚ ਰਾਅ ਬਾਡੀ ਨੂੰ ਬਫਰ ਕਰੋ। NestJS / Koa ਲਈ ਵੀ ਇਹੀ ਗੱਲ ਹੈ — ਉਹਨਾਂ ਦੇ "ਰਾਅ ਬਾਡੀ" ਦਸਤਾਵੇਜ਼ ਵੇਖੋ।

ਟਾਈਮਿੰਗ-ਅਸੁਰੱਖਿਅਤ ਤੁਲਨਾ

JS ਵਿੱਚ expected === signature ਜਾਂ Python ਵਿੱਚ expected == signature ਟਾਈਮਿੰਗ-ਵੇਰੀਏਬਲ ਤੁਲਨਾਵਾਂ ਹਨ। ਕ੍ਰਮਵਾਰ crypto.timingSafeEqual ਜਾਂ hmac.compare_digest ਵਰਤੋ। ਕਾਰਗੁਜ਼ਾਰੀ ਵਿੱਚ ਅੰਤਰ ਨਾ ਦੇ ਬਰਾਬਰ ਹੈ।

ਟੂਲ ਐਂਡਪੌਇੰਟਾਂ ਲਈ ਗਲਤ ਸੀਕ੍ਰੇਟ

ਸਿੱਧੀਆਂ ਟੂਲ-ਐਂਡਪੌਇੰਟ ਕਾਲਾਂ ਨੂੰ ਸੰਗਠਨ-ਪੱਧਰੀ ਵੈੱਬਹੁੱਕ ਸੀਕ੍ਰੇਟ (GET /v1/webhook) ਨਾਲ ਸਾਈਨ ਕੀਤਾ ਜਾਂਦਾ ਹੈ — ਨਾ ਕਿ /v1/developer/webhook-endpoints ਦੇ ਕਿਸੇ ਪ੍ਰਤੀ-ਐਂਡਪੌਇੰਟ ਸੀਕ੍ਰੇਟ ਨਾਲ। ਉਹੀ ਹੈਲਪਰ ਦੁਬਾਰਾ ਵਰਤੋ, ਪਰ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਸੀਂ ਟੂਲ ਰੂਟਾਂ ਉੱਤੇ ਇਸਨੂੰ ਸੰਗਠਨ ਦਾ ਸੀਕ੍ਰੇਟ ਦਿੰਦੇ ਹੋ।

GET/DELETE ਟੂਲਾਂ ਉੱਤੇ ਕੁਐਰੀ ਸਟਰਿੰਗਾਂ

V2 ਕੁਐਰੀ ਸਟਰਿੰਗ ਨੂੰ ਬਿਲਕੁਲ ਉਸੇ ਤਰ੍ਹਾਂ ਸਾਈਨ ਕਰਦਾ ਹੈ ਜਿਵੇਂ ਉਹ ਭੇਜੀ ਗਈ ਸੀ। ਲੈਗੇਸੀ ਹੈਡਰ ਖਾਲੀ ਬਾਡੀ ਨੂੰ ਸਾਈਨ ਕਰਦਾ ਹੈ ਅਤੇ ਕਦੇ ਵੀ ਕੁਐਰੀ ਸਟਰਿੰਗ ਨੂੰ ਨਹੀਂ, ਇਸ ਲਈ ਲੈਗੇਸੀ ਹੈਡਰ ਲਈ URL ਦਾ ਹੈਸ਼ ਬਣਾਉਣਾ ਕਦੇ ਮੇਲ ਨਹੀਂ ਖਾਏਗਾ।

ਵਿੰਡੋ ਅੰਦਰ ਡੁਪਲੀਕੇਟ

ਟਾਈਮਸਟੈਂਪ 5 ਮਿੰਟ ਤੋਂ ਪੁਰਾਣੇ ਰੀਪਲੇ ਰੋਕਦਾ ਹੈ, ਨਾ ਕਿ ਉਸ ਵਿੰਡੋ ਅੰਦਰ ਦੂਜੀ ਕਾਪੀ ਨੂੰ। ਐਂਡਪੌਇੰਟ ਡਿਲਿਵਰੀਆਂ ਘੱਟੋ-ਘੱਟ ਇੱਕ ਵਾਰ ਹੁੰਦੀਆਂ ਹਨ, ਇਸ ਲਈ ਉਸੇ ਇਵੈਂਟ ਦੀ ਰੀਟ੍ਰਾਈ ਵੀ ਆ ਸਕਦੀ ਹੈ; ਐਨਵਲਪ ਦੇ event_id ਦੇ ਆਧਾਰ ਉੱਤੇ ਦੁਹਰਾਵੇ ਹਟਾਓ।

ਮੇਲ ਨਾ ਹੋਣ ਉੱਤੇ 401 ਵਾਪਸ ਨਾ ਕਰਨਾ

ਅਸਫਲ ਪੁਸ਼ਟੀ ਉੱਤੇ 200 ਵਾਪਸ ਕਰਨ ਨਾਲ ਹੈਂਡਲਰ ਰੀਪਲੇ ਟਾਰਗੇਟ ਬਣ ਜਾਂਦਾ ਹੈ। ਪੁਸ਼ਟੀ ਅਸਫਲ ਹੋਣ 'ਤੇ ਹਮੇਸ਼ਾ ਗੈਰ-2xx ਜਵਾਬ ਦਿਓ।


ਅਗਲੇ ਕਦਮ