Megérkezett a ThunderPhone 2.0.Önkiszolgáló használat már 2 cent/perctől.Olvassa el a bejelentést

Operations

Webhook-aláírások ellenőrzése

A ThunderPhone által küldött minden webhook- és eszközkérés aláírt. Ellenőrizze az aláírást egyszer az itt található útmutatóval, majd használja újra ugyanezt az ellenőrzést minden futtatott végponton.

Minden, a szerverére küldött kérésünk — webhook-kézbesítés és eszközvégpont-meghívás — HMAC-SHA256-aláírást tartalmaz a X-ThunderPhone-Signature fejlécben. Állítsa be egyszer helyesen az ellenőrzést, majd használja ugyanazt a segédfüggvényt minden kezelőben.

Az algoritmus

  1. Olvassa be a kérés nyers törzsét — azokat a pontos bájtokat, amelyeket POST-kéréssel küldtünk Önnek.
  2. Számítsa ki: hmac_sha256(secret, body).hexdigest().
  3. Hasonlítsa össze konstans időben az X-ThunderPhone-Signature értékével. (Az egyszerű sztring-összehasonlítás időzítési információkat szivárogtat.)

Pontosan azokat a bájtokat írjuk alá, amelyeket továbbítunk, ezért a nyers törzs ellenőrzése mindig működik. Ezek a bájtok egyben a hasznos adat kanonikus JSON-szerializációját is jelentik — a kulcsok ábécérendben vannak, az elválasztók tömörek (szóköz nélkül: , és :), a kódolás pedig UTF-8. Ez egy második, teljesen egyenértékű megoldást ad, ha a keretrendszere csak a feldolgozott JSON-t teszi elérhetővé: szerializálja újra kanonikusan, majd erre számítsa a HMAC-et.

# Equivalent to hashing the raw body:
import json
canonical = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")

Részesítse előnyben a nyers törzset — ezzel egy lépéssel kevesebb, és nem érintik az egyes nyelvekben előforduló JSON-számok oda-vissza szerializálásának sajátosságai.

Melyik titkos kulcs?

ForrásTitkos kulcs
Webhook-végpont (/v1/developer/webhook-endpoints)A végpontonkénti secret (48 hexadecimális karakter), amelyet a rendszer egyszer, a létrehozáskor ad vissza
Régi, egy URL-es webhookA szervezetenkénti secret, amelyet a GET /v1/webhook ad vissza
Eszközvégpont-meghívás (közvetlen hívás az Ön endpoint.url címére)A szervezeti szintű webhook-titkos kulcs (ugyanaz, mint a régi, egy URL-es webhook esetén) — nem végpontonkénti titkos kulcs

Tárolja a titkos kulcsot a titkoskulcs-kezelőjében vagy környezeti változóban — soha ne commitolja.

Referenciamegvalósítások

Mind a négy a nyers kéréstörzset ellenőrzi:

Python
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 "")
Node.js
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),
  );
}
Go
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))
}
Ruby
require "openssl"
 
def verify(body, signature, secret)
  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, body)
  Rack::Utils.secure_compare(expected, signature.to_s)
end

Keretrendszer-specifikus integráció

FastAPI
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}
Express
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);
  },
);
Django
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})

Eszközhívások ellenőrzése

Amikor az ügynök közvetlenül meghívja valamelyik függvényeszközét (az eszköz rendelkezik endpoint értékkel), a kérés az Ön konfigurált endpoint.headers értékei mellett két ThunderPhone fejlécet is tartalmaz:

  • X-ThunderPhone-Call-ID — az élő hívás numerikus azonosítója.
  • X-ThunderPhone-Signature — HMAC-SHA256, amelynek kulcsa az Ön szervezeti szintű webhooktitka, és amely a kérés törzsének pontos bájtjaira van számítva.

Ugyanaz a verify() segédfüggvény változatlanul használható, két eltéréssel:

  1. A GET / DELETE eszközöknek nincs törzsük. Az argumentumok lekérdezési paraméterekként érkeznek, az aláírás pedig az üres bájtsorozatra számítódik — tehát verify(b"", sig, secret) (Python), illetve verify(Buffer.alloc(0), sig, secret) (Node) használata szükséges. Ne hashelje a lekérdezési karakterláncot.
  2. Az örökölt webhookot nem konfiguráló szervezeteknek nincs szervezeti titkuk. Ebben az esetben az eszközhívások csak az X-ThunderPhone-Call-ID fejlécet tartalmazzák, aláírási fejlécet nem. Konfigurálja az örökölt webhookot (PUT /v1/webhook), hogy aláíró titkot kapjon, vagy hitelesítse az eszközhívásokat saját fejléccel az endpoint.headers használatával.
@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)
    ...

A webhook-módú eszközdiszpécselés (az endpoint nélküli eszközök, amelyek telephony.tool / web.tool formájában érkeznek az Ön szervezeti webhookjára) hagyományos aláírt webhook — a fenti standard eljárás alkalmazandó. Mindkét kérésformátumról lásd: Függvényeszközök.

Gyakori buktatók

Újraszerializálás alapértelmezett formázással

A törzs elemzése, majd újra kiírása a JSON-könyvtár alapértelmezett beállításaival (szóközök a , / : után, beillesztési sorrendű kulcsok) eltérő bájtokat eredményez, és érvényteleníti a HMAC-et. Ellenőrizze a nyers törzset — vagy ha újra kell szerializálnia, pontosan egyezzen meg a kanonikus formánkkal: rendezett kulcsok, tömör elválasztók, UTF-8.

A keretrendszer automatikusan elemzi a JSON-t

Az Express express.json() middleware-e felhasználja a törzsfolyamot, így elvesznek a nyers bájtok. Kifejezetten a webhook útvonalon használja az express.raw()-t, vagy pufferelje a nyers törzset egy előzetes middleware-ben. Ugyanez vonatkozik a NestJS-re / Koára is — tekintse meg a „raw body” dokumentációjukat.

Időzítés szempontjából nem biztonságos összehasonlítás

A JS-ben használt expected === signature, illetve a Pythonban használt expected == signature időzítésfüggő összehasonlítások. Használja rendre a crypto.timingSafeEqual vagy a hmac.compare_digest függvényt. A teljesítménykülönbség elhanyagolható.

Rossz titok a toolvégpontokhoz

A közvetlen toolvégpont-hívásokat a szervezeti szintű webhook-titok (GET /v1/webhook) írja alá — nem pedig a /v1/developer/webhook-endpoints végpontspecifikus titkai. Használja újra ugyanazt a verify() függvényt, de ügyeljen arra, hogy a toolútvonalakon a szervezeti titkot adja át neki.

A lekérdezési karakterlánc hashelése GET/DELETE tooloknál

A törzs nélküli toolmetódusok esetén az aláírás az üres bájtkarakterláncot fedi le, így egyetlen univerzális módszer használható: a nyers kéréstörzs HMAC-je, bármi is legyen az. Az URL vagy a lekérdezési karakterlánc hashelése soha nem fog egyezni.

Nem 401-es válasz eltérés esetén

Ha sikertelen ellenőrzéskor 200-as választ ad vissza, a kezelő ismétlési célponttá válik. Ha az ellenőrzés sikertelen, mindig 2xx-től eltérő választ adjon.


Következő lépések