ವೆಬ್‌ಹುಕ್ ಸಹಿಗಳನ್ನು ಪರಿಶೀಲಿಸಿ

ನಾವು ನಿಮ್ಮ ಸರ್ವರ್‌ಗೆ ಕಳುಹಿಸುವ ಪ್ರತಿಯೊಂದು ವಿನಂತಿ — ವೆಬ್‌ಹುಕ್ ವಿತರಣೆಗಳು ಮತ್ತು ಟೂಲ್-ಎಂಡ್‌ಪಾಯಿಂಟ್ ಕರೆಯುವಿಕೆಗಳು — 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 ಸಂಖ್ಯೆ ರೌಂಡ್-ಟ್ರಿಪ್‌ನ ವೈಶಿಷ್ಟ್ಯಗಳಿಂದ ಸುರಕ್ಷಿತವಾಗಿದೆ.

ಯಾವ ಸೀಕ್ರೆಟ್?

ಮೂಲಸೀಕ್ರೆಟ್
ವೆಬ್‌ಹುಕ್ ಎಂಡ್‌ಪಾಯಿಂಟ್ (/v1/developer/webhook-endpoints)ರಚಿಸುವಾಗ ಒಮ್ಮೆ ಹಿಂತಿರುಗುವ ಪ್ರತಿ-ಎಂಡ್‌ಪಾಯಿಂಟ್ secret (48 ಹೆಕ್ಸ್ ಅಕ್ಷರಗಳು)
ಲೆಗಸಿ ಏಕ-URL ವೆಬ್‌ಹುಕ್GET /v1/webhook ನಲ್ಲಿ ಹಿಂತಿರುಗುವ ಪ್ರತಿ-ಸಂಸ್ಥೆಯ secret
ಟೂಲ್-ಎಂಡ್‌ಪಾಯಿಂಟ್ ಕರೆಯುವಿಕೆ (ನಿಮ್ಮ endpoint.url ಗೆ ನೇರ ಕರೆ)ಸಂಸ್ಥೆ-ಮಟ್ಟದ ವೆಬ್‌ಹುಕ್ ಸೀಕ್ರೆಟ್ (ಲೆಗಸಿ ಏಕ-URL ವೆಬ್‌ಹುಕ್‌ನಲ್ಲಿರುವುದೇ) — ಪ್ರತಿ-ಎಂಡ್‌ಪಾಯಿಂಟ್ ಸೀಕ್ರೆಟ್ ಅಲ್ಲ

ಸೀಕ್ರೆಟ್ ಅನ್ನು ನಿಮ್ಮ ಸೀಕ್ರೆಟ್ ಮ್ಯಾನೇಜರ್ ಅಥವಾ 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 ಹೆಡರ್‌ಗಳನ್ನು ಹೊಂದಿರುತ್ತದೆ:

ಅದೇ 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 ಆಗಿ ತಲುಪಿಸಲಾಗುತ್ತದೆ) ಸಾಮಾನ್ಯ ಸಹಿ ಮಾಡಲಾದ ವೆಬ್‌ಹುಕ್ ಆಗಿದೆ — ಮೇಲಿನ ಪ್ರಮಾಣಿತ ವಿಧಾನ ಅನ್ವಯಿಸುತ್ತದೆ. ಎರಡೂ ವಿನಂತಿ ಸ್ವರೂಪಗಳಿಗಾಗಿ ಫಂಕ್ಷನ್ ಟೂಲ್‌ಗಳು ನೋಡಿ.

ಸಾಮಾನ್ಯ ದೋಷಗಳು

ಡೀಫಾಲ್ಟ್ ಫಾರ್ಮ್ಯಾಟಿಂಗ್‌ನೊಂದಿಗೆ ಮರು-ಸೀರಿಯಲೈಸ್ ಮಾಡುವುದು

ಬಾಡಿಯನ್ನು ಪಾರ್ಸ್ ಮಾಡಿ ನಿಮ್ಮ 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 ಅಲ್ಲದ ಪ್ರತಿಕ್ರಿಯೆ ನೀಡಿ.


ಮುಂದಿನ ಹಂತಗಳು