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

ਤੁਹਾਡੇ ਸਰਵਰ ਨੂੰ ਭੇਜੀ ਹਰ ਬੇਨਤੀ — ਵੈੱਬਹੁੱਕ ਡਿਲਿਵਰੀਆਂ ਅਤੇ ਟੂਲ-ਐਂਡਪੁਆਇੰਟ ਇਨਵੋਕੇਸ਼ਨਾਂ — ਵਿੱਚ X-ThunderPhone-Signature ਹੈਡਰ ਅੰਦਰ ਇੱਕ HMAC-SHA256 ਸਿਗਨੇਚਰ ਹੁੰਦਾ ਹੈ। ਵੇਰੀਫਿਕੇਸ਼ਨ ਇੱਕ ਵਾਰ ਸਹੀ ਕਰੋ ਅਤੇ ਉਹੀ ਹੈਲਪਰ ਹਰ ਹੈਂਡਲਰ ਵਿੱਚ ਵਰਤੋ।

ਐਲਗੋਰਿਦਮ

  1. ਕੱਚੀ ਰਿਕਵੈਸਟ ਬਾਡੀ ਪੜ੍ਹੋ — ਬਿਲਕੁਲ ਉਹੀ ਬਾਈਟਾਂ ਜੋ ਅਸੀਂ ਤੁਹਾਨੂੰ POST ਕੀਤੀਆਂ ਹਨ।
  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 ਨੰਬਰਾਂ ਦੀ ਰਾਊਂਡ-ਟ੍ਰਿਪਿੰਗ ਦੀਆਂ ਵਿਲੱਖਣਤਾਵਾਂ ਤੋਂ ਪ੍ਰਭਾਵਿਤ ਨਹੀਂ ਹੁੰਦੀ।

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

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

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

ਰੈਫਰੈਂਸ ਇੰਪਲੀਮੈਂਟੇਸ਼ਨਾਂ

ਸਾਰੀਆਂ ਚਾਰ ਕੱਚੀ ਰਿਕਵੈਸਟ ਬਾਡੀ ਦੀ ਪੁਸ਼ਟੀ ਕਰਦੀਆਂ ਹਨ:

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

def verify(body, signature, secret)
  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
  Rack::Utils.secure_compare(expected, signature.to_s)
end

ਫ੍ਰੇਮਵਰਕ-ਵਿਸ਼ੇਸ਼ ਵਾਇਰਿੰਗ

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

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

ਜਦੋਂ ਏਜੰਟ ਤੁਹਾਡੇ ਕਿਸੇ ਫੰਕਸ਼ਨ ਟੂਲ ਨੂੰ ਸਿੱਧਾ ਚਲਾਉਂਦਾ ਹੈ (ਟੂਲ ਕੋਲ ਇੱਕ endpoint ਹੁੰਦਾ ਹੈ), ਤਾਂ ਬੇਨਤੀ ਤੁਹਾਡੇ ਕਨਫ਼ਿਗਰ ਕੀਤੇ endpoint.headers ਦੇ ਨਾਲ ਦੋ ThunderPhone ਹੈਡਰ ਲੈ ਕੇ ਆਉਂਦੀ ਹੈ:

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

ਆਮ ਖਾਮੀਆਂ

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

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

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

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

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

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

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

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

GET/DELETE ਟੂਲਾਂ ਉੱਤੇ ਕਵੇਰੀ ਸਟ੍ਰਿੰਗ ਨੂੰ ਹੈਸ਼ ਕਰਨਾ

ਬਾਡੀ-ਰਹਿਤ ਟੂਲ ਮੈਥਡਾਂ ਲਈ ਸਿਗਨੇਚਰ ਖਾਲੀ ਬਾਈਟ ਸਟ੍ਰਿੰਗ ਨੂੰ ਕਵਰ ਕਰਦਾ ਹੈ, ਜਿਸ ਨਾਲ ਇੱਕ ਸਰਵਭੌਮ ਵਿਧੀ ਬਣੀ ਰਹਿੰਦੀ ਹੈ: ਰਿਕਵੇਸਟ ਦੀ ਰਾਅ ਬਾਡੀ ਦਾ HMAC ਕਰੋ, ਜੋ ਵੀ ਉਹ ਹੋਵੇ। URL ਜਾਂ ਕਵੇਰੀ ਸਟ੍ਰਿੰਗ ਨੂੰ ਹੈਸ਼ ਕਰਨਾ ਕਦੇ ਵੀ ਮੇਲ ਨਹੀਂ ਖਾਏਗਾ।

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

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


ਅਗਲੇ ਕਦਮ