വെബ്ഹുക്ക് സിഗ്നേച്ചറുകൾ സ്ഥിരീകരിക്കുക
നിങ്ങളുടെ സെർവറിലേക്ക് ഞങ്ങൾ അയയ്ക്കുന്ന ഓരോ അഭ്യർത്ഥനയും — webhook ഡെലിവറികളും
tool-endpoint ഇൻവൊക്കേഷനുകളും — 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 |
Tool-endpoint ഇൻവൊക്കേഷൻ (നിങ്ങളുടെ 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 ഹെഡറുകൾ ഉണ്ടാകും:
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 അല്ലെങ്കിൽ
പൈത്തണിലെ expected == signature ടൈമിംഗ്-വേരിയബിൾ താരതമ്യങ്ങളാണ്. യഥാക്രമം crypto.timingSafeEqual
അല്ലെങ്കിൽ hmac.compare_digest ഉപയോഗിക്കുക. പ്രകടനത്തിലെ വ്യത്യാസം നിസ്സാരമാണ്.
ടൂൾ എൻഡ്പോയിന്റുകൾക്കുള്ള തെറ്റായ സീക്രട്ട്
നേരിട്ടുള്ള ടൂൾ-എൻഡ്പോയിന്റ് കോളുകൾ ഓർഗ്-തല വെബ്ഹുക്ക്
സീക്രട്ടിൽ (GET /v1/webhook) ഉപയോഗിച്ചാണ് സൈൻ ചെയ്യുന്നത് —
/v1/developer/webhook-endpoints-ൽ നിന്നുള്ള ഓരോ എൻഡ്പോയിന്റിനുമുള്ള സീക്രട്ടുകൾ ഉപയോഗിച്ചല്ല. അതേ verify()
ഫംഗ്ഷൻ പുനരുപയോഗിക്കുക, എന്നാൽ ടൂൾ റൂട്ടുകളിൽ അതിന് ഓർഗ് സീക്രട്ട് നൽകുന്നുവെന്ന് ഉറപ്പാക്കുക.
GET/DELETE ടൂളുകളിൽ ക്വറി സ്ട്രിംഗ് ഹാഷ് ചെയ്യുന്നത്
ബോഡി ഇല്ലാത്ത ടൂൾ മെത്തഡുകൾക്കായി സിഗ്നേച്ചർ ശൂന്യ ബൈറ്റ് സ്ട്രിംഗിനെയാണ് ഉൾക്കൊള്ളുന്നത്, അതുവഴി ഒരേയൊരു സാർവത്രിക രീതി നിലനിർത്തുന്നു: റോ റിക്വസ്റ്റ് ബോഡി എന്തായാലും അതിന്റെ HMAC കണക്കാക്കുക. URL അല്ലെങ്കിൽ ക്വറി സ്ട്രിംഗ് ഹാഷ് ചെയ്യുന്നത് ഒരിക്കലും പൊരുത്തപ്പെടില്ല.
പൊരുത്തക്കേടിൽ 401 മടക്കി നൽകാതിരിക്കുന്നത്
പരിശോധന പരാജയപ്പെട്ടിട്ടും 200 മടക്കി നൽകുന്നത് ഹാൻഡ്ലറെ റീപ്ലേ ലക്ഷ്യമാക്കുന്നു. പരിശോധന പരാജയപ്പെട്ടാൽ എപ്പോഴും non-2xx പ്രതികരണം നൽകുക.
അടുത്ത ഘട്ടങ്ങൾ
ഡെലിവറി സെമാന്റിക്സ്, റീട്രൈകൾ, സോഴ്സ് IP-കൾ.
ഒന്നിലധികം URL-കൾ നിയന്ത്രിക്കുക, സീക്രട്ടുകൾ റൊട്ടേറ്റ് ചെയ്യുക.
രണ്ട് ടൂൾ-ഇൻവൊക്കേഷൻ പാതകളും അവയുടെ റിക്വസ്റ്റ് രൂപങ്ങളും.
പൂർണ്ണമായ ടൂൾ-അധിഷ്ഠിത ഇന്റഗ്രേഷൻ അറ്റംമുതൽ അറ്റംവരെ നിർമ്മിക്കുക.