வெப்ஹுக் கையொப்பங்களைச் சரிபார்க்கவும்
நாங்கள் உங்கள் சர்வருக்கு அனுப்பும் ஒவ்வொரு கோரிக்கையும் — 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-இல் சேமிக்கவும் — அதை ஒருபோதும் 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 ஆக அனுப்பப்படுபவை) ஒரு வழக்கமான கையொப்பமிடப்பட்ட வெப்ஹுக் ஆகும் — மேலுள்ள நிலையான முறையைப் பயன்படுத்தவும். இரண்டு கோரிக்கை வடிவங்களுக்கும் செயல்பாட்டு கருவிகள் பக்கத்தைப் பார்க்கவும்.
பொதுவான சிக்கல்கள்
இயல்புநிலை வடிவமைப்புடன் மீண்டும் வரிசைப்படுத்துதல்
body-ஐ parse செய்து, உங்கள் JSON library-இன்
இயல்புநிலைகளுடன் (, / : க்குப் பின் இடைவெளிகள், சேர்க்கை வரிசையிலான keys) மீண்டும் dump செய்வது
வேறுபட்ட bytes-ஐ உருவாக்கி HMAC-ஐ செயலிழக்கச் செய்கிறது. raw body-ஐச் சரிபார்க்கவும் — அல்லது நீங்கள் மீண்டும் வரிசைப்படுத்த வேண்டியிருந்தால்,
எங்கள் canonical வடிவத்துடன் துல்லியமாகப் பொருந்தச் செய்யவும்: வரிசைப்படுத்தப்பட்ட
keys, சுருக்கமான பிரிப்பான்கள், UTF-8.
Framework தானாகவே JSON-ஐ parse செய்கிறது
Express-இன் express.json() middleware body stream-ஐ
பயன்படுத்திவிடும்; raw bytes-ஐ நீங்கள் இழந்துவிடுவீர்கள். webhook
route-இல் குறிப்பாக express.raw()-ஐப் பயன்படுத்தவும், அல்லது pre-middleware-இல் raw body-ஐ buffer செய்யவும்.
NestJS / Koa-க்கும் இதே நிலைதான் — அவற்றின் "raw body" ஆவணங்களைப் பார்க்கவும்.
Timing-பாதுகாப்பற்ற ஒப்பீடு
JS-இல் expected === signature அல்லது
Python-இல் expected == signature என்பவை timing-மாறுபடும் ஒப்பீடுகள். முறையே crypto.timingSafeEqual
அல்லது hmac.compare_digest-ஐப் பயன்படுத்தவும். செயல்திறன் வேறுபாடு
இல்லை.
Tool endpoint-களுக்கான தவறான secret
நேரடி tool-endpoint அழைப்புகள் org-நிலை webhook
secret (GET /v1/webhook) மூலம் கையொப்பமிடப்படுகின்றன — /v1/developer/webhook-endpoints-இலுள்ள
ஒவ்வொரு endpoint-க்கான secret மூலமாக அல்ல. அதே verify()
function-ஐ மீண்டும் பயன்படுத்தவும், ஆனால் tool route-களில் அதற்கு org secret-ஐ வழங்குவதை உறுதிசெய்யவும்.
GET/DELETE tool-களில் query string-ஐ hash செய்தல்
body இல்லாத tool method-களில் கையொப்பம் காலியான byte string-ஐ உள்ளடக்கும்; இதனால் ஒரே பொதுவான நடைமுறை தொடர்கிறது: raw request body எதுவாக இருந்தாலும், அதற்கு HMAC செய்யவும். URL அல்லது query string-ஐ hash செய்வது ஒருபோதும் பொருந்தாது.
பொருந்தாதபோது 401-ஐத் திருப்பி அனுப்பாதது
சரிபார்ப்பு தோல்வியடைந்தபோது 200-ஐத் திருப்பி அனுப்புவது handler-ஐ replay இலக்காக மாற்றுகிறது. சரிபார்ப்பு தோல்வியடைந்தால் எப்போதும் 2xx அல்லாத பதிலை அனுப்பவும்.
அடுத்த படிகள்
விநியோக அர்த்தவியல், மீண்டும் முயற்சிகள், மூல IP-கள்.
பல URL-களை நிர்வகிக்கவும், secret-களை மாற்றவும்.
இரண்டு tool-invocation பாதைகள் மற்றும் அவற்றின் request வடிவங்கள்.
முழுமையான tool-ஆதரவு கொண்ட ஒருங்கிணைப்பை தொடக்கம் முதல் முடிவு வரை உருவாக்கவும்.