Open in
വെബ്ഹുക്ക് സിഗ്നേച്ചറുകൾ പരിശോധിക്കുക
ThunderPhone അയയ്ക്കുന്ന എല്ലാ വെബ്ഹുക്ക്, ടൂൾ അഭ്യർത്ഥനകളും സൈൻ ചെയ്തതാണ്. ഇവിടെ നൽകിയിരിക്കുന്ന റെസിപ്പി ഉപയോഗിച്ച് ഒരിക്കൽ സിഗ്നേച്ചർ പരിശോധിക്കുക, തുടർന്ന് നിങ്ങൾ പ്രവർത്തിപ്പിക്കുന്ന എല്ലാ എൻഡ്പോയിന്റുകളിലും അതേ പരിശോധന വീണ്ടും ഉപയോഗിക്കുക.
നിങ്ങളുടെ സെർവറിലേക്ക് ഞങ്ങൾ അയയ്ക്കുന്ന ഓരോ അഭ്യർത്ഥനയ്ക്കും — webhook ഡെലിവറികൾക്കും
tool-endpoint ഇൻവോക്കേഷനുകൾക്കും — X-ThunderPhone-Signature ഹെഡറിൽ ഒരു HMAC-SHA256 ഒപ്പുണ്ടാകും. ഒരിക്കൽ ശരിയായി പരിശോധിച്ച്
അതേ helper എല്ലാ handler-കളിലും ചേർക്കുക.
അൽഗോരിതം
- raw request body വായിക്കുക — ഞങ്ങൾ നിങ്ങൾക്ക് POST ചെയ്ത കൃത്യമായ bytes.
hmac_sha256(secret, body).hexdigest()കണക്കാക്കുക.X-ThunderPhone-Signature-നോട് constant time-ൽ താരതമ്യം ചെയ്യുക. (സാധാരണ string താരതമ്യം timing വിവരങ്ങൾ ചോർത്തും.)
ഞങ്ങൾ കൈമാറുന്ന കൃത്യമായ bytes-ലാണ് ഒപ്പിടുന്നത്, അതിനാൽ raw body
പരിശോധിക്കുന്നത് എല്ലായ്പ്പോഴും പ്രവർത്തിക്കും. ആ bytes payload-ന്റെ canonical JSON serialization കൂടിയാണ് —
കീകൾ അക്ഷരമാലാക്രമത്തിൽ sort ചെയ്തത്, compact separators
(സ്പേസുകളില്ലാത്ത ,, :), UTF-8. നിങ്ങളുടെ framework parsed JSON മാത്രം നൽകുന്നുണ്ടെങ്കിൽ,
ഇതിന് പൂർണമായും തുല്യമായ രണ്ടാമത്തെ രീതിയുണ്ട്:
canonical ആയി വീണ്ടും serialize ചെയ്ത് അതിൽ HMAC നടത്തുക.
# Equivalent to hashing the raw body:
import json
canonical = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")raw body മുൻഗണന നൽകുക — ഇത് ഒരു ഘട്ടം കുറവാണ്, കൂടാതെ ചില ഭാഷകളിലെ JSON number round-tripping പ്രത്യേകതകളിൽ നിന്ന് സംരക്ഷിതവുമാണ്.
ഏത് secret?
| ഉറവിടം | Secret |
|---|---|
Webhook endpoint (/v1/developer/webhook-endpoints) | സൃഷ്ടിക്കുമ്പോൾ ഒരിക്കൽ നൽകുന്ന ഓരോ endpoint-നുമുള്ള secret (48 hex chars) |
| Legacy single-URL webhook | GET /v1/webhook-ൽ ലഭിക്കുന്ന ഓരോ org-നുമുള്ള secret |
Tool-endpoint invocation (നിങ്ങളുടെ endpoint.url-ലേക്കുള്ള നേരിട്ടുള്ള കോൾ) | org-level webhook secret (legacy single-URL webhook-ലേതിന് സമാനം) — endpoint-നുള്ള secret അല്ല |
secret നിങ്ങളുടെ secret manager-ലോ env var-ലോ സൂക്ഷിക്കുക — ഒരിക്കലും commit ചെയ്യരുത്.
റഫറൻസ് ഇംപ്ലിമെന്റേഷനുകൾ
നാലും raw request body പരിശോധിക്കുന്നു:
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-അല്ലാത്ത പ്രതികരണം നൽകുക.
അടുത്ത ഘട്ടങ്ങൾ
ഡെലിവറി സെമാന്റിക്സ്, റീട്രൈകൾ, ഉറവിട IP-കൾ.
ഒന്നിലധികം URL-കൾ കൈകാര്യം ചെയ്യുക, സീക്രട്ടുകൾ റൊട്ടേറ്റ് ചെയ്യുക.
രണ്ട് ടൂൾ-ഇൻവൊക്കേഷൻ പാതകളും അവയുടെ റിക്വസ്റ്റ് രൂപങ്ങളും.
ടൂൾ പിന്തുണയുള്ള ഒരു സമ്പൂർണ ഇന്റഗ്രേഷൻ തുടക്കം മുതൽ അവസാനം വരെ നിർമ്മിക്കുക.