વેબહૂક સહીની ચકાસણી કરો

અમે તમારા સર્વરને મોકલીએ છીએ તે દરેક વિનંતી — webhook ડિલિવરી અને ટૂલ-એન્ડપોઇન્ટ ઇન્વોકેશન — 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 નંબરના રાઉન્ડ-ટ્રિપિંગની વિચિત્રતાઓથી મુક્ત છે.

કયું સિક્રેટ?

સ્રોતસિક્રેટ
Webhook એન્ડપોઇન્ટ (/v1/developer/webhook-endpoints)દરેક એન્ડપોઇન્ટ માટેનું secret (48 હેક્સ અક્ષરો), બનાવતી વખતે એક વાર પરત મળે છે
લેગસી સિંગલ-URL webhookદરેક સંસ્થા માટેનું secret, GET /v1/webhook પર પરત મળે છે
ટૂલ-એન્ડપોઇન્ટ ઇન્વોકેશન (તમારા endpoint.url પર સીધો કૉલ)સંસ્થા-સ્તરનું webhook સિક્રેટ (લેગસી સિંગલ-URL webhook જેવું જ) — દરેક એન્ડપોઇન્ટનું સિક્રેટ નહીં

સિક્રેટને તમારા સિક્રેટ મેનેજર અથવા env varમાં સંગ્રહિત કરો — તેને ક્યારેય commit ન કરો.

સંદર્ભ અમલીકરણો

ચારેય કાચા રિક્વેસ્ટ બોડીની ચકાસણી કરે છે:

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. legacy webhook કન્ફિગર કરેલ ન હોય તેવી orgs પાસે org secret હોતું નથી. તે કિસ્સામાં ટૂલ કૉલ્સમાં માત્ર X-ThunderPhone-Call-ID હોય છે અને કોઈ સિગ્નેચર હેડર હોતું નથી. સાઇનિંગ secret મેળવવા માટે legacy webhook (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)
    ...

Webhook-મોડ ટૂલ ડિસ્પૅચ (endpoint વિનાના ટૂલ્સ, જે તમારી org webhook પર telephony.tool / web.tool તરીકે ડિલિવર થાય છે) સામાન્ય સાઇન કરાયેલ webhook છે — ઉપર આપેલી માનક રીત લાગુ પડે છે. બંને વિનંતિ આકારો માટે ફંક્શન ટૂલ્સ જુઓ.

સામાન્ય ખામીઓ

ડિફૉલ્ટ ફોર્મેટિંગ સાથે ફરીથી સિરિયલાઇઝ કરવું

બોડી પાર્સ કરીને તેને તમારી 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માંથી મળતા કોઈ પણ પ્રતિ-એન્ડપોઇન્ટ સિક્રેટ વડે નહીં. એ જ verify() ફંક્શન ફરીથી વાપરો, પરંતુ ખાતરી કરો કે ટૂલ રૂટ્સ પર તમે તેને સંસ્થાનું સિક્રેટ આપો છો.

GET/DELETE ટૂલ્સ પર ક્વેરી સ્ટ્રિંગ હૅશ કરવી

બોડી વિનાની ટૂલ મેથડ્સ માટે સહી ખાલી બાઇટ સ્ટ્રિંગને આવરી લે છે, જેથી એક સર્વવ્યાપક રીત જળવાઈ રહે: કાચી રિક્વેસ્ટ બોડીનું HMAC કરો, તે જે હોય તે. URL અથવા ક્વેરી સ્ટ્રિંગ હૅશ કરવાથી ક્યારેય મેળ નહીં આવે.

મેળ ન ખાય ત્યારે 401 પરત ન કરવું

ચકાસણી નિષ્ફળ જાય ત્યારે 200 પરત કરવાથી હેન્ડલર રીપ્લે લક્ષ્ય બની જાય છે. ચકાસણી નિષ્ફળ જાય તો હંમેશા નૉન-2xx પ્રતિસાદ આપો.


આગળનાં પગલાં