Juhendid
Veebikonksud
Kutsume teie URL-i, niipea kui midagi juhtub - lõppenud kõne, ebaõnnestunud katse, saadetud või vastuvõetud SMS. Seadistate selle ühe korra (PUT /v1/webhook, vt REST API) ja edasi te millegi pärast ei muretse.
Sündmused
Kuus sündmust, igaühel oma data välja kuju:
call.completed- kõne lõppes, olenemata sellest, kas selles oli agent või mitte. Agendiga kõnede korral sisaldab see lisaks transkriptsiooni (transcript), kokkuvõtet (summary) ja salvestatud andmeid (data).durationSecson sekundites ja volai oma häälmootoril toimuvate kõnede puhul võib olla murdarv. Kõne, mille teises otsas oli automaat, mis kordas ikka sama teadet, tuleb siia samuti, väärtusegaendReason: "caller_loop"(hind0, kui kõne kestis kuni viis minutit). Siia tuleb ka vastatud värava läbikutse (kind: "ring",ring.outcome: "answered"), ühe erandiga, mida kirjeldataksecall.no_answernäite all. Volai oma mootoril toimunud kõne (alates 15. 10. 2026 järgust) kannab lisaksengineploki väljadegaprotectedUtterancesjaprotectedUtterancesCount: kõnelejale vastanu repliigid tervituse aknast, mida mootor tahtlikult vastu ei võtnud ("Halloo?" või nimi agendi esimese lause ajal) - need ei ole väljastranscriptja agent neile ei reageerinud; sama kuju naguGET /v1/calls/{id}-s, ElevenLabsi ja vanema mootori puhul võti puudub.call.failed- kõne lõppes olekusfailed- olgu siis, et häälplatvorm lükkas selle kohe tagasi või võrk ebaõnnestus. Tuleb volai mootoril ja ElevenLabsil töötava agendi puhul, otseühenduse (bridge) puhul, BYO relay lepingu (lease) puhul, värava läbikutse puhul (operaator lükkas tellimuse tagasi,kind: "ring") ja proovikõne puhul - alati täpselt üks kord. Keha kannabreason(tehniline põhjus) jaendReason. Vastamata kõne SIIA EI KUULU, sellel on allpool oma sündmus. Kõne, mis ÜHENDATI, kuid lõppes häälmootori tehnilise veaga (endReason: "engine_error"), tuleb siia samuti - selle keha kannab lisakstranscript/summary/durationSecs/priceHal, teistereason-väärtuste puhul neid nelja välja ei ole. Sellise kõne korral onreasonkasengine_stallvõiengine_error(mootori viga), võiunfinished, kui kõne katkestas mootori taaskäivitus või peatamine kõne keskel (näiteks uue versiooni juurutamisel). Sellist kõnet ei arveldata (priceHalon0), sellel pole salvestust jasummaryselgitab lühikese tekstiga, mis juhtus.transcriptkannab ainult seda, mis enne katkestust jõuti salvestada, ja võib olla ka tühi (tühi on tavaliselt siis, kui mootorist ei jõudnud kõne kirjet kohale ja kõne suleti selle kõneülevaate põhjal, mis transkriptsiooni ei kanna).durationSecson ligikaudne aeg katkestuseni (kui mootor ei jõudnud seda mõõta, kuni 1 800 s). Plokkenginetervitusaknas hüljatud repliikidega selle puhul puudub, sest mootor ei mõõtnud katkestatud kõne tervitusakent.call.no_answer- väljaminev kõne - kutsutav ei vastanud (helises, vastust ei tulnud). Nii tuleb ka värava läbikutse (POST /v1/rings), mille värav tagasi lükkas või mis helises kuni nõutud aja lõpuni - tunnete selle ära väärtusestkind: "ring", vt allpool.call.missed- sissetulev kõne jäi vastamata (vastamata kõne). Tuleb ka sissetuleva kõne eest, mille häälmootor tagasi lükkas, kuna kõik liinid olid täis (helistaja ei pääsenud läbi, võtke temaga ühendust). Sellise kõne kirjel onendReason: "capacity", kestus0ja hind0; veebikonks ei pruugi seda siiski kanda, kui operaator teavitas vastamata kõnest varem, usaldusväärselt loete selle kõne kirjest (GET /v1/calls/{id}).message.sent- operaator võttis SMS-i saatmiseks vastu - see ei ole kohaletoimetamise kinnitus. Tuleb ka SMS-i puhul, mis saadeti mobiilinumbrilt või number kõnede ja SMS-ide jaoks numbrilt. VälismsNumberkannab numbrit, millelt sõnum saadeti, jagatud saatja SMSinfo puhul on seenull. Kohaletoimetamise kinnitus (deliveryStatus) mobiilinumbrilt saadetud SMS-i puhul ega hilisem ebaõnnestumine (failed, saldo tagastatakse) ühtegi sündmust ei käivita - lugege olekGET /v1/messages/{id}kaudu.message.received- teie mobiilinumbrile (mobiilinumbri lisand) või teie number kõnede ja SMS-ide jaoks numbrile saabus sõnum.datakannabid,from(saatja: telefoninumber E.164 vormingus või tekstinimi, nt „Google“, kuni 32 märki; kui saatja puudub, on seeunknown),to(teie number, millele sõnum saabus),body,segmentsjareceivedAt(sõnumi vastuvõtu aeg, ajatempel ms-des). Toimetatakse kohale kordusega järjekorra kaudu nagu kõnesündmused. Mobiilinumbri puhul täiendab sõnumi, mida vastuvõtt kohe ei tabanud, regulaarne kontroll umbes 15 minuti jooksul ja sündmus lahkub siis viivitusega, vabalt ka uuemate sõnumite järel. number kõnede ja SMS-ide jaoks numbri puhul sellist kontrolli ei ole: operaator kordab sõnumi kohaletoimetamist kuni tunni, nii et kui vastuvõtt meil ei tööta kauem, võib sõnum kaduda ja te ei leia seda kaGET /v1/messages?direction=inalt.tsümbrikus on sündmuse tekkimise aeg (samuti ms-des, samas kuit=allkirja päises on sekundites). Üle 300 vastuvõetud sõnumi tunnis ühe numbri kohta (nii mobiilinumber kui ka number kõnede ja SMS-ide jaoks) veebikonksu järgmiste sõnumite jaoks ei saadeta; sõnumeid salvestatakse edasi ja leiate needGET /v1/messages?direction=inalt, mis hoiab kontol kuni 1 000 uusimat vastuvõetud sõnumit ja säilitab neid 90 päeva. Numbril number kõnede ja SMS-ide jaoks, mis vabastatakse (olekreleasing), sõnum salvestatakse, kuid selle kohtamessage.receivedei saadeta. Kontole, mille volai operaator on käsitsi peatanud,message.receivedei saadeta üldse ja peatamise ajal vastuvõetud sõnumeid ei toimetata pärast konto taastamist tagantjärele kohale; needki leiateGET /v1/messages?direction=inalt.
Milliseid neist soovite tellida, ütlete väljaga events kutses PUT /v1/webhook. Pange tähele üht vastuintuitiivset detaili: tühi massiiv tähendab kõiki, mitte mitte ühtegi - ja nii jääb see ka tulevikus lisanduvate sündmuste puhul. Kui soovite kohaletoimetamise tõesti välja lülitada, kustutage veebikonks portaalis.
Iga kõnesündmuse (call.completed, call.failed, call.no_answer, call.missed) keha kannab lisaks kind, kõne liiki nagu GET /v1/calls (agent, bridge, relay, inbound, transfer, sip, ring, voice_message). Häälsõnumi (POST /v1/voice-messages) puhul on kehas plokk voiceMessage väljadega text, repeat ja pärast kõne lõppu repeatPlayed (mitu korda sõnum kõlas) ja completed (kõlas tervikuna); vastamata sõnum tuleb kui call.no_answer. Värava läbikutse puhul on kehas ka plokk ring väljadega requestedSecs, outcome ja rangSecs, teistel kõnedel võti ring puudub. Mõlemad väljad on aditiivsed, vanemad integratsioonid lihtsalt ignoreerivad neid.
Iga sündmus saabub POST-päringuna selle ümbrikuga:
call.completed
{
"event": "call.completed",
"ts": 1756111640000,
"event_id": "evt_V1StGXR8_Z5jdHi6B-myT",
"data": {
"id": "c_8f2ac1d4e5b0",
"direction": "in",
"from": "+420777123456",
"to": "+420601234567",
"status": "completed",
"durationSecs": 47,
"priceHal": 236,
"endReason": "completed",
"transcript": [
{ "role": "agent", "message": "Tere päevast, siin on kohvik Nulli vastuvõtt, kuidas ma saan teid aidata?" },
{ "role": "caller", "message": "Tere päevast, tahaksin tellida kaks latet kaasa." },
{ "role": "agent", "message": "Selge, kaks latet kaasa, valmis viieteistkümne minuti pärast." }
],
"summary": "Klient tellis kaks latet kaasa, kättesaamine 15 minuti pärast.",
"data": {
"jmeno": "Jana Nováková",
"pocet_kav": 2,
"spechalo": "bezna"
},
"kind": "inbound"
}
}Pesastatud data.data (jah, kaks korda - väline data on iga sündmuse ümbrik) on andmed, mille agent kõnest oma dataFields järgi salvestas - võtmed määrate teie, vt Häälagent. Väärtus null tähendab, et kõnes seda ei öeldud. Kui agendil pole ühtegi välja seadistatud, võtit data kehas üldse ei ole - nagu ka transcript ja summary agendita kõne puhul.
Salvestus veebikonksu kehas ei tule
Heli veebikonksuga ei saadeta ja lipp hasRecording ei tule samuti. Kui salvestust vajate, võtke GET /v1/calls/{id} (ütleb hasRecording) ja laadige salvestus alla aadressilt GET /v1/calls/{id}/recording - vormingu määrab vastuse päis content-type: ElevenLabsi agendil audio/mpeg (MP3), volai mootoril töötaval agendil täna audio/ogg. Salvestusi hoiame 90 päeva kõnest arvates.
call.failed
{
"event": "call.failed",
"ts": 1756111640000,
"event_id": "evt_Qp3xLk9F-2WyoTr8cNaZ1",
"data": {
"id": "c_9d4e2b7f1a63",
"direction": "out",
"from": "+420601234567",
"to": "+420777998877",
"reason": "rejected",
"endReason": "failed",
"kind": "agent"
}
}call.no_answer
Sama kuju on ka call.missed (vastamata SISSETULEV kõne) - erineb ainult sündmuse nimi, direction ja status.
{
"event": "call.no_answer",
"ts": 1756111640000,
"event_id": "evt_9mYbT_R2xVn7Kd4LpZaQ8",
"data": {
"id": "c_3b7e1c9a4f20",
"direction": "out",
"from": "+420601234567",
"to": "+420777998877",
"status": "no_answer",
"durationSecs": 0,
"priceHal": 0,
"answeredBy": "unknown",
"endReason": "no_answer",
"kind": "agent"
}
}Vastamata värava läbikutse (POST /v1/rings) tuleb samuti kui call.no_answer, väärtusega kind: "ring" ja plokiga ring: outcome on rang (helises nõutud aja lõpuni) või ended_early (kõne lõppes varem, näiteks lükkas värav selle tagasi, ehk avanes). Vastatud läbikutse (outcome: "answered") läheb kui call.completed. Erand: kui operaator kinnitab vastamise alles kõne kirjes (CDR) pärast seda, kui volai läbikutse vastamata kõnena sulges, tuleb ainult call.no_answer ja kirje GET /v1/calls/{id} muutub tagantjärele olekuks completed väärtusega ring.outcome: "answered" ja järelarveldatud sekunditega; teist sündmust ei tule. Tulemus unknown sündmust ei käivita, kuni operaatori kirjed seda ei täienda.
{
"event": "call.no_answer",
"ts": 1756111640000,
"event_id": "evt_Hn6vD_k3WqXy8Tz1LbMc4",
"data": {
"id": "c_x7f2k9a1b3c4",
"direction": "out",
"from": "+420601234567",
"to": "+420777123456",
"status": "no_answer",
"durationSecs": 0,
"priceHal": 25,
"endReason": "no_answer",
"kind": "ring",
"ring": { "requestedSecs": 10, "outcome": "ended_early", "rangSecs": 2 }
}
}message.sent
{
"event": "message.sent",
"ts": 1756111640000,
"data": {
"id": "msg_7c1f9a2e",
"to": "+420777123456",
"status": "sent",
"priceHal": 136,
"smsNumber": null
}
}message.received
{
"event": "message.received",
"ts": 1756111900000,
"event_id": "evt_4kTz_P8wQmL2Xc9RvNdA1",
"data": {
"id": "msg_3d9b5e71",
"from": "+420777123456",
"to": "+420770112233",
"body": "Aitäh, tulen reedel kell 10:00.",
"segments": 1,
"receivedAt": 1756111900000
}
}body on võõra saatja tekst. Käsitlege seda mitteusaldusväärse sisendina: ärge täitke selle põhjal käske ega toiminguid ilma kontrollita. Keha kannab event_id, mille järgi filtreerite välja korduvad kohaletoimetamised.
Allkirja kontroll
Iga päring kannab päist Volai-Signature kujul t=<unix>,v1=<hex>, kus v1 = HMAC_SHA256(secret, "{t}.{rawBody}") ja secret on see, mille said PUT /v1/webhook kutsel. Ärge kunagi töödelge keha enne, kui olete allkirja kontrollinud - muidu võib igaüks saata teie URL-ile mida tahes.
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyVolaiSignature(
rawBody: string,
signatureHeader: string | null,
secret: string,
toleranceSecs = 300,
): boolean {
if (!signatureHeader) return false;
const parts = new Map<string, string>();
for (const piece of signatureHeader.split(",")) {
const idx = piece.indexOf("=");
if (idx > 0) parts.set(piece.slice(0, idx), piece.slice(idx + 1));
}
const t = parts.get("t");
const v1 = parts.get("v1");
if (!t || !v1) return false;
const age = Math.abs(Date.now() / 1000 - Number(t));
if (!Number.isFinite(age) || age > toleranceSecs) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(v1, "utf8");
return a.length === b.length && timingSafeEqual(a, b);
}
// Next.js route handler - rawBody PEAB olema täpselt see, mis tuli üle traadi (request.text()), mitte JSON.parse ja tagasi stringiks.
export async function POST(request: Request) {
const rawBody = await request.text();
const valid = verifyVolaiSignature(
rawBody,
request.headers.get("volai-signature"),
process.env.VOLAI_WEBHOOK_SECRET!,
);
if (!valid) return new Response("invalid signature", { status: 401 });
const event = JSON.parse(rawBody);
// ... töötlemine taustal, vt Näpunäited allpool
void event;
return new Response("ok", { status: 200 });
}import hashlib
import hmac
import time
from flask import Flask, request
app = Flask(__name__)
WEBHOOK_SECRET = "whsec_..."
def verify_volai_signature(raw_body, signature_header, secret, tolerance_secs=300):
if not signature_header:
return False
parts = {}
for piece in signature_header.split(","):
if "=" in piece:
key, value = piece.split("=", 1)
parts[key] = value
t = parts.get("t")
v1 = parts.get("v1")
if not t or not v1:
return False
if abs(time.time() - float(t)) > tolerance_secs:
return False
payload = f"{t}.{raw_body}".encode("utf-8")
expected = hmac.new(secret.encode("utf-8"), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
@app.route("/webhooks/volai", methods=["POST"])
def volai_webhook():
raw_body = request.get_data(as_text=True)
signature = request.headers.get("Volai-Signature", "")
if not verify_volai_signature(raw_body, signature, WEBHOOK_SECRET):
return "invalid signature", 401
event = request.get_json()
# ... töötlemine taustal, vt Näpunäited allpool
del event
return "ok", 200Kohandatud päised
Kui sihtkoht võtab vastu ainult autentitud päringuid (näiteks automatiseering Cursoris või teie enda API värava taga), lisage võti kohandatud päisena. See saadetakse iga kohaletoimetamisega ja ka testsündmusega, näiteks Authorization: Bearer <võti>. Seadistate selle väljaga headers kutses PUT /v1/webhook, MCP tööriistas set_webhook või portaalis jaotises Ühendused ja API osas Veebikonksud.
curl -X PUT https://volai.cz/v1/webhook \
-H "Authorization: Bearer $VOLAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://api.example.com/hooks/volai", "events": ["message.received", "call.completed", "call.missed"], "headers": {"Authorization": "Bearer YOUR_TARGET_KEY"}}'- Kuni 5 päist, iga väärtus 1 kuni 1000 märki, ainult prinditavad ASCII märgid (ilma diakriitikuta). Nimed ei erista suur- ja väiketähti ning ei tohi korduda.
host,content-type,content-length,volai-signatureja hop-by-hop päised seab kohaletoimetamine ise, neid seada ei saa. - Väärtused on saladused:
GET /v1/webhooktagastab need ainult lühendatult (Bear***). Kui saadate sellise maski tagasi, jääb salvestatud väärtus alles. - Väljajäetud
headersjätab salvestatud päised alles,{}kustutab need. Kui muudate URL-i teiseks aadressiks (teine domeen, skeem või port, ka lisatudwww.), salvestatud päised visatakse ära ja väärtused sisestate uuesti täielikult: võtit võõrale aadressile me ei saada. Kui saadate sel hetkel väärtuse asemel maski, tagastatakse vigainvalid_webhook_headers(400), nagu ka keelatud või kehtetu päise puhul. - Teisele aadressile suunamisel kohandatud päiseid ei saadeta, allkiri
Volai-Signaturesaadetakse. Ka suunamine aadressiltexample.comaadressilewww.example.comon teine aadress, seega sisestage veebikonksu kohe lõplik aadress.
Allkiri Volai-Signature tuleb edasi kohandatud päiste kõrval. Päis ütleb, et päring tohib sisse, allkiri, et selle saatis tõesti volai.
Kordamine vea korral
Kui teie lõpp-punkt ei vasta edukas olekus (2xx), proovib volai veel kaks korda - kokku 3 katset 3-sekundilise pausiga vahel. Kõnesündmuste (`call.completed`, `call.failed`, `call.no_answer`, `call.missed`) ja `message.received` puhul sellega kohaletoimetamine ei lõpe: kui kõik kolm katset ebaõnnestuvad, jääb sündmus järjekorda ja kohaletoimetamist korratakse järgmiste minutite jooksul veel mitu korda, kuni 5 vooru. Seetõttu kannab keha `event_id` - selle järgi filtreerite kohaletoimetamise duplikaadina välja, kui see peaks erandina saabuma mitu korda; korduv kohaletoimetamine kannab täpselt sama keha mis esimene (lihtsalt saadetud mõne minuti hiljem). `message.sent` järjekorda ei ole: pärast 3 ebaõnnestunud katset logitakse see ja rohkem ei saadeta.
Erand on allpool olev testsündmus - see saadetakse ainult üks kord, ilma kordamiseta, et vastus tuleks kohe.
Veebikonks ei ole ainus tõeallikas
Veebikonks kannab ainult väljavõtet - kõige jaoks, mis tõesti loeb (arveldus, audit, sõnumi tekst), tõmmake parem REST API-st ka täielik kirje: GET /v1/calls/{id} ja GET /v1/messages/{id}, millal iganes seda küsite.
Testsündmus
Enne kui hakkate esimest tõelist kõnet ootama, kontrollige kohaletoimetamist kohe - POST /v1/webhook/test (vt REST API) saadab teie URL-ile ühe näidissündmuse webhook.test, SÕLTUMATA sellest, millised events olete kutses PUT /v1/webhook märkinud - testitakse URL-i enda ja allkirja kohaletoimetatavust, mitte konkreetsete sündmuste tellimust. Kuni 10 katset tunnis. Test saadab ainult webhook.test, mitte teisi sündmusi: message.received kuju proovite alles päris SMS-iga oma mobiilinumbrile või number kõnede ja SMS-ide jaoks numbrile (näide eespool).
Kohaletoimetamiste ülevaade
Viimased 50 kohaletoimetamist (edukad, ebaõnnestunud ja testimised) näete kutsega GET /v1/webhook/deliveries (vt REST API), või otse portaalis veebikonksu vormi all. Sama väljavõtte kannab ka GET /v1/webhook väljas recentDeliveries.
Näpunäited
- Vastake 200 võimalikult kiiresti - allkirja kontrolli ja ID salvestamise jõuate kohe, raske töö (e-kirjad, ümberarvutused, teiste API-de kutsed) lükake taustale.
- Lõpp-punkt peab olema avalikult kättesaadav HTTPS-i kaudu -
localhostega self-signed sertifikaat ei tööta. - Kasutage
data.id(kõne või sõnumi ID) duplikaatide väljafiltreerimiseks, kui sama sündmus peaks juhuslikult saabuma mitu korda. - Testige lokaalselt tunneli kaudu (nt ngrok) ja lülitage URL kutses
PUT /v1/webhookajutiselt selle aadressile.
Seotud
MCP server
Ühendage Claude Code, Codex CLI, Codex desktop, Cursor ja teised AI-redaktorid.
REST API
Täielik viide kõigile lõpp-punktidele: numbrid, kõned, SMS, SMS-i mobiilinumbrid, agendid ja nende mustandid, tööriistad, salvestised, veebihaagid, mittehelistamise nimekiri, relee, konto, ülesanded, saldo, dokumendid, kalendrid ja integratsioonid.
Häälagent
Süsteemijuhis, tööriistad, ülekanne inimesele, salvestised ja struktureeritud kõneandmed.