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_idi 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". Kontrollerastatusinnan 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:
| Plan | Endpoints |
|---|---|
| Starter | 3 |
| Pro | 5 |
| Business | 10 |
| Enterprise | 25 |
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#
| Beteende | Detaljer |
|---|---|
| Metod | POST, JSON-body |
| Utskick | Skickas till alla aktiva endpoints samtidigt |
| Timeout | 5 sekunder. Svara med valfri 2xx-status innan dess. |
| Nya försök | Inga. En timeout eller ett svar som inte är 2xx skickas inte igen. |
| Leveranslogg | Finns inte i dashboarden |
| Tidpunkt | Om 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#
POST /webhooks/diaform HTTP/1.1
Content-Type: application/json
X-Webhook-Event: conversation.completed
X-Webhook-Signature: 4fa4d553f8abf7a199e5d51ee2401320af843de438e63a2ed6bc33f4f943d906| Header | Värde |
|---|---|
Content-Type | application/json |
X-Webhook-Event | conversation.completed |
X-Webhook-Signature | Hex-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#
{
"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ält | Typ | Beskrivning |
|---|---|---|
conversation_id | string | Konversationens ID. Använd det för att ta bort dubbletter. |
agent_id | string | ID för projektet som konversationen hör till |
organization_id | string | Din organisations ID |
status | string | completed eller cancelled |
respondent | object | Vem respondenten är, se nedan |
summary | object | AI-sammanfattningen, se nedan |
completed_at | string | Tidpunkt i ISO 8601 då konversationen avslutades |
respondent#
| Fält | Typ | Beskrivning |
|---|---|---|
email | string eller null | Respondentens e-postadress, om den är känd |
metadata | object | Tomt för direktlänkar. För inbäddade konversationer innehåller det uppgifter om inbäddningen, som "embed": true och embed_parent_origin. |
identities | array | Allt som är känt om respondenten, som type, value och source |
identities[].type | string | Till exempel email, name, identifier, user_id, account_id eller company |
identities[].source | string | owner: du angav den, via en inbjudningslänk eller en identifierare i inbäddningen. ai: hämtad ur respondentens meddelanden. system: tillagd av Diaform. |
invite_id | string eller null | Inbjudans id när konversationen kom från en inbjudningslänk. Se Respondentinbjudnings-API. |
summary#
| Fält | Typ | Beskrivning |
|---|---|---|
respondent_email | null | Äldre fält, alltid null. Läs e-postadresser från identities. |
respondent_identities | array | Respondentens identiteter som type och value, utan source |
answers | array | En post per fråga |
answers[].question_id | string | Frågans ID i ditt projekt |
answers[].question_text | string | Frågetexten |
answers[].summary | string | Kort sammanfattning av svaret |
answers[].sentiment | string | positive, negative, neutral eller mixed |
answers[].confidence | string | Hur fullständigt frågan besvarades: high, medium eller low |
answers[].quotes | array eller null | Citat från respondenten som sticker ut. null när det inte finns några. |
overall_sentiment | string | positive, negative, neutral eller mixed |
urgent_issues | array, null eller saknas | Kritiska problem som AI:n har flaggat |
additional_insights | string, null eller saknas | Feedback 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.
- Läs den råa bodyn som bytes, innan du tolkar någon JSON.
- Beräkna HMAC-SHA256 av de bytesen med din hemlighet, som hex.
- Avvisa förfrågan om headern saknas eller har en annan längd.
- 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)#
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)#
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 "", 200Om 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.