வெப்ஹுக் கையொப்பங்களைச் சரிபார்க்கவும்

நாங்கள் உங்கள் சர்வருக்கு அனுப்பும் ஒவ்வொரு கோரிக்கையும் — webhook விநியோகங்கள் மற்றும் கருவி எண்ட்பாயிண்ட் அழைப்புகள் — X-ThunderPhone-Signature ஹெடரில் ஒரு HMAC-SHA256 கையொப்பத்தைக் கொண்டிருக்கும். சரிபார்ப்பை ஒருமுறை சரியாகச் செய்து, அதே உதவியாளரை ஒவ்வொரு ஹேண்ட்லரிலும் இணைக்கவும்.

அல்காரிதம்

  1. மூல கோரிக்கை உடலைப் படிக்கவும் — நாங்கள் உங்களுக்கு POST செய்த துல்லியமான பைட்டுகள்.
  2. hmac_sha256(secret, body).hexdigest()-ஐக் கணக்கிடவும்.
  3. 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 webhookGET /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 தலைப்புகளும் இடம்பெறும்:

அதே verify() உதவி மாற்றமின்றி செயல்படும், ஆனால் இரண்டு வேறுபாடுகள் உள்ளன:

  1. GET / DELETE கருவிகளுக்கு உடல் இல்லை. வாதங்கள் வினவல் அளவுருக்களாகச் செல்லும்; கையொப்பம் வெற்று பைட் சரம் மீது கணக்கிடப்படும் — எனவே verify(b"", sig, secret) (Python) அல்லது verify(Buffer.alloc(0), sig, secret) (Node) பயன்படுத்தவும். வினவல் சரத்தை ஹாஷ் செய்ய வேண்டாம்.
  2. பழைய வெப்ஹுக் கட்டமைக்கப்படாத நிறுவனங்களுக்கு நிறுவனம் ரகசியம் இருக்காது. அந்நிலையில் கருவி அழைப்புகளில் 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 அல்லாத பதிலை அனுப்பவும்.


அடுத்த படிகள்