Integraatiot

Webhookit

Webhookit lähettävät jokaisen päättyneen keskustelun palvelimellesi JSON-muodossa AI-yhteenvedon ja vastaajan tietojen kera. Niiden avulla voit päivittää CRM:si, avata tikettejä tai ladata tulokset omaan tietokantaasi viemättä mitään käsin.

Mikä laukeaa ja milloin#

Diaform lähettää yhden tapahtuman, conversation.completed. Se laukeaa, kun AI-haastattelija päättää keskustelun ja yhteenveto on valmis, yleensä hetki viimeisen viestin jälkeen.

  • Koko organisaation laajuinen. Jokainen päätepiste saa tapahtumat organisaatiosi kaikista projekteista. Erota projektit toisistaan tietosisällön agent_id-kentällä.
  • Myös perutut keskustelut laukaisevat sen. Jos vastaaja pyytää lopettamaan, keskustelu päättyy perutuksi ja saat silti tapahtuman conversation.completed arvolla "status": "cancelled". Tarkista status ennen kuin käsittelet vastausta valmiina.
  • Kesken jätetyt keskustelut eivät laukaise sitä. Jos vastaaja sulkee sivun ilman, että keskustelu päättyy, mitään ei lähetetä.
  • Hallintapaneelin esikatselut eivät laukaise sitä. Testaa käymällä keskustelu loppuun projektin oikean linkin kautta.

Paketit ja oikeudet#

Webhookit ovat käytettävissä kaikissa maksullisissa paketeissa, mutta eivät ilmaisen kokeilun aikana. Jokaisella paketilla on päätepisteraja:

PakettiPäätepisteet
Starter3
Pro5
Business10
Enterprise25

Vain omistajat ja ylläpitäjät voivat lisätä, keskeyttää tai poistaa päätepisteitä. Katso Jäsenet ja roolit ja Tilauksen hallinta.

Lisää päätepiste#

Avaa webhookit

Siirry sivupalkissa kohtaan Asetukset → Webhookit.

Anna URL-osoite

Klikkaa Lisää päätepiste, liitä URL-osoitteesi Uusi webhook-päätepiste -ikkunaan ja klikkaa Lisää päätepiste. URL-osoitteen on alettava https://.

Tallenna salaisuus

Webhook-salaisuus-ikkuna näyttää allekirjoitussalaisuutesi. Klikkaa Kopioi, tallenna salaisuus palvelimesi ympäristöön ja klikkaa sitten Valmis.

Jokaisella päätepisteellä on oma salaisuutensa, joka on muotoa whsec_ ja sen perässä 43 merkkiä. Voit näyttää tai kopioida sen myöhemmin uudelleen päätepisteen rivin Allekirjoitussalaisuus-kohdasta.

Keskeytä tai poista päätepiste#

  • Keskeytä: vaihda päätepisteen rivin kytkin tilasta Aktiivinen tilaan Keskeytetty. Keskeytyksen aikana tapahtuneita tapahtumia ei lähetetä jälkikäteen.
  • Poista: klikkaa roskakorikuvaketta ja vahvista valitsemalla Poista päätepiste.

Päätepisteen URL-osoitetta ei voi muokata. Jos haluat siirtyä uuteen URL-osoitteeseen, lisää uusi päätepiste, päivitä palvelimellesi sen salaisuus ja poista sitten vanha.

Toimitus#

ToimintaTiedot
MetodiPOST, JSON-runko
JakeluLähetetään kaikkiin aktiivisiin päätepisteisiin samanaikaisesti
Aikakatkaisu5 sekuntia. Vastaa millä tahansa 2xx-tilakoodilla ennen sitä.
UudelleenyrityksetEi ole. Aikakatkaisun tai muun kuin 2xx-vastauksen jälkeen pyyntöä ei lähetetä uudelleen.
ToimituslokiEi saatavilla hallintapaneelissa
AjoitusJos valmiin keskustelun käsittely epäonnistuu, Diaform yrittää käsittelyä uudelleen 30 minuutin välein, yhteensä enintään 4 kertaa. Webhook voi silloin saapua selvästi keskustelun päättymisen jälkeen.

Vastaa ensin ja tee hidas työ vasta sen jälkeen, esimerkiksi laittamalla tapahtuma jonoon.

Anna Diaformille lopullinen URL-osoite. Uudelleenohjauksia seurataan, mutta 301 tai 302 muuttaa POST-pyynnön GET-pyynnöksi, ja runko katoaa.

Pyyntö#

HTTP
POST /webhooks/diaform HTTP/1.1
Content-Type: application/json
X-Webhook-Event: conversation.completed
X-Webhook-Signature: 4fa4d553f8abf7a199e5d51ee2401320af843de438e63a2ed6bc33f4f943d906
OtsakeArvo
Content-Typeapplication/json
X-Webhook-Eventconversation.completed
X-Webhook-SignatureRaa'an rungon HMAC-SHA256 heksamuodossa, allekirjoitettu päätepisteen salaisuudella

Aikaleima- tai toimitus-ID-otsaketta ei ole. Käytä conversation_id-arvoa idempotenssiavaimena: tallenna käsittelemäsi ID:t ja ohita toistot.

Tietosisältö#

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

Ylätason kentät#

KenttäTyyppiKuvaus
conversation_idstringKeskustelun ID. Käytä sitä kaksoiskappaleiden poistoon.
agent_idstringSen projektin ID, johon keskustelu kuuluu
organization_idstringOrganisaatiosi ID
statusstringcompleted tai cancelled
respondentobjectKuka vastaaja on, katso alla
summaryobjectAI-yhteenveto, katso alla
completed_atstringKeskustelun päättymisaika ISO 8601 -muodossa

respondent#

KenttäTyyppiKuvaus
emailstring tai nullVastaajan sähköpostiosoite, jos se on tiedossa
metadataobjectTyhjä suorissa linkeissä. Upotetuissa keskusteluissa siinä on upotuksen tietoja, kuten "embed": true ja embed_parent_origin.
identitiesarrayKaikki vastaajasta tiedossa oleva muodossa type, value ja source
identities[].typestringEsimerkiksi email, name, identifier, user_id, account_id tai company
identities[].sourcestringowner: sinä annoit sen kutsulinkin tai upotuksen tunnisteen kautta. ai: poimittu vastaajan viesteistä. system: Diaformin lisäämä.
invite_idstring tai nullKutsun tunnus, kun keskustelu alkoi kutsulinkistä. Katso Vastaajakutsu-API.

summary#

KenttäTyyppiKuvaus
respondent_emailnullVanha kenttä, aina null. Lue sähköpostiosoitteet kentästä identities.
respondent_identitiesarrayVastaajan tunnisteet muodossa type ja value, ilman kenttää source
answersarrayYksi merkintä kutakin kysymystä kohden
answers[].question_idstringKysymyksen ID projektissasi
answers[].question_textstringKysymyksen teksti
answers[].summarystringLyhyt yhteenveto vastauksesta
answers[].sentimentstringpositive, negative, neutral tai mixed
answers[].confidencestringKuinka kattavasti kysymykseen vastattiin: high, medium tai low
answers[].quotesarray tai nullVastaajan huomionarvoiset lainaukset. null, kun niitä ei ole.
overall_sentimentstringpositive, negative, neutral tai mixed
urgent_issuesarray, null tai puuttuuAI:n merkitsemät kriittiset ongelmat
additional_insightsstring, null tai puuttuuPalaute, joka ei sovi minkään tietyn kysymyksen alle

Litterointi ja projektin nimi eivät ole mukana. Lue, miten Diaform kerää tunnisteita, kohdasta Vastaajien tunnistaminen.

Tarkista allekirjoitus#

Tarkista jokainen pyyntö ennen kuin luotat siihen. Diaform allekirjoittaa pyynnön rungon täsmälleen sellaisenaan HMAC-SHA256:lla käyttäen avaimena päätepisteen salaisuutta ja lähettää heksamuotoisen tiivisteen otsakkeessa X-Webhook-Signature.

  1. Lue raaka runko tavuina ennen JSON-jäsennystä.
  2. Laske näiden tavujen HMAC-SHA256 salaisuudellasi heksamuodossa.
  3. Hylkää pyyntö, jos otsake puuttuu tai sen pituus on eri.
  4. Vertaa kahta arvoa vakioaikaisella funktiolla.

Käytä koko salaisuutta whsec_-etuliite mukaan lukien tavallisena UTF-8-merkkijonona. Älä pura sitä base64-koodauksesta.

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

Ota express.raw() käyttöön tällä reitillä. Jos express.json() jäsentää rungon ensin, tavut muuttuvat ja jokainen allekirjoitus epäonnistuu.

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

Jos tarkistus epäonnistuu#

  • Allekirjoita raa'at tavut, älä uudelleen sarjallistettua JSON-objektia.
  • Käytä sen päätepisteen salaisuutta, joka vastaanotti pyynnön. Jokaisella päätepisteellä on omansa.
  • Tarkista, ettei tallentamassasi salaisuudessa ole ylimääräisiä välilyöntejä tai rivinvaihtoja.