Integracije

Webhookovi

Webhookovi šalju svaki završeni razgovor na vaš poslužitelj kao JSON, s AI sažetkom i podacima o ispitaniku. Pomoću njih ažurirajte CRM, otvarajte tikete ili učitavajte rezultate u vlastitu bazu podataka bez ručnog izvoza.

Što se šalje i kada#

Diaform šalje jedan događaj, conversation.completed. Pokreće se kad AI intervjuer završi razgovor i sažetak je spreman, obično nekoliko trenutaka nakon zadnje poruke.

  • Za cijelu organizaciju. Svaka krajnja točka prima događaje iz svih projekata u vašoj organizaciji. Projekte razlikujte po polju agent_id u payloadu.
  • Šalju se i otkazani razgovori. Ako ispitanik zatraži prekid, razgovor završava kao otkazan, a vi i dalje dobivate conversation.completed, uz "status": "cancelled". Provjerite status prije nego što odgovor smatrate dovršenim.
  • Napušteni razgovori ne šalju se. Ako ispitanik zatvori stranicu, a razgovor nije završio, ništa se ne šalje.
  • Pregledi na nadzornoj ploči ne šalju se. Za testiranje dovršite razgovor putem stvarne poveznice projekta.

Paketi i dopuštenja#

Webhookovi su dostupni u svim plaćenim paketima, ali ne tijekom besplatnog probnog razdoblja. Svaki paket ima ograničenje broja krajnjih točaka:

PaketKrajnje točke
Starter3
Pro5
Business10
Enterprise25

Krajnje točke mogu dodavati, pauzirati i brisati samo vlasnici i administratori. Pogledajte Članovi i uloge i Upravljanje pretplatom.

Dodavanje krajnje točke#

Otvorite webhookove

Na bočnoj traci idite na Postavke → Webhookovi.

Unesite URL

Kliknite Dodaj krajnju točku, zalijepite URL u dijalog Nova krajnja točka webhooka i kliknite Dodaj krajnju točku. URL mora počinjati s https://.

Spremite tajni ključ

Dijalog Tajni ključ webhooka prikazuje vaš tajni ključ za potpisivanje. Kliknite Kopiraj, spremite ga u okruženje svojeg poslužitelja, a zatim kliknite Gotovo.

Svaka krajnja točka ima vlastiti tajni ključ u obliku whsec_ nakon čega slijede 43 znaka. Kasnije ga možete ponovno prikazati ili kopirati preko stavke Tajni ključ za potpisivanje u retku krajnje točke.

Pauziranje ili brisanje krajnje točke#

  • Pauziranje: prebacite prekidač na retku krajnje točke s Aktivna na Pauzirana. Događaji koji se dogode dok je pauzirana ne šalju se naknadno.
  • Brisanje: kliknite ikonu koša za smeće, a zatim za potvrdu Izbriši krajnju točku.

URL krajnje točke ne možete uređivati. Da biste prešli na novi URL, dodajte novu krajnju točku, ažurirajte poslužitelj njezinim tajnim ključem, a zatim izbrišite staru.

Isporuka#

PonašanjePojedinosti
MetodaPOST, tijelo u formatu JSON
RaspodjelaŠalje se svim aktivnim krajnjim točkama istodobno
Vremensko ograničenje5 sekundi. Prije toga odgovorite bilo kojim statusom 2xx.
Ponovni pokušajiNema ih. Istek vremena ili odgovor koji nije 2xx ne šalje se ponovno.
Zapisnik isporukaNije dostupan na nadzornoj ploči
VrijemeAko obrada dovršenog razgovora ne uspije, Diaform ponavlja obradu svakih 30 minuta, najviše 4 pokušaja ukupno. Webhook tada može stići dugo nakon završetka razgovora.

Najprije odgovorite, a sporiji posao obavite poslije, npr. tako da događaj stavite u red čekanja.

Diaform treba dobiti konačni URL. Preusmjeravanja se slijede, ali 301 ili 302 pretvara POST u GET i tijelo se gubi.

Zahtjev#

HTTP
POST /webhooks/diaform HTTP/1.1
Content-Type: application/json
X-Webhook-Event: conversation.completed
X-Webhook-Signature: 4fa4d553f8abf7a199e5d51ee2401320af843de438e63a2ed6bc33f4f943d906
ZaglavljeVrijednost
Content-Typeapplication/json
X-Webhook-Eventconversation.completed
X-Webhook-SignatureHeksadecimalni HMAC-SHA256 neobrađenog tijela, potpisan tajnim ključem krajnje točke

Nema zaglavlja s vremenskom oznakom ni ID-jem isporuke. Kao ključ idempotentnosti koristite conversation_id: spremajte ID-jeve koje ste obradili i preskačite ponavljanja.

Payload#

JSON
{
  "conversation_id": "0f8d43a2-6df9-49c8-bf5d-fd2b6ec2d9ee",
  "agent_id": "b8acb3ab-1f3d-4a7e-a846-b7f493f86b6d",
  "organization_id": "4d6ecdb4-8543-4adf-ae65-1f24106d5474",
  "status": "completed",
  "respondent": {
    "email": "alex@acme.com",
    "metadata": {},
    "identities": [
      { "type": "email", "value": "alex@acme.com", "source": "owner" },
      { "type": "identifier", "value": "customer-123", "source": "owner" },
      { "type": "name", "value": "Alex Morgan", "source": "ai" }
    ],
    "invite_id": "3a4f49c7-b8f4-4c44-8f74-fd8e4277c3e4"
  },
  "summary": {
    "respondent_email": null,
    "respondent_identities": [
      { "type": "email", "value": "alex@acme.com" },
      { "type": "identifier", "value": "customer-123" },
      { "type": "name", "value": "Alex Morgan" }
    ],
    "answers": [
      {
        "question_id": "V1StGXR8_Z5jdHi6B-myT",
        "question_text": "How did your first week with the product go?",
        "summary": "Setup was quick, but Alex struggled to invite teammates.",
        "sentiment": "mixed",
        "confidence": "high",
        "quotes": ["I had everything running in ten minutes."]
      }
    ],
    "overall_sentiment": "mixed",
    "urgent_issues": ["Teammate invites fail for Google Workspace accounts."],
    "additional_insights": "Alex would pay more for an API."
  },
  "completed_at": "2026-05-07T12:15:41.123Z"
}

Polja na najvišoj razini#

PoljeVrstaOpis
conversation_idstringID razgovora. Koristite ga za uklanjanje duplikata.
agent_idstringID projekta kojem razgovor pripada
organization_idstringID vaše organizacije
statusstringcompleted ili cancelled
respondentobjectTko je ispitanik, pogledajte u nastavku
summaryobjectAI sažetak, pogledajte u nastavku
completed_atstringVrijeme završetka razgovora u formatu ISO 8601

respondent#

PoljeVrstaOpis
emailstring ili nullE-adresa ispitanika, ako je poznata
metadataobjectPrazno za izravne poveznice. Za ugrađene razgovore sadrži podatke o ugradnji poput "embed": true i embed_parent_origin.
identitiesarraySve što se zna o ispitaniku, kao type, value i source
identities[].typestringNa primjer email, name, identifier, user_id, account_id ili company
identities[].sourcestringowner: vi ste ga dali, putem pozivne poveznice ili identifikatora iz ugradnje. ai: izdvojen iz poruka ispitanika. system: dodao ga je Diaform.
invite_idstring ili nullID pozivnice kad je razgovor započeo putem pozivne poveznice. Pogledajte API za pozivnice ispitanika.

summary#

PoljeVrstaOpis
respondent_emailnullZastarjelo polje, uvijek null. E-adrese čitajte iz identities.
respondent_identitiesarrayIdentiteti ispitanika kao type i value, bez source
answersarrayJedan unos po pitanju
answers[].question_idstringID pitanja u vašem projektu
answers[].question_textstringTekst pitanja
answers[].summarystringKratak sažetak odgovora
answers[].sentimentstringpositive, negative, neutral ili mixed
answers[].confidencestringKoliko je potpuno pitanje odgovoreno: high, medium ili low
answers[].quotesarray ili nullIstaknuti citati ispitanika. null kad ih nema.
overall_sentimentstringpositive, negative, neutral ili mixed
urgent_issuesarray, null ili izostavljenoKritični problemi koje je AI označio
additional_insightsstring, null ili izostavljenoPovratne informacije koje se ne uklapaju ni u jedno pitanje

Transkript i naziv projekta nisu uključeni. Kako Diaform prikuplja identitete, pročitajte u članku Prepoznavanje ispitanika.

Provjera potpisa#

Provjerite svaki zahtjev prije nego što mu povjerujete. Diaform potpisuje točno tijelo zahtjeva algoritmom HMAC-SHA256, s tajnim ključem krajnje točke kao ključem, i šalje heksadecimalni sažetak u zaglavlju X-Webhook-Signature.

  1. Pročitajte neobrađeno tijelo kao bajtove, prije bilo kakvog parsiranja JSON-a.
  2. Izračunajte HMAC-SHA256 tih bajtova sa svojim tajnim ključem, u heksadecimalnom obliku.
  3. Odbijte zahtjev ako zaglavlje nedostaje ili ima drukčiju duljinu.
  4. Usporedite dvije vrijednosti funkcijom koja radi u konstantnom vremenu.

Koristite cijeli tajni ključ, uključujući prefiks whsec_, kao običan UTF-8 niz. Nemojte ga dekodirati iz base64.

Node.js (Express)#

JavaScript
const crypto = require("node:crypto");
const express = require("express");

const app = express();
const secret = process.env.DIAFORM_WEBHOOK_SECRET;

function isValidSignature(rawBody, signature) {
  if (!Buffer.isBuffer(rawBody) || typeof signature !== "string") {
    return false;
  }

  const expected = Buffer.from(
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  );
  const received = Buffer.from(signature);

  return (
    received.length === expected.length &&
    crypto.timingSafeEqual(received, expected)
  );
}

app.post(
  "/webhooks/diaform",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!isValidSignature(req.body, req.get("X-Webhook-Signature"))) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(req.body.toString("utf8"));
    res.sendStatus(200);

    // Respond first, then do the slow work.
    console.log(event.conversation_id, event.status);
  }
);

app.listen(3000);

Postavite express.raw() na ovu rutu. Ako express.json() prvi parsira tijelo, bajtovi se mijenjaju i nijedan potpis ne prolazi.

Python (Flask)#

Python
import hashlib
import hmac
import json
import os

from flask import Flask, request

app = Flask(__name__)
SECRET = os.environ["DIAFORM_WEBHOOK_SECRET"].encode("utf-8")


def is_valid_signature(raw_body, signature):
    if not signature:
        return False
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected.encode("utf-8"), signature.encode("utf-8"))


@app.post("/webhooks/diaform")
def diaform_webhook():
    raw_body = request.get_data()
    if not is_valid_signature(raw_body, request.headers.get("X-Webhook-Signature")):
        return "Invalid signature", 401

    event = json.loads(raw_body)
    print(event["conversation_id"], event["status"])
    return "", 200

Ako provjera ne uspije#

  • Potpisujte neobrađene bajtove, a ne ponovno serijalizirani JSON objekt.
  • Koristite tajni ključ krajnje točke koja je primila zahtjev. Svaka krajnja točka ima vlastiti.
  • Provjerite ima li u spremljenom tajnom ključu suvišnih razmaka ili prijeloma redaka.