ThunderPhone 2.0 on nyt julkaistu.Ota käyttöön itse – alkaen 2 ¢/min.Lue lisää julkistuksesta

Webhooks

telephony.complete / web.complete

Estoton webhook, joka toimitetaan puhelun päättyessä ja sisältää transkriptin, tallenteen URL-osoitteen ja mittarit.

Valmistumistapahtuma käynnistyy jokaisen puhelun päätyttyä — saapuvan puhelun, lähtevän puhelun, verkkopuhelun tai testipuhelun (rakentajan mikrofonisessio) jälkeen. Se on ei-estävä: vastaa millä tahansa 2xx-tilakoodilla.

Tapahtuma toimitetaan molempia reittejä pitkin:

Pyyntöhyötykuorma (päätepistetoimitukset)

{
  "data": {
    "billable_minutes": 1.25,
    "billing_total_cents": 8,
    "call_id": 987654321,
    "direction": "inbound",
    "duration_seconds": 54,
    "end_reason": "user_hangup",
    "end_time": "2026-04-20T18:25:04.822Z",
    "extracted_data": {
      "status": "completed",
      "fields": {
        "customer_name": "Alex Morgan",
        "appointment_date": "2026-04-23"
      },
      "evidence": {
        "customer_name": {
          "quote": "My name is Alex Morgan",
          "speaker_role": "caller",
          "turn_index": 4
        },
        "appointment_date": {
          "quote": "April 23 works for me",
          "speaker_role": "caller",
          "turn_index": 7
        }
      },
      "verification": "verified",
      "field_reasons": {},
      "schema_version": "92850758e231a3c95a..."
    },
    "from_number": "+14155550199",
    "product": "spark",
    "recording_stopped_at": null,
    "recording_stopped_reason": null,
    "recording_url": "https://storage.example.com/…",
    "start_time": "2026-04-20T18:24:10.113Z",
    "status": "completed",
    "summary": "Alex Morgan called to book a follow-up visit and confirmed Thursday, April 23. The agent read the appointment back and Alex asked for a reminder text the day before.",
    "to_number": "+15551234567",
    "transcripts": [ /* see Transcript format */ ],
    "transfer_number": null,
    "unresolved_variables": ["campaign_owner"],
    "variables": {"campaign_name": "Spring renewals"},
    "voice": "john"
  },
  "event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
  "type": "telephony.complete"
}
KenttäTyyppiKuvaus
call_idkokonaislukuPysyy samana tämän puhelun kaikissa tapahtumissa
agent_idkokonaisluku | nullPuhelun käsitellyt agentti, kun sellainen on määritetty
agent_namemerkkijono | nullPuhelun käsitellyt agentti, kun sellainen on määritetty
directionmerkkijonoinbound, outbound, web, test. Historialliset payloadit voivat sisältää vanhoja mic- tai widget-arvoja
from_number, to_numbermerkkijonoE.164. from_number on kirjaimellisesti "web" verkkopuheluissa ja testipuheluissa
origin_domainmerkkijonoVain web/test — sivun alkuperä, jolla widget oli isännöitynä (tyhjä mic-istunnoissa)
start_time, end_timeaikaleimaISO 8601 UTC
duration_secondskokonaisluku | nullJohdettu aloitus- ja päättymisajasta
statusmerkkijonocompleted tai failed
end_reasonmerkkijonoKatso alla oleva taulukko
product, voicemerkkijonoPuhelun aikana käytössä ollut agentin määritys
variablesobjektiSyötemuuttujat, joista otettiin tilannekuva puhelun alkaessa
unresolved_variablestaulukkoMuuttujien nimet, joihin puhelun määritys viittaa mutta joita ei annettu puhelun alkaessa
transfer_numbermerkkijono | nullAsetetaan, kun puhelu siirrettiin
recording_urlmerkkijono | nullVanheneva allekirjoitettu URL-osoite; lataa se viipymättä. null, kun tallennetta ei ole saatavilla
recording_stopped_ataikaleima | nullHetki, jolloin tallennus lopetettiin ennen puhelun päättymistä; tallenne ei sisällä mitään tämän hetken jälkeen. null, kun tallennus jatkui puhelun loppuun asti tai puhelua ei tallennettu
recording_stopped_reasonmerkkijono | nullSyy siihen, miksi tallennus lopetettiin ennenaikaisesti. warm_transfer: tallennus pysähtyy juuri ennen kuin lämmin siirto soittaa kohdenumeroon, ja pysyy pois käytöstä, vaikka siirto epäonnistuisi ja soittaja palaisi agentille. Käsittele muita arvoja tiedoksi tarkoitettuina; niitä voidaan lisätä lisää. null, kun recording_stopped_at on null
billable_minutesnumeroLaskutettavat minuutit pyöristettynä lähimpään neljännesminuuttiin (15 sekunnin välein, vähintään 0.25). Suoraan vastaajaan ohjautuvat puhelut ilmoittavat silti tässä todelliset mitatut minuuttinsa, mutta veloitus on rajattu yhteen minuuttiin sopimushinnalla.
billing_total_centskokonaislukuUSD-sentit
transcriptstaulukkoVuorokohtaiset transkriptiomerkinnät; voi olla tyhjä, jos transkriptiota ei ole saatavilla
summarymerkkijono | nullSelkokielinen puhelun jälkeinen yhteenveto (kahdesta neljään proosalausetta, enintään 2 000 merkkiä, kirjoitettuna puhelun kielellä: agentin ensisijaisella kielellä, ellei puhelu ohittanut sitä), kun agentilla on agentin rakennustyökalun kohdassa Kerättävät tiedot käytössä Luo puhelun yhteenveto (call_summary_enabled kohteessa Agent) ja luonti onnistui; muussa tapauksessa null, mukaan lukien puhelut, jotka ovat liian lyhyitä yhteenvetoon, puhelut ilman transkriptiota ja agentit, joiden tallennus on poistettu käytöstä. Yhteenveto luodaan samassa puhelun jälkeisessä vaiheessa kuin poiminta, joten asetus Odota poimitut tiedot ennen puhelun päättymisen webhookin lähettämistä (extraction_blocks_completion) määrittää, odottaako tämä tapahtuma sitä: odotustilassa yhteenveto on valmis ennen tämän tapahtuman lähettämistä (itse yhteenvedolla on enintään 30 sekuntia vaiheen 75 sekunnin budjetista); kun tapahtuma ei odota (never tai siirto asetuksella except_transfers), tämä kenttä on null eikä yhteenvetoa lähetetä uudelleen kohteessa call.data_extracted, joten lue se myöhemmin kohteen Call object summary-kentästä. Saatavilla myös hallintapaneelin puhelusivulla
extracted_dataobjekti | nullJäsennelty poimintatulos, jossa ovat status, fields, evidence, verification, field_reasons ja schema_version. Jokaisella muulla kuin null-kentällä on tarkka rakenteellisesti tarkistettu lainaus (enintään 1 000 merkkiä) sekä puhujan rooli ja vuoroindeksi; mallin palauttamat pidemmät lainaukset hylätään katkaisemisen sijaan. Evidenssi on null, kun kenttä on null. verified tarkoittaa, että jokainen ehdokas sai täsmälleen yhden kelvollisen riippumattoman arvion. unavailable kattaa myös virheellisen tai osittaisen tarkistajan tulosteen; kelvollisia osittaisia arvioita sovelletaan silti, kun taas ehdokkaat ilman yhtä kelvollista arviota nollataan. status on completed, failed, exhausted, skipped tai skipped_recording_disabled; null, kun agentilla ei ollut poimintakenttiä
steps_tracetaulukkoVain puheluissa, joiden agentilla on vaiheita tai siirtoja. Järjestys, jossa puhelu eteni niiden läpi: jokaisella merkinnällä on t_ms (millisekunteina puhelun alkamisesta), agent (agentin avain, a ja sen tunnus), step (vaiheen tunnus tai null, kun siirto tehdään agentille, jolla ei ole vaiheita) ja via (start, model, condition, on_enter tai handoff). Sisältää vain tunnukset ja ajat, joten se säilytetään myös, kun tallennus on pois käytöstä
collected_valuesobjektiVain puheluissa, joiden agentilla on vaiheita tai siirtoja. Arvot, jotka agentti keräsi vaiheissaan, avaimena keruukentän nimi, sekä <tool>_result jokaisen vaiheen on_enter_tool-tulokselle. Jätetään pois, kun puhelun sisältöä ei säilytetä (tallennus pois käytöstä)

Päättymissyyt

ArvoMerkitys
user_hangupEtäosapuoli katkaisi puhelun ensin
ai_hangupTekoäly päätti puhelun tarkoituksellisesti
ai_transferTekoäly siirsi puhelun; transfer_number on asetettu
ai_warm_transferTekoäly suoritti lämpimän (avustetun) siirron
voicemail_hangupVastaaja havaittiin, ja puhelu päätettiin voicemail_action-asetuksesi mukaisesti
max_durationPuhelu saavutti enimmäiskestorajan: agentin tai puhelun oman rajan tai alustan 6 tunnin rajan. Puhelu, jonka tekoäly tai soittaja päättää aikarajavaroituksen jälkeen, säilyttää arvon ai_hangup tai user_hangup
supersededIstunto korvattiin uudemmalla
inbound_config_webhook_failedSaapuva puhelu webhook-tilassa olevaan numeroon: määrityswebhook ei palauttanut käyttökelpoista määritystä, joten puhelu päätettiin ennen kuin agentti vastasi. status on failed, eikä puhelua laskuteta
unknownPäättymissyytä ei voitu määrittää

Transkriptin muoto

Jokainen transcripts-merkintä on yksi keskusteluvuoro. Roolit ovat user (soittajan puhe), model (agentin puhe ja työkalukutsut), tool (työkalutulokset) ja system (puhelutapahtumat, kuten kielen vaihdot).

[
  {
    "role": "user",
    "content_type": "text/plain",
    "content": "Hi, I'm calling about my appointment.",
    "start_ms": 1200,
    "end_ms":   4100,
    "audio_url": "https://storage.example.com/…"
  },
  {
    "role": "model",
    "content_type": "text/plain",
    "content": "Sure, what date works best?",
    "start_ms": 4200,
    "end_ms":   6100
  },
  {
    "role": "model",
    "content_type": "application/json",
    "content": {
      "tool_call": "search_appointments",
      "arguments": { "date": "2026-04-21" }
    }
  },
  {
    "role": "tool",
    "content_type": "application/json",
    "content": {
      "tool_name": "search_appointments",
      "response": { "available_slots": ["9:00 AM", "2:00 PM"] }
    }
  }
]
KenttäTyyppiKuvaus
rolemerkkijonouser, model, tool tai system
content_typemerkkijonotext/plain puheelle; application/json työkalukutsuille, työkalutuloksille ja järjestelmätapahtumille
contentmerkkijono | objektiPuheteksti tai yllä näkyvä jäsennelty objekti. Työkalukutsut: {"tool_call": name, "arguments": {…}}. Työkalutulokset: {"tool_name": name, "response": {…}}
start_ms, end_mskokonaislukuPoikkeamat puhelun alusta millisekunteina. Sisältyvät, kun äänen ajoitus tunnetaan
ttfa_mskokonaislukuEnsimmäiseen ääneen kuluva aika model-vuorolle, kun se on mitattu
audio_url, audio_urlsmerkkijono / taulukkoVanhenevat allekirjoitetut URL-osoitteet vuoron äänelle, kun ääni on tallennettu vuorokohtaisesti

Käytä täysin jäsenneltyä vuorohistoriaa varten (keskeytysmerkintöineen, kuittauskehotteineen ja raakoine sijainteineen) GET /v1/calls/{call_id}/history-päätepistettä.

Vanhan hyötykuorman erot

Vanhan yhden URL-osoitteen webhook-kirjekuori on {"type": "telephony.complete" | "web.complete", "data": {…}}, eikä siinä ole event_id-kenttää. Sen data eroaa päätepisteen hyötykuormasta:

Vanha valmistumisen hyötykuorma sisältää myös agent_id- ja agent_name-kentät.

  • Vuorotaulukko on kentässä history, ei transcripts-kentässä (sama vuorokaava kuin yllä).
  • Kenttäjoukko on puhelun päättymisen raaka raportti, ja se voi sisältää taulukon lisäksi sisäisiä kenttiä — käsittele tuntemattomia kenttiä tiedoksi tarkoitettuina.
  • Verkkopuhelut (direction: "web") jättävät pois kentät from_number / to_number ja lisäävät kentän origin_domain.
  • Builderin mikrofonitestipuhelut raportoidaan vanhalla polulla muodossa telephony.complete (päätepistejärjestelmä muuntaa ne muotoon web.complete).
  • Siirron koordinointi: kun puhelu päättyy siirtoon, vanhaa webhookia kutsutaan synkronisesti, ja se voi vastata {"transfer_ready": false} ilmoittaakseen, ettei siirron kohde ole valmis. Mikä tahansa muu vastaus (tai vanhan webhookin puuttuminen) antaa siirron jatkua. Päätepistetoimituksia ei koskaan tarkisteta tätä varten.

Esimerkkikäsittelijä

Python (FastAPI)
import hashlib
import hmac
import json
import os
 
from fastapi import FastAPI, HTTPException, Request
 
app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
 
def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")
 
@app.post("/thunderphone-webhook")
async def webhook(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(status_code=401)
 
    event = json.loads(body)
    if event["type"] in ("telephony.complete", "web.complete"):
        data = event["data"]
        # Endpoint deliveries use "transcripts"; the legacy webhook uses "history".
        turns = data.get("transcripts") or data.get("history") or []
        await persist_call_record(
            call_id=data["call_id"],
            turns=turns,
            recording_url=data.get("recording_url"),
        )
        if data["end_reason"] in ("ai_transfer", "ai_warm_transfer"):
            await notify_team(data.get("transfer_number"), data["call_id"])
    return {"ok": True}
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
 
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
 
function verify(body, signature) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return signature &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
 
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    if (["telephony.complete", "web.complete"].includes(event.type)) {
      const data = event.data;
      // Endpoint deliveries use "transcripts"; the legacy webhook uses "history".
      const turns = data.transcripts ?? data.history ?? [];
      await persistCallRecord({ ...data, turns });
      if (["ai_transfer", "ai_warm_transfer"].includes(data.end_reason)) {
        await notifyTeam(data.transfer_number, data.call_id);
      }
    }
    res.json({ ok: true });
  },
);

Yleiset käyttötapaukset

CRM-integraatio

Tallenna jokaisen puhelun litterointi ja tallenteen URL-osoite asiakastietojen yhteyteen.

Analytiikka

Lähetä litteroinnit putkeen aiheiden mallinnusta, CSAT-signaalien poimintaa tai siirtoprosentin seurantaa varten.

Laadun tarkastus

Avaa puhelut laadunvarmistustyökalussa ihmisen tarkastettavaksi tai aja ne oman arviointimallisi läpi.

Ilmoitukset

Käynnistä ilmoitus tiimin jäsenelle siirron tai epäonnistumisen yhteydessä.


Aiheeseen liittyvää