Integrácie

Webhooky

Webhooky posielajú každý skončený rozhovor na váš server ako JSON spolu s AI zhrnutím a údajmi o respondentovi. Pomocou nich môžete aktualizovať CRM, zakladať tikety alebo nahrávať výsledky do vlastnej databázy bez ručného exportu.

Čo sa odosiela a kedy#

Diaform posiela jednu udalosť, conversation.completed. Spustí sa, keď AI moderátor ukončí rozhovor a zhrnutie je hotové, zvyčajne chvíľu po poslednej správe.

  • Pre celú organizáciu. Každý endpoint dostáva udalosti zo všetkých projektov vo vašej organizácii. Projekty rozlíšite podľa agent_id v payloade.
  • Aj zrušené rozhovory spustia udalosť. Ak respondent požiada o skončenie, rozhovor skončí ako zrušený a aj tak dostanete conversation.completed s "status": "cancelled". Než budete odpoveď považovať za dokončenú, skontrolujte status.
  • Opustené rozhovory udalosť nespustia. Ak respondent zavrie stránku bez toho, aby sa rozhovor skončil, nič sa neodošle.
  • Náhľady v dashboarde udalosť nespustia. Na otestovanie dokončite rozhovor cez skutočný odkaz projektu.

Plány a oprávnenia#

Webhooky sú dostupné vo všetkých platených plánoch, nie počas skúšobnej verzie. Každý plán má limit endpointov:

PlánEndpointy
Starter3
Pro5
Business10
Enterprise25

Endpointy môžu pridávať, pozastavovať a odstraňovať len vlastníci a administrátori. Pozrite si Členovia a roly a Správa predplatného.

Pridanie endpointu#

Otvorte webhooky

V bočnom paneli prejdite na Nastavenia → Webhooky.

Zadajte URL

Kliknite na Pridať endpoint, vložte URL do dialógu Nový webhookový endpoint a kliknite na Pridať endpoint. URL musí začínať na https://.

Uložte tajný kľúč

Dialóg Tajný kľúč webhooku ukáže váš podpisový tajný kľúč. Kliknite na Kopírovať, uložte ho do prostredia servera a potom kliknite na Hotovo.

Každý endpoint má vlastný tajný kľúč v tvare whsec_ a za ním 43 znakov. Neskôr ho môžete znova zobraziť alebo skopírovať v riadku Podpisový tajný kľúč pri danom endpointe.

Pozastavenie alebo odstránenie endpointu#

  • Pozastavenie: prepnite prepínač v riadku endpointu z Aktívny na Pozastavený. Udalosti, ktoré nastanú počas pozastavenia, sa neskôr neodošlú.
  • Odstránenie: kliknite na ikonu koša a potvrďte tlačidlom Odstrániť endpoint.

URL endpointu nemôžete upraviť. Ak chcete prejsť na novú URL, pridajte nový endpoint, aktualizujte server jeho tajným kľúčom a potom odstráňte starý.

Doručovanie#

SprávaniePodrobnosti
MetódaPOST, telo vo formáte JSON
RozoslanieOdosiela sa na všetky aktívne endpointy naraz
Časový limit5 sekúnd. Dovtedy odpovedzte ľubovoľným stavom 2xx.
OpakovanieŽiadne. Pri vypršaní času alebo odpovedi inej než 2xx sa udalosť znova neodošle.
Záznam doručeníV dashboarde nie je k dispozícii
NačasovanieAk spracovanie dokončeného rozhovoru zlyhá, Diaform ho skúša spracovať znova každých 30 minút, celkovo najviac 4 pokusy. Webhook potom môže prísť dlho po skončení rozhovoru.

Najprv odpovedzte a pomalú prácu urobte až potom, napríklad tak, že udalosť zaradíte do fronty.

Zadajte do Diaform konečnú URL. Presmerovania sa sledujú, ale 301 alebo 302 zmení POST na GET a telo sa stratí.

Požiadavka#

HTTP
POST /webhooks/diaform HTTP/1.1
Content-Type: application/json
X-Webhook-Event: conversation.completed
X-Webhook-Signature: 4fa4d553f8abf7a199e5d51ee2401320af843de438e63a2ed6bc33f4f943d906
HlavičkaHodnota
Content-Typeapplication/json
X-Webhook-Eventconversation.completed
X-Webhook-SignatureHexadecimálny HMAC-SHA256 nespracovaného tela, podpísaný tajným kľúčom endpointu

Hlavička s časovou pečiatkou ani ID doručenia sa neposiela. Ako kľúč idempotencie použite conversation_id: ukladajte ID, ktoré ste už spracovali, a opakovania preskočte.

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"
}

Polia najvyššej úrovne#

PoleTypPopis
conversation_idstringID rozhovoru. Použite ho na odstránenie duplicít.
agent_idstringID projektu, ku ktorému rozhovor patrí
organization_idstringID vašej organizácie
statusstringcompleted alebo cancelled
respondentobjectKto je respondent, pozri nižšie
summaryobjectAI zhrnutie, pozri nižšie
completed_atstringČas skončenia rozhovoru vo formáte ISO 8601

respondent#

PoleTypPopis
emailstring alebo nullE-mail respondenta, ak je známy
metadataobjectPri priamych odkazoch prázdne. Pri vložených rozhovoroch obsahuje údaje o vložení, napríklad "embed": true a embed_parent_origin.
identitiesarrayVšetko, čo je o respondentovi známe, ako type, value a source
identities[].typestringNapríklad email, name, identifier, user_id, account_id alebo company
identities[].sourcestringowner: dodali ste ho vy, cez odkaz na pozvanie alebo identifikátor vo vložení. ai: získané zo správ respondenta. system: pridal Diaform.
invite_idstring alebo nullID pozvánky, ak rozhovor vznikol z odkazu na pozvanie. Pozrite API pre pozvánky respondentov.

summary#

PoleTypPopis
respondent_emailnullZastarané pole, vždy null. E-maily čítajte z identities.
respondent_identitiesarrayIdentity respondenta ako type a value, bez source
answersarrayJedna položka pre každú otázku
answers[].question_idstringID otázky vo vašom projekte
answers[].question_textstringText otázky
answers[].summarystringKrátke zhrnutie odpovede
answers[].sentimentstringpositive, negative, neutral alebo mixed
answers[].confidencestringAko úplne respondent na otázku odpovedal: high, medium alebo low
answers[].quotesarray alebo nullZaujímavé citáty respondenta. null, ak žiadne nie sú.
overall_sentimentstringpositive, negative, neutral alebo mixed
urgent_issuesarray, null alebo chýbaKritické problémy, ktoré AI označila
additional_insightsstring, null alebo chýbaSpätná väzba, ktorá sa nehodí ku konkrétnej otázke

Prepis ani názov projektu sa neposielajú. Ako Diaform získava identity, sa dozviete v časti Identifikácia respondentov.

Overenie podpisu#

Každú požiadavku si overte skôr, než jej budete dôverovať. Diaform podpisuje presné telo požiadavky pomocou HMAC-SHA256, ako kľúč používa tajný kľúč endpointu a hexadecimálny výsledok posiela v hlavičke X-Webhook-Signature.

  1. Načítajte nespracované telo ako bajty, ešte pred akýmkoľvek parsovaním JSON.
  2. Vypočítajte HMAC-SHA256 týchto bajtov s vaším tajným kľúčom, v hexadecimálnom tvare.
  3. Ak hlavička chýba alebo má inú dĺžku, požiadavku odmietnite.
  4. Porovnajte obe hodnoty funkciou s konštantným časom.

Použite celý tajný kľúč vrátane predpony whsec_ ako obyčajný reťazec UTF-8. Nedekódujte ho z 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);

Na túto routu pripojte express.raw(). Ak telo najprv spracuje express.json(), bajty sa zmenia a žiadny podpis nebude sedieť.

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

Ak overenie zlyhá#

  • Podpisujte nespracované bajty, nie znova serializovaný objekt JSON.
  • Použite tajný kľúč endpointu, ktorý požiadavku prijal. Každý endpoint má vlastný.
  • Skontrolujte, či uložený tajný kľúč neobsahuje medzery alebo zalomenia riadkov navyše.