ThunderPhone 2.0 er lanceret.Selvbetjening fra 2 cent/min.Læs mere om lanceringen

Webhooks

Oversigt over webhooks

Sådan leverer ThunderPhone hændelser i realtid, sådan verificerer du signaturer, og sådan sammenlignes de ældre og endpoint-baserede leveringsmodeller.

ThunderPhone sender HTTP-POST-anmodninger til din server, når der sker noget under et opkald — et indgående opkald starter, et opkald slutter, en evalueringskørsel fuldføres, en alarm udløses osv. Der er to leveringsmodeller:

Alle ti hændelsestyper i hændelseskataloget leveres via webhookendepunkter. De seks opkaldslivscyklushændelser (telephony.incoming, telephony.complete, telephony.tool, web.incoming, web.complete, web.tool) sendes også til det forældede webhook med enkelt URL — hvis du både har en forældet URL og et matchende endepunkt, modtager du hændelsen på begge ruter. Blokerende adfærd (konfigurationsudvekslingen for telephony.incoming / web.incoming og værktøjsafsendelse i webhooktilstand) findes udelukkende på den forældede rute; hver levering til et endepunkt er en fire-and-forget-notifikation.

Payloadformat

Leveringer til endepunkter er et JSON-objekt med data, event_id og type:

{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}

event_id er unik for hver udsendt hændelse. Den er identisk på tværs af genforsøg og på tværs af alle endepunkter, der modtager hændelsen — dedupliker efter den.

Det forældede webhook med enkelt URL sender samme type og data, men uden event_id:

{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}

Ved overførsel serialiseres alle brødtekster kanonisk — nøgler sorteres alfabetisk, uden mellemrum, UTF-8. De pænt formaterede eksempler i denne dokumentation er kun for læsbarhed.

Se hændelseskataloget for den komplette liste over hændelsestyper og payloadfelter.

Signaturverifikation

Hver anmodning indeholder en HMAC-SHA256-signatur over den rå anmodnings- brødtekst i headeren X-ThunderPhone-Signature. Signeringsnøglen er endepunktets secret (eller din organisations webhook-secret for ældre leveringer).

Trin

  1. Læs den rå anmodningsbrødtekst før enhver parsing.
  2. Beregn hmac_sha256(secret, body).hexdigest().
  3. Sammenlign i konstant tid med headeren X-ThunderPhone-Signature.

Vi signerer nøjagtigt de bytes, vi sender, og disse bytes er den kanoniske JSON-serialisering (sorterede nøgler, kompakte separatorer). Derfor virker verifikation mod den rå brødtekst altid — og hvis dit framework kun giver dig parset JSON, producerer en genserialisering med sorterede nøgler og kompakte separatorer identiske bytes. Begge fremgangsmåder er beskrevet i verifikationsvejledningen.

Python
import hmac
import hashlib
 
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature or "")
 
# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)
 
@app.post("/thunderphone-webhook")
def handle():
    body = request.get_data()
    sig = request.headers.get("X-ThunderPhone-Signature", "")
    if not verify_signature(body, sig, WEBHOOK_SECRET):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
 
function verifySignature(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),
  );
}
 
const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("X-ThunderPhone-Signature") || "";
    if (!verifySignature(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);
  },
);

Leveringssemantik

Denne semantik gælder for leveringer til endepunkter. Den ældre webhook med én URL er ét enkelt synkront forsøg uden genforsøg.

Genforsøg

Hver hændelse forsøges leveret én gang med det samme. Ethvert 2xx-svar bekræfter leveringen. Ved ethvert andet udfald (ikke-2xx, forbindelsesfejl, timeout) forsøger vi igen efter 1 m, 5 m, 30 m, 2 h, 6 h, 12 h og 24 h efter det første forsøg — 8 forsøg fordelt over 24 timer. Hvis alle forsøg fejler, stopper leveringen, og endepunktet markeres med status="failing" i webhook-endepunkter. Returnér 2xx, så snart payloadet er accepteret varigt; behandl det asynkront.

Rækkefølge

Leveringsrækkefølgen er efter bedste evne. I praksis leverer vi i den rækkefølge, hændelser udsendes, men genforsøg kan ændre rækkefølgen ved fejl. Dedupliker og afstem altid efter call_id / objekt-id.

Dubletter

Levering er mindst én gang: et genforsøg efter et svar, vi aldrig modtog, kan duplikere en hændelse. Hvert genforsøg bruger det samme event_id, så gem behandlede id'er, og spring gentagelser over. event_id deles også på tværs af endepunkter — to endepunkter, der abonnerer på den samme hændelse, modtager det samme event_id.

Timeouts

Leveringer til endepunkter har en timeout på 30 s pr. forsøg. På den ældre sti får blokerende anmodninger, der styrer live opkaldsadfærd — den telephony.incoming / web.incoming konfigurationsudveksling — timeout efter 10 s, men et langsomt svar forsinker opkaldsbesvarelsen, så tilstræb at svare inden for et par sekunder. Værktøjskald i webhook-tilstand tillader som standard 20 s, og værktøjsdeklarationer kan angive en timeout på øverste niveau.

Kilde-IP'er

Udgående webhooks kommer fra ThunderPhone's cloud-IP-område. Hvis din firewall kræver en tilladelsesliste, skal du kontakte support, så deler vi de aktuelle områder.

Valg mellem ældre og endepunktbaserede webhooks

FunktionÆldre (/v1/webhook)Endepunkter (/v1/developer/webhook-endpoints)
Antal URL'er1 pr. organisationFlere pr. organisation
HændelsesdækningKun telephony.* / web.*Alle 10 hændelsestyper
HændelsesfilterPr. endepunkt
GenforsøgIngen8 forsøg over 24 h
Indpakningtype + datatype + data + event_id
Rotation af hemmelighedErstatter enkelt hemmelighedHemmelighed pr. endepunkt
Deaktiver uden sletningstatus=disabled
Statussynlighedactive / disabled / failing
Blokerende konfigurationsudvekslingJa (telephony.incoming / web.incoming)Aldrig — kun notifikationer
Bedst tilDynamisk opkaldskonfigurationHændelsesforbrug i produktion

Nye integrationer bør modtage hændelser via endepunktbaserede webhooks. Behold (eller tilføj) kun en ældre URL, hvis du konfigurerer opkald dynamisk ved besvarelse eller bruger værktøjskald i webhook-tilstand — disse anmodnings-/svarudvekslinger kører kun på den ældre sti.


Relateret