Open in
वेबहुक स्वाक्षऱ्या सत्यापित करा
ThunderPhone पाठवत असलेल्या प्रत्येक वेबहुक आणि टूल विनंतीवर स्वाक्षरी केलेली असते. येथे दिलेल्या पद्धतीने स्वाक्षरी एकदा सत्यापित करा, त्यानंतर तुम्ही चालवत असलेल्या प्रत्येक एंडपॉइंटवर तीच तपासणी पुन्हा वापरा.
आम्ही तुमच्या सर्व्हरला पाठवणाऱ्या प्रत्येक विनंतीमध्ये — webhook वितरणे आणि
टूल-एंडपॉइंट इनव्होकेशन्समध्ये — X-ThunderPhone-Signature हेडरमध्ये
HMAC-SHA256 स्वाक्षरी असते. पडताळणी एकदाच योग्यरीत्या करा आणि
प्रत्येक हँडलरमध्ये तोच हेल्पर वापरा.
अल्गोरिदम
- कच्चा विनंती बॉडी वाचा — आम्ही तुम्हाला POST केलेले अचूक बाइट्स.
hmac_sha256(secret, body).hexdigest()गणना करा.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 | GET /v1/webhook वर परत मिळणारे प्रत्येक ऑर्गनायझेशनसाठीचे secret |
टूल-एंडपॉइंट इनव्होकेशन (तुमच्या endpoint.url ला थेट कॉल) | ऑर्गनायझेशन-स्तरीय webhook गुप्त की (लेगसी सिंगल-URL webhook प्रमाणेच) — प्रत्येक एंडपॉइंटची गुप्त की नाही |
गुप्त की तुमच्या सीक्रेट मॅनेजरमध्ये किंवा env var मध्ये साठवा — ती कधीही कमिट करू नका.
संदर्भ अंमलबजावण्या
सर्व चार कच्च्या विनंती बॉडीची पडताळणी करतात:
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— अचूक विनंती-बॉडी बाइट्सवर तुमच्या संस्था-स्तरीय webhook गुपिताने की केलेला HMAC-SHA256.
तोच verify() हेल्पर कोणताही बदल न करता कार्य करतो, पण दोन बाबी लक्षात घ्या:
GET/DELETEटूल्सना बॉडी नसते. आर्ग्युमेंट्स क्वेरी पॅरामीटर्स म्हणून जातात आणि स्वाक्षरीची गणना रिकाम्या बाइट स्ट्रिंगवर केली जाते — म्हणजेverify(b"", sig, secret)(Python) किंवाverify(Buffer.alloc(0), sig, secret)(Node). क्वेरी स्ट्रिंग हॅश करू नका.- लेगसी webhook कॉन्फिगर नसलेल्या संस्थांकडे संस्था गुपित नसते. अशा
परिस्थितीत टूल कॉल्समध्ये फक्त
X-ThunderPhone-Call-IDअसतो आणि स्वाक्षरी हेडर नसतो. स्वाक्षरीकरण गुपित मिळवण्यासाठी लेगसी 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 नसलेली आणि तुमच्या संस्था 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 वापरा. कार्यक्षमतेतील फरक
नगण्य आहे.
टूल एंडपॉइंट्ससाठी चुकीचा सीक्रेट
थेट टूल-एंडपॉइंट कॉल्सवर org-स्तरीय वेबहुक
सीक्रेट (GET /v1/webhook) सह साइन केले जाते —
/v1/developer/webhook-endpoints मधील कोणत्याही प्रति-एंडपॉइंट सीक्रेटसह नाही. तीच verify()
फंक्शन पुन्हा वापरा, पण टूल रूट्सवर तुम्ही त्याला org सीक्रेट देता याची खात्री करा.
GET/DELETE टूल्सवरील क्वेरी स्ट्रिंग हॅश करणे
बॉडी नसलेल्या टूल मेथड्ससाठी सिग्नेचर रिकाम्या बाइट स्ट्रिंगला कव्हर करते, त्यामुळे एकच सार्वत्रिक पद्धत कायम राहते: रॉ रिक्वेस्ट बॉडीचे HMAC करा, ती काहीही असो. URL किंवा क्वेरी स्ट्रिंग हॅश केल्यास ते कधीही जुळणार नाही.
न जुळल्यास 401 परत न करणे
सत्यापन अयशस्वी झाल्यावर 200 परत केल्यास हँडलर रिप्ले टार्गेट बनतो. सत्यापन अयशस्वी झाल्यास नेहमी non-2xx प्रतिसाद द्या.