Open in
વેબહૂક સહી ચકાસો
ThunderPhone મોકલે છે તે દરેક વેબહૂક અને ટૂલ વિનંતી પર સહી હોય છે. અહીં આપેલી રીતથી સહી એકવાર ચકાસો, પછી તમે ચલાવતા દરેક એન્ડપોઇન્ટ પર એ જ ચકાસણી ફરીથી વાપરો.
અમે તમારા સર્વરને મોકલતી દરેક વિનંતી — webhook ડિલિવરી અને
ટૂલ-એન્ડપોઇન્ટ ઇન્વોકેશન — X-ThunderPhone-Signature હેડરમાં HMAC-SHA256
સિગ્નેચર ધરાવે છે. વેરિફિકેશન એકવાર યોગ્ય રીતે સેટ કરો અને
દરેક હેન્ડલરમાં એ જ હેલ્પર વાપરો.
અલ્ગોરિધમ
- raw રિક્વેસ્ટ બોડી વાંચો — અમે તમને POST કરેલા ચોક્કસ બાઇટ્સ.
hmac_sha256(secret, body).hexdigest()ગણો.X-ThunderPhone-Signatureસાથે constant time માં સરખામણી કરો. (સામાન્ય સ્ટ્રિંગ સરખામણી ટાઇમિંગ માહિતી લીક કરે છે.)
અમે ટ્રાન્સમિટ કરીએ છીએ તે જ ચોક્કસ બાઇટ્સ પર સિગ્નેચર કરીએ છીએ, તેથી raw બોડી
વેરિફાય કરવું હંમેશા કાર્ય કરે છે. તે બાઇટ્સ પેલોડનું canonical JSON serialization
પણ છે — કીઓ મૂળાક્ષર મુજબ સૉર્ટ કરેલી, કોમ્પેક્ટ સેપરેટર્સ
(ખાલી જગ્યાઓ વિનાના , અને :), UTF-8. જ્યારે તમારું ફ્રેમવર્ક માત્ર પાર્સ કરેલું JSON
આપતું હોય ત્યારે આ તમને બીજું, સંપૂર્ણપણે સમકક્ષ રીત આપે છે:
કેનોનિકલ રીતે ફરી સિરિયલાઇઝ કરો અને તેના પર HMAC કરો.
# Equivalent to hashing the raw body:
import json
canonical = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")raw બોડી પસંદ કરો — તેમાં એક પગલું ઓછું છે અને કેટલીક ભાષાઓમાં JSON નંબરના રાઉન્ડ-ટ્રિપિંગની વિચિત્રતાઓથી સુરક્ષિત છે.
કયું સિક્રેટ?
| સ્રોત | સિક્રેટ |
|---|---|
Webhook એન્ડપોઇન્ટ (/v1/developer/webhook-endpoints) | બનાવતી વખતે એકવાર પરત મળતું પ્રતિ-એન્ડપોઇન્ટ secret (48 હેક્સ અક્ષરો) |
| લેગસી સિંગલ-URL webhook | GET /v1/webhook પર પરત મળતું પ્રતિ-org secret |
ટૂલ-એન્ડપોઇન્ટ ઇન્વોકેશન (તમારા endpoint.url પર સીધો કૉલ) | org-સ્તરનું webhook સિક્રેટ (લેગસી સિંગલ-URL webhook જેવું જ) — પ્રતિ-એન્ડપોઇન્ટ સિક્રેટ નહીં |
સિક્રેટને તમારા સિક્રેટ મેનેજર અથવા env var માં સ્ટોર કરો — તેને ક્યારેય કમિટ કરશો નહીં.
સંદર્ભ અમલીકરણો
ચારેય raw રિક્વેસ્ટ બોડીને વેરિફાય કરે છે:
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 હેડર્સ ધરાવે છે:
X-ThunderPhone-Call-ID— લાઇવ કૉલનું આંકડાકીય આઈડી.X-ThunderPhone-Signature— ચોક્કસ વિનંતિ-બોડી બાઇટ્સ પર તમારા સંસ્થા-સ્તરીય વેબહૂક સિક્રેટ વડે કી કરાયેલ HMAC-SHA256.
એ જ verify() હેલ્પર કોઈ ફેરફાર વિના કાર્ય કરે છે, પરંતુ બે ખાસ બાબતો છે:
GET/DELETEટૂલ્સમાં કોઈ બોડી હોતી નથી. આર્ગ્યુમેન્ટ્સ ક્વેરી પેરામીટર્સ તરીકે જાય છે, અને સહીની ગણતરી ખાલી બાઇટ સ્ટ્રિંગ પર થાય છે — એટલેverify(b"", sig, secret)(Python) અથવાverify(Buffer.alloc(0), sig, secret)(Node). ક્વેરી સ્ટ્રિંગને હૅશ ન કરો.- લેગસી વેબહૂક કૉન્ફિગર ન હોય તેવી સંસ્થાઓ પાસે સંસ્થા સિક્રેટ હોતું નથી.
તે સ્થિતિમાં ટૂલ કૉલ્સમાં માત્ર
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 વાપરો. કામગીરીમાં તફાવત
નગણ્ય છે.
ટૂલ એન્ડપૉઇન્ટ્સ માટે ખોટું સિક્રેટ
સીધા ટૂલ-એન્ડપૉઇન્ટ કૉલ્સ પર સંસ્થા-સ્તરનું વેબહૂક
સિક્રેટ (GET /v1/webhook) વડે સહી કરવામાં આવે છે —
/v1/developer/webhook-endpoints ના કોઈપણ પ્રતિ-એન્ડપૉઇન્ટ સિક્રેટથી નહીં. એ જ verify()
ફંક્શન ફરી વાપરો, પરંતુ ટૂલ રૂટ્સ પર તેને સંસ્થાનું સિક્રેટ આપો તેની ખાતરી કરો.
GET/DELETE ટૂલ્સ પર ક્વેરી સ્ટ્રિંગને હૅશ કરવી
બોડી વિનાની ટૂલ મેથડ્સ માટે સહી ખાલી બાઇટ સ્ટ્રિંગને આવરી લે છે, જેથી એક સર્વવ્યાપક રીત જળવાઈ રહે: રૉ રિક્વેસ્ટ બોડીનું HMAC કરો, તે ગમે તે હોય. URL અથવા ક્વેરી સ્ટ્રિંગને હૅશ કરવાથી ક્યારેય મેળ નહીં પડે.
અસંગતતા પર 401 પરત ન કરવું
ચકાસણી નિષ્ફળ જાય ત્યારે 200 પરત કરવાથી હેન્ડલર રીપ્લે લક્ષ્ય બને છે. ચકાસણી નિષ્ફળ જાય તો હંમેશા નૉન-2xx પ્રતિસાદ આપો.