Integrationer

Webhooks

Webhooks skickar varje avslutad konversation till din server som JSON, med AI-sammanfattningen och uppgifter om respondenten. Använd dem för att uppdatera ditt CRM, skapa ärenden eller läsa in resultat i din egen databas utan att exportera något för hand.

Vad som skickas och när#

Diaform skickar en händelse, conversation.completed. Den skickas när AI-intervjuaren avslutar en konversation och sammanfattningen är klar, oftast en liten stund efter det sista meddelandet.

  • Hela organisationen. Varje endpoint får händelser från alla projekt i din organisation. Använd agent_id i payloaden för att skilja projekten åt.
  • Avbrutna konversationer skickas också. Om en respondent ber om att få sluta avslutas konversationen som avbruten, och du får ändå conversation.completed, med "status": "cancelled". Kontrollera status innan du behandlar ett svar som komplett.
  • Övergivna konversationer skickas inte. Om en respondent stänger sidan utan att konversationen har avslutats skickas ingenting.
  • Förhandsvisningar i dashboarden skickas inte. För att testa slutför du en konversation via projektets riktiga länk.

Planer och behörigheter#

Webhooks finns i alla betalplaner, men inte under den gratis provperioden. Varje plan har en gräns för antalet endpoints:

PlanEndpoints
Starter3
Pro5
Business10
Enterprise25

Bara ägare och admins kan lägga till, pausa eller ta bort endpoints. Läs mer under Medlemmar och roller och Hantera prenumerationen.

Lägg till en endpoint#

Öppna webhooks

Gå till Inställningar → Webhooks i sidofältet.

Ange din URL

Klicka på Lägg till endpoint, klistra in din URL i dialogen Ny webhook-endpoint och klicka på Lägg till endpoint. URL:en måste börja med https://.

Spara hemligheten

Dialogen Webhook-hemlighet visar din signeringshemlighet. Klicka på Kopiera, spara den i serverns miljövariabler och klicka sedan på Klar.

Varje endpoint har en egen hemlighet, i formatet whsec_ följt av 43 tecken. Du kan visa eller kopiera den igen senare via Signeringshemlighet på endpointens rad.

Pausa eller ta bort en endpoint#

  • Pausa: ändra reglaget på endpointens rad från Aktiv till Pausad. Händelser som inträffar medan den är pausad skickas inte i efterhand.
  • Ta bort: klicka på papperskorgsikonen och sedan på Ta bort endpoint för att bekräfta.

Du kan inte redigera en endpoints URL. För att byta till en ny URL lägger du till en ny endpoint, uppdaterar din server med dess hemlighet och tar sedan bort den gamla.

Leverans#

BeteendeDetaljer
MetodPOST, JSON-body
UtskickSkickas till alla aktiva endpoints samtidigt
Timeout5 sekunder. Svara med valfri 2xx-status innan dess.
Nya försökInga. En timeout eller ett svar som inte är 2xx skickas inte igen.
LeveransloggFinns inte i dashboarden
TidpunktOm bearbetningen av en slutförd konversation misslyckas försöker Diaform igen var 30:e minut, upp till 4 försök totalt. Webhooken kan då komma långt efter att konversationen avslutades.

Svara först och gör långsamt arbete efteråt, till exempel genom att lägga händelsen i en kö.

Ge Diaform den slutliga URL:en. Omdirigeringar följs, men en 301 eller 302 gör om POST till GET och bodyn går förlorad.

Förfrågan#

HTTP
POST /webhooks/diaform HTTP/1.1
Content-Type: application/json
X-Webhook-Event: conversation.completed
X-Webhook-Signature: 4fa4d553f8abf7a199e5d51ee2401320af843de438e63a2ed6bc33f4f943d906
HeaderVärde
Content-Typeapplication/json
X-Webhook-Eventconversation.completed
X-Webhook-SignatureHex-kodad HMAC-SHA256 av den råa bodyn, signerad med endpointens hemlighet

Det finns ingen header med tidsstämpel eller leverans-ID. Använd conversation_id som idempotensnyckel: spara de ID:n du har hanterat och hoppa över upprepningar.

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

Fält på toppnivå#

FältTypBeskrivning
conversation_idstringKonversationens ID. Använd det för att ta bort dubbletter.
agent_idstringID för projektet som konversationen hör till
organization_idstringDin organisations ID
statusstringcompleted eller cancelled
respondentobjectVem respondenten är, se nedan
summaryobjectAI-sammanfattningen, se nedan
completed_atstringTidpunkt i ISO 8601 då konversationen avslutades

respondent#

FältTypBeskrivning
emailstring eller nullRespondentens e-postadress, om den är känd
metadataobjectTomt för direktlänkar. För inbäddade konversationer innehåller det uppgifter om inbäddningen, som "embed": true och embed_parent_origin.
identitiesarrayAllt som är känt om respondenten, som type, value och source
identities[].typestringTill exempel email, name, identifier, user_id, account_id eller company
identities[].sourcestringowner: du angav den, via en inbjudningslänk eller en identifierare i inbäddningen. ai: hämtad ur respondentens meddelanden. system: tillagd av Diaform.
invite_idstring eller nullInbjudans id när konversationen kom från en inbjudningslänk. Se Respondentinbjudnings-API.

summary#

FältTypBeskrivning
respondent_emailnullÄldre fält, alltid null. Läs e-postadresser från identities.
respondent_identitiesarrayRespondentens identiteter som type och value, utan source
answersarrayEn post per fråga
answers[].question_idstringFrågans ID i ditt projekt
answers[].question_textstringFrågetexten
answers[].summarystringKort sammanfattning av svaret
answers[].sentimentstringpositive, negative, neutral eller mixed
answers[].confidencestringHur fullständigt frågan besvarades: high, medium eller low
answers[].quotesarray eller nullCitat från respondenten som sticker ut. null när det inte finns några.
overall_sentimentstringpositive, negative, neutral eller mixed
urgent_issuesarray, null eller saknasKritiska problem som AI:n har flaggat
additional_insightsstring, null eller saknasFeedback som inte passar under en viss fråga

Transkriptionen och projektets namn ingår inte. Hur Diaform samlar in identiteter står i Identifiera respondenter.

Verifiera signaturen#

Kontrollera varje förfrågan innan du litar på den. Diaform signerar den exakta bodyn i förfrågan med HMAC-SHA256, med endpointens hemlighet som nyckel, och skickar den hex-kodade hashen i X-Webhook-Signature.

  1. Läs den råa bodyn som bytes, innan du tolkar någon JSON.
  2. Beräkna HMAC-SHA256 av de bytesen med din hemlighet, som hex.
  3. Avvisa förfrågan om headern saknas eller har en annan längd.
  4. Jämför de två värdena med en funktion som körs i konstant tid.

Använd hela hemligheten, inklusive prefixet whsec_, som en vanlig UTF-8-sträng. Base64-avkoda den inte.

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

Montera express.raw() på den här routen. Om express.json() tolkar bodyn först ändras bytesen och alla signaturer misslyckas.

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

Om verifieringen misslyckas#

  • Signera de råa bytesen, inte ett JSON-objekt som har serialiserats om.
  • Använd hemligheten för den endpoint som tog emot förfrågan. Varje endpoint har sin egen.
  • Kontrollera att hemligheten du sparade inte har fått med extra mellanslag eller radbrytningar.