Webhooks
Webhooks sender hver avsluttede samtale til serveren din som JSON, med AI-sammendraget og opplysninger om respondenten. Bruk dem til å oppdatere CRM-et, opprette saker eller laste resultatene inn i din egen database uten å eksportere noe for hånd.
Hva som sendes, og når#
Diaform sender én hendelse, conversation.completed. Den sendes når AI-intervjueren avslutter en samtale og sammendraget er klart, vanligvis kort tid etter den siste meldingen.
- For hele organisasjonen. Hvert endepunkt får hendelser fra alle prosjektene i organisasjonen. Bruk
agent_idi payloaden for å skille prosjektene fra hverandre. - Avbrutte samtaler sendes også. Hvis en respondent ber om å stoppe, avsluttes samtalen som avbrutt, og du får likevel
conversation.completed, med"status": "cancelled". Sjekkstatusfør du behandler et svar som fullført. - Forlatte samtaler sendes ikke. Hvis en respondent lukker siden uten at samtalen er avsluttet, sendes ingenting.
- Forhåndsvisninger i dashbordet sendes ikke. For å teste fullfører du en samtale via den ekte lenken til prosjektet.
Planer og tilganger#
Webhooks er tilgjengelige i alle betalte planer, men ikke i den gratis prøveperioden. Hver plan har en grense for antall endepunkter:
| Plan | Endepunkter |
|---|---|
| Starter | 3 |
| Pro | 5 |
| Business | 10 |
| Enterprise | 25 |
Bare eiere og administratorer kan legge til, sette på pause eller slette endepunkter. Se Medlemmer og roller og Administrer abonnementet.
Legg til et endepunkt#
Åpne webhooks
Gå til Innstillinger → Webhooks i sidepanelet.
Skriv inn URL-en
Klikk på Legg til endepunkt, lim inn URL-en i dialogen Nytt webhook-endepunkt og klikk på Legg til endepunkt. URL-en må starte med https://.
Lagre hemmeligheten
Dialogen Webhook-hemmelighet viser signeringshemmeligheten din. Klikk på Kopier, lagre den i miljøet på serveren din og klikk deretter på Ferdig.
Hvert endepunkt har sin egen hemmelighet, på formen whsec_ etterfulgt av 43 tegn. Du kan vise eller kopiere den igjen senere fra linjen Signeringshemmelighet på raden til endepunktet.
Sett på pause eller slett et endepunkt#
- Sett på pause: bytt bryteren på raden til endepunktet fra Aktiv til På pause. Hendelser som skjer mens det står på pause, sendes ikke senere.
- Slett: klikk på søppelbøtteikonet og deretter på Slett endepunktet for å bekrefte.
Du kan ikke endre URL-en til et endepunkt. For å flytte til en ny URL legger du til et nytt endepunkt, oppdaterer serveren med hemmeligheten til det nye og sletter det gamle.
Levering#
| Oppførsel | Detaljer |
|---|---|
| Metode | POST, JSON-innhold |
| Utsending | Sendes til alle aktive endepunkter samtidig |
| Tidsavbrudd | 5 sekunder. Svar med en hvilken som helst 2xx-status før det. |
| Nye forsøk | Ingen. Et tidsavbrudd eller et svar som ikke er 2xx, sendes ikke på nytt. |
| Leveringslogg | Ikke tilgjengelig i dashbordet |
| Tidspunkt | Hvis behandlingen av en fullført samtale feiler, prøver Diaform behandlingen på nytt hvert 30. minutt, opptil 4 forsøk totalt. Webhooken kan da komme lenge etter at samtalen ble avsluttet. |
Svar først og gjør det tunge arbeidet etterpå, for eksempel ved å legge hendelsen i en kø.
Gi Diaform den endelige URL-en. Omdirigeringer følges, men en 301 eller 302 gjør POST om til GET, og innholdet går tapt.
Forespørsel#
POST /webhooks/diaform HTTP/1.1
Content-Type: application/json
X-Webhook-Event: conversation.completed
X-Webhook-Signature: 4fa4d553f8abf7a199e5d51ee2401320af843de438e63a2ed6bc33f4f943d906| Header | Verdi |
|---|---|
Content-Type | application/json |
X-Webhook-Event | conversation.completed |
X-Webhook-Signature | Hex-kodet HMAC-SHA256 av det rå innholdet, signert med hemmeligheten til endepunktet |
Det finnes ingen header for tidsstempel eller leverings-ID. Bruk conversation_id som idempotensnøkkel: lagre ID-ene du har behandlet, og hopp over gjentakelser.
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"
}Felt på toppnivå#
| Felt | Type | Beskrivelse |
|---|---|---|
conversation_id | string | ID-en til samtalen. Bruk den til å fjerne duplikater. |
agent_id | string | ID-en til prosjektet samtalen hører til |
organization_id | string | ID-en til organisasjonen din |
status | string | completed eller cancelled |
respondent | object | Hvem respondenten er, se nedenfor |
summary | object | AI-sammendraget, se nedenfor |
completed_at | string | ISO 8601-tidspunktet da samtalen ble avsluttet |
respondent#
| Felt | Type | Beskrivelse |
|---|---|---|
email | string eller null | E-postadressen til respondenten, hvis den er kjent |
metadata | object | Tomt for direkte lenker. For innebygde samtaler inneholder det detaljer om innbyggingen, for eksempel "embed": true og embed_parent_origin. |
identities | array | Alt som er kjent om respondenten, som type, value og source |
identities[].type | string | For eksempel email, name, identifier, user_id, account_id eller company |
identities[].source | string | owner: du oppga den, via en invitasjonslenke eller en identifikator i innbyggingen. ai: hentet ut fra meldingene til respondenten. system: lagt til av Diaform. |
invite_id | string eller null | Invitasjons-ID når samtalen kom fra en invitasjonslenke. Se Respondentinvitasjons-API. |
summary#
| Felt | Type | Beskrivelse |
|---|---|---|
respondent_email | null | Eldre felt, alltid null. Les e-postadresser fra identities. |
respondent_identities | array | Identitetene til respondenten som type og value, uten source |
answers | array | Én oppføring per spørsmål |
answers[].question_id | string | ID-en til spørsmålet i prosjektet ditt |
answers[].question_text | string | Spørsmålsteksten |
answers[].summary | string | Kort sammendrag av svaret |
answers[].sentiment | string | positive, negative, neutral eller mixed |
answers[].confidence | string | Hvor fullstendig spørsmålet ble besvart: high, medium eller low |
answers[].quotes | array eller null | Sitater fra respondenten som skiller seg ut. null når det ikke finnes noen. |
overall_sentiment | string | positive, negative, neutral eller mixed |
urgent_issues | array, null eller mangler | Kritiske problemer AI-en har flagget |
additional_insights | string, null eller mangler | Tilbakemeldinger som ikke passer til et bestemt spørsmål |
Transkripsjonen og navnet på prosjektet er ikke med. For å se hvordan Diaform samler inn identiteter, se Identifiser respondenter.
Bekreft signaturen#
Sjekk hver forespørsel før du stoler på den. Diaform signerer det nøyaktige innholdet i forespørselen med HMAC-SHA256, med hemmeligheten til endepunktet som nøkkel, og sender den hex-kodede verdien i X-Webhook-Signature.
- Les det rå innholdet som bytes, før all JSON-parsing.
- Beregn HMAC-SHA256 av disse bytene med hemmeligheten din, som hex.
- Avvis forespørselen hvis headeren mangler eller har en annen lengde.
- Sammenlign de to verdiene med en funksjon som bruker konstant tid.
Bruk hele hemmeligheten, inkludert prefikset whsec_, som en vanlig UTF-8-streng. Ikke base64-dekod den.
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);Monter express.raw() på denne ruten. Hvis express.json() parser innholdet først, endres bytene, og alle signaturer feiler.
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 "", 200Hvis bekreftelsen feiler#
- Signer de rå bytene, ikke et JSON-objekt som er serialisert på nytt.
- Bruk hemmeligheten til endepunktet som tok imot forespørselen. Hvert endepunkt har sin egen.
- Sjekk om hemmeligheten du lagret, har mellomrom eller linjeskift som ikke hører hjemme der.