ThunderPhone 2.0 is live.Direct zelf aan de slag, vanaf 2 cent/min.Lees de aankondiging

Webhooks

Overzicht van webhooks

Hoe ThunderPhone realtimegebeurtenissen levert, hoe je handtekeningen verifieert en hoe de verouderde en endpointgebaseerde leveringsmodellen zich tot elkaar verhouden.

ThunderPhone verstuurt HTTP-POST-verzoeken naar je server wanneer er iets gebeurt tijdens een oproep — een inkomende oproep start, een oproep eindigt, een beoordelingsrun is voltooid, een waarschuwing wordt geactiveerd, enzovoort. Er zijn twee bezorgmodellen:

Alle tien gebeurtenistypen in de gebeurteniscatalogus worden geleverd via webhook-eindpunten. De zes gebeurtenissen uit de oproeplevenscyclus (telephony.incoming, telephony.complete, telephony.tool, web.incoming, web.complete, web.tool) worden ook verzonden naar de verouderde webhook met één URL — als je zowel een verouderde URL als een overeenkomend eindpunt hebt, ontvang je de gebeurtenis via beide paden. Blokkerend gedrag (de telephony.incoming / web.incoming-configuratie-uitwisseling en toolverzending in webhookmodus) bestaat uitsluitend op het verouderde pad; elke levering aan een eindpunt is een fire-and-forgetmelding.

Payloadindeling

Leveringen aan eindpunten zijn een JSON-object met data, event_id en type:

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

event_id is uniek voor elke verstuurde gebeurtenis. Het is identiek bij nieuwe pogingen en bij elk eindpunt dat de gebeurtenis ontvangt — gebruik het voor deduplicatie.

De verouderde webhook met één URL verstuurt hetzelfde type en dezelfde data, maar zonder event_id:

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

Bij verzending wordt elke body canoniek geserialiseerd — sleutels alfabetisch gesorteerd, geen witruimte, UTF-8. De mooi opgemaakte voorbeelden in deze documentatie dienen alleen voor de leesbaarheid.

Bekijk de gebeurteniscatalogus voor de volledige lijst met gebeurtenis- typen en payloadvelden.

Handtekeningverificatie

Elk verzoek bevat een HMAC-SHA256-handtekening over de onbewerkte aanvraagbody in de header X-ThunderPhone-Signature. De ondertekeningssleutel is het secret van het eindpunt (of je webhook-secret op organisatieniveau voor verouderde leveringen).

Stappen

  1. Lees de onbewerkte aanvraagbody vóór enige parsing.
  2. Bereken hmac_sha256(secret, body).hexdigest().
  3. Vergelijk deze in constante tijd met de header X-ThunderPhone-Signature.

We ondertekenen exact de bytes die we verzenden, en die bytes zijn de canonieke JSON-serialisatie (gesorteerde sleutels, compacte scheidingstekens). Daarom werkt verificatie aan de hand van de onbewerkte body altijd — en als je framework je alleen geparste JSON geeft, levert deze opnieuw serialiseren met gesorteerde sleutels en compacte scheidingstekens identieke bytes op. Beide methoden worden behandeld in de verificatiehandleiding.

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

Bezorgsemantiek

Deze semantiek geldt voor leveringen aan endpoints. De verouderde webhook met één URL is één synchrone poging zonder nieuwe pogingen.

Nieuwe pogingen

Elk event wordt onmiddellijk één keer geprobeerd. Elke 2xx-respons bevestigt de levering. Bij elke andere uitkomst (geen 2xx, verbindingsfout, time-out) proberen we opnieuw na 1 m, 5 m, 30 m, 2 u, 6 u, 12 u en 24 u na de eerste poging — 8 pogingen verspreid over 24 uur. Als elke poging mislukt, stopt de levering en wordt het endpoint gemarkeerd met status="failing" in webhook-endpoints. Retourneer 2xx zodra de payload duurzaam is geaccepteerd; verwerk deze asynchroon.

Volgorde

De leveringsvolgorde is gebaseerd op best effort. In de praktijk leveren we in de volgorde waarin events worden verzonden, maar nieuwe pogingen kunnen bij fouten de volgorde wijzigen. Dedupliceer en stem altijd af op call_id / object-id.

Duplicaten

Levering is minstens één keer: een nieuwe poging na een respons die we nooit hebben gezien kan een event dupliceren. Elke nieuwe poging bevat dezelfde event_id, dus sla verwerkte id's op en sla herhalingen over. event_id wordt ook gedeeld tussen endpoints — twee endpoints die op hetzelfde event zijn geabonneerd, ontvangen dezelfde event_id.

Time-outs

Leveringen aan endpoints hebben een time-out van 30 s per poging. Op het verouderde pad verlopen blokkerende verzoeken die het gedrag van live oproepen bepalen — de configuratie-uitwisseling voor telephony.incoming / web.incoming — na 10 s, maar een trage respons vertraagt het opnemen van de oproep, dus probeer binnen enkele seconden te antwoorden. Tooldispatch in webhookmodus staat standaard 20 s toe, en tooldeclaraties kunnen een timeout op het hoogste niveau instellen.

Bron-IP's

Uitgaande webhooks zijn afkomstig uit het cloud-IP-bereik van ThunderPhone. Als je firewall een toelatingslijst vereist, neem dan contact op met support en we delen de huidige bereiken.

Kiezen tussen verouderde en endpointgebaseerde webhooks

FunctieVerouderd (/v1/webhook)Endpoints (/v1/developer/webhook-endpoints)
Aantal URL's1 per organisatieMeerdere per organisatie
EventdekkingAlleen telephony.* / web.*Alle 10 eventtypen
EventfilterPer endpoint
Nieuwe pogingenGeen8 pogingen in 24 u
Enveloptype + datatype + data + event_id
SecretrotatieVervangt één secretSecret per endpoint
Uitschakelen zonder verwijderenstatus=disabled
Zichtbaarheid van statusactive / disabled / failing
Blokkerende configuratie-uitwisselingJa (telephony.incoming / web.incoming)Nooit — alleen meldingen
Het beste voorDynamische oproepconfiguratieEventverwerking in productie

Nieuwe integraties moeten events verwerken via endpointgebaseerde webhooks. Behoud (of voeg) alleen een verouderde URL toe als je oproepen dynamisch configureert op het moment van opnemen of tooldispatch in webhookmodus gebruikt — deze verzoek/respons-uitwisselingen werken alleen via het verouderde pad.


Gerelateerd