Bygg et endepunkt
Du trenger ett offentlig HTTPS-endepunkt som svarer med JSON. Vi kaller det med
metodene GET, POST, PUT eller
PATCH, legger feltene du har definert inn i stien, query-strengen
eller bodyen, og sender med faste headere.
Kontrakten i korte trekk:
- Metode:
GET, POST,
PUT eller PATCH.
- URL: base-URL-en du oppgir i portalen pluss stien i
verktøyet, for eksempel
https://api.dittfirma.no +
/svaria/ordrestatus.
- Payload: alltid flat. Query-strengen er
nøkkel=verdi-par, og bodyen er et flatt JSON-objekt. Vi sender
aldri arrays eller nestede objekter.
- Verdier: sendes som tekst. Trenger APIet ditt tall eller
ja/nei, konverterer du det på din side.
- Svar: JSON. Vi gir hele svaret, eller et utdrag du peker
på, til agenten som tekst.
- Tid og størrelse: sikt på under 3 sekunder. Vi gir opp
etter 4 sekunder i tale og 8 sekunder i chat, og avviser svar større enn
8 kB.
- Feil: HTTP 2xx regnes som ok. Alt annet er en teknisk feil;
i chat prøver vi én gang til ved 5xx eller timeout.
GET med query-streng og de faste headerne:
GET https://api.dittfirma.no/svaria/ordrestatus?ordrenummer=12345&phone=%2B4790000000
Authorization: Bearer DIN_NØKKEL
Accept: application/json
X-Svaria-Integration-Id: abc123def4
X-Svaria-Call-Id: k3F9xQ2mZp
Bodyen er JSON når metoden er POST, PUT eller
PATCH, med Content-Type: application/json:
POST https://api.dittfirma.no/svaria/ordrestatus
Content-Type: application/json
{
"ordrenummer": "12345",
"phone": "+4790000000"
}
Headere vi sender
Accept: application/json - alltid.
Content-Type: application/json - når vi sender body
(POST, PUT, PATCH).
X-Svaria-Integration-Id - integrasjonens id, alltid. Bruk den
til å se hvilken integrasjon kallet gjelder.
X-Svaria-Call-Id - samtalens id, når oppslaget kommer fra en
telefonsamtale. Sendes ikke fra chat.
- Autentisering etter valget ditt:
Authorization: Bearer ...,
egen header, Basic eller HMAC. Se
Autentisering.
Svar fra endepunktet
Svar med JSON og HTTP 2xx. Vi sender hele JSON-svaret til agenten som tekst, slik
at den kan formulere svaret til kunden. Har du mye data, kan du peke på akkurat
den delen agenten trenger med et responsutdrag i verktøyet. Stien
bruker punktnotasjon, og tall indekserer lister:
{
"data": {
"ordrer": [
{ "status": "sendt", "sporing": "ABC123", "intern": "skjules" }
]
}
}
# Responsutdrag: data.ordrer.0.status -> "sendt"
- Finner vi ikke stien, eller er svaret tomt, får agenten beskjed om at
oppslaget ikke ga treff - den spør da kunden om noe var feil.
- HTTP 4xx og 5xx, timeout og svar over 8 kB blir en teknisk feil. Agenten sier
kort beklager og tilbyr å ta imot beskjed.
- I chat gjør vi ett nytt forsøk ved 5xx eller timeout. I tale gjør vi det ikke,
for å unngå lang ventetid.
Hvilke felter som finnes, hvor verdiene kommer fra, og hvordan de mappes til
forespørselen er beskrevet i Felter vi sender.
1. Koble til i portalen
Gå til Portal → Integrasjoner → Ny integrasjon. Velg navn, lim inn
base-URL og velg autentisering (Bearer, egen header, brukernavn/passord eller
HMAC-signatur). Trykk «Test tilkobling».
2. Definer verktøyet
Et verktøy har en nøkkel (ordrestatus), et navn, en beskrivelse av
når det skal brukes, og feltene som skal sendes til endepunktet ditt.
Beskrivelsen er den viktigste teksten - den er instruksjonen agenten leser.
God beskrivelse: «Bruk denne når kunden spør om status på en ordre
og har oppgitt ordrenummer.»
Dårlig beskrivelse: «Slår opp ordre.»
Feltene deles i tre grupper - se Felter vi sender.
Felter vi sender
Når agenten bruker verktøyet, sender vi en HTTP-forespørsel til endepunktet ditt.
Den inneholder bare feltene du har lagt til i verktøyet, og hvert felt har et
payload-navn (nøkkelen som sendes, på engelsk) og en kilde. Verdien
fylles ut på én av to måter:
- Spørsmål - agenten spør innringeren og sender svaret.
- Systemfelt - vi fyller fra samtalen eller chatten, uten at
agenten spør.
Uansett kilde mappes feltet inn i stien, query-strengen eller bodyen med
{{payload_navn}}, akkurat der du vil ha det. Felter uten verdi
utelates helt fra forespørselen.
Flat payload - ingen arrays
Forespørselen vi sender er alltid flat, uansett hvor mange felter du legger til:
- Sti: én tekststreng, som
/svaria/bookinger/BK-10231.
- Query:
nøkkel=verdi-par, ett felt per
nøkkel, som ?phone=%2B4790000000&channel=voice.
- Body: ett flatt JSON-objekt,
nøkkel: "verdi",
uten arrays eller nestede objekter.
Vi sender aldri arrays eller objekter i forespørselen, og alle verdier sendes
som tekst. Trenger APIet ditt tall eller ja/nei, konverterer du verdien på din
side. Har du behov for flere verdier på samme nøkkel, bruker du separate
nøkler, for eksempel item_1 og item_2. Svaret ditt
kan derimot være så nøstet du vil - se
Svar fra endepunktet. Verdier i stien og
query-strengen URL-enkodes automatisk, så du trenger ikke enkode dem selv.
Spørsmål til innringeren
Hvert spørsmål har en informasjon (navnet du og kunden ser), et
payload-navn, en spørsmålstekst, en
kontekst (hvorfor vi trenger opplysningen, slik at agenten kan
forklare det), en type og om feltet er påkrevd. Spørsmålet og
konteksten blir agentens instruks. Er feltet påkrevd, spør agenten til det har
fått det; er det valgfritt, går oppslaget videre uten. Typen styrer hva agenten
godtar som gyldig svar.
| Informasjon |
Payload-navn |
Type |
Spørsmål |
Kontekst |
Påkrevd |
| Bookingnummer |
booking_ref |
tekst |
Hva er bookingnummeret ditt? |
Vi trenger bookingnummeret for å finne reservasjonen din. |
Ja |
| Navn |
full_name |
tekst |
Hva heter du? |
Vi bruker navnet til å bekrefte bookingen. |
Nei |
| Antall gjester |
party_size |
heltall |
Hvor mange gjester kommer dere? |
Vi trenger det for å sette av nok plass. |
Nei |
| Ønsket dato |
booking_date |
tekst |
Hvilken dato ønsker du å flytte til? |
Vi finner ledige tider den dagen. |
Ja |
| Vil bli oppringt |
callback_requested |
ja/nei |
Ønsker du at vi ringer deg tilbake? |
Vi trenger å vite om du vil bli kontaktet. |
Nei |
Metadata om samtalen
Faste opplysninger om samtalen eller chatten. Huk av de du vil sende, og gi dem
gjerne et eget payload-navn.
| Kilde |
Innhold |
Eksempel payload-navn |
channel | voice eller chat | channel |
started_at | Når samtalen startet (ISO 8601) | started_at |
public_id | Samtale- eller sesjons-id | call_id |
caller_number | Nummeret som ringer inn (ved tale) | caller_phone |
called_to | Nummeret det ringes til (ved tale) | called_to |
Data fra Svaria
Opplysninger vi allerede har om kunden, fra innringer- eller besøksprofilen.
Sendes bare hvis de finnes.
| Kilde |
Innhold |
Eksempel payload-navn |
customer_name | Kundens navn | full_name |
customer_email | Kundens e-post | email |
customer_phone | Kundens telefonnummer | phone |
customer_company | Firma | company |
customer_address | Adresse | address |
customer_category | Kategori | category |
Fullt eksempel
Et POST-verktøy som lar agenten finne og endre en booking. Feltene
under er satt opp i portalen. Legg merke til at informasjonsnavnet er det kunden
ser, mens payload-navnet er nøkkelen som sendes - de kan være ulike.
| Informasjon |
Payload-navn |
Kilde |
Spørsmål |
Kontekst |
Påkrevd |
| Bookingnummer |
booking_ref |
Spørsmål |
Hva er bookingnummeret ditt? |
Vi trenger det for å finne bookingen din. |
Ja |
| Hva vil du endre? |
customer_note |
Spørsmål |
Hva ønsker du å endre? |
|
Nei |
| Kundens navn | customer_name | Data: customer_name | | | |
| Kundens e-post | customer_email | Data: customer_email | | | |
| Telefonnummer | caller_phone | Metadata: caller_number | | | |
| Nummeret det ringes til | called_to | Metadata: called_to | | | |
| Samtalen startet | started_at | Metadata: started_at | | | |
| Kanal | channel | Metadata: channel | | | |
| Samtale-id | call_id | Metadata: public_id | | | |
Feltene mappes slik i verktøyet:
- Sti:
/svaria/bookinger/{{booking_ref}}
- Query:
source={{channel}} og phone={{caller_phone}}
- Body:
note={{customer_note}},
customer_name={{customer_name}},
customer_email={{customer_email}},
called_to={{called_to}},
started_at={{started_at}},
call_id={{call_id}}
Agenten har spurt om bookingnummeret og hva kunden vil endre, og resten er
systemfelter. Forespørselen til deg blir da:
POST https://api.dittfirma.no/svaria/bookinger/BK-10231?source=voice&phone=%2B4790000000
Authorization: Bearer DIN_NØKKEL
Accept: application/json
Content-Type: application/json
X-Svaria-Integration-Id: abc123def4
X-Svaria-Call-Id: k3F9xQ2mZp
{
"note": "Vil flytte til fredag",
"customer_name": "Kari Nordmann",
"customer_email": "kari@example.no",
"called_to": "+4722334455",
"started_at": "2026-10-05T10:00:00+02:00",
"call_id": "k3F9xQ2mZp"
}
Er innringeren ukjent, eller sier kunden nei til å oppgi notat, utelates de
feltene helt - vi sender aldri tomme verdier. De påkrevde feltene
(booking_ref) må agenten skaffe før kallet går.
Med GET havner de samme feltene i query-strengen, URL-enkodet:
/svaria/bookinger/BK-10231?source=voice&phone=%2B4790000000.
Det sender vi ikke
Utover feltene over sender vi ingen samtaledata. Transkript, opptak,
sammendrag og kunnskapsbaseinnhold blir aldri med til endepunktet ditt. Vil du ha
for eksempel e-postadressen til en kunde vi kjenner, legger du til
customer_email som systemfelt og mapper det inn selv.
3. Test
Fyll inn eksempelverdier og trykk «Kjør testkall». Du ser HTTP-status, tid og
nøyaktig teksten agenten får.
Eksempler
Node/Express
app.get('/svaria/ordrestatus', async (req, res) => {
if (req.headers.authorization !== 'Bearer DIN_NØKKEL') {
return res.status(401).json({ error: 'unauthorized' });
}
const ordre = await hentOrdre(req.query.ordrenummer);
if (!ordre) return res.status(404).json({ error: 'ikke funnet' });
res.json({ status: ordre.status, sporing: ordre.sporing, forventet: ordre.forventet });
});
PHP
<?php
if (($_SERVER['HTTP_AUTHORIZATION'] ?? '') !== 'Bearer DIN_NØKKEL') {
http_response_code(401);
exit(json_encode(['error' => 'unauthorized']));
}
$ordre = hent_ordre($_GET['ordrenummer'] ?? '');
if ($ordre === null) {
http_response_code(404);
exit(json_encode(['error' => 'ikke funnet']));
}
header('Content-Type: application/json');
echo json_encode($ordre);
C#/.NET
[HttpGet("/svaria/ordrestatus")]
public IActionResult Ordrestatus(string ordrenummer)
{
if (Request.Headers.Authorization != "Bearer DIN_NØKKEL")
return Unauthorized();
var ordre = _repository.Find(ordrenummer);
return ordre is null ? NotFound() : Ok(new { ordre.Status, ordre.Sporing, ordre.Forventet });
}
Webhooks ut
Agenten kan også varsle systemene dine. Når noe skjer - en samtale
fullføres, en booking opprettes eller avbestilles, en beskjed kommer inn,
eller chatten ber om et menneske - sender vi en signert HTTP-postering til
en URL du velger. Du oppretter abonnementet i
Portal → Webhooks.
Slik setter du opp
- Gå til Portal → Webhooks.
- Gi abonnementet et navn og lim inn en offentlig
https-URL.
Private og lokale adresser avvises.
- Huk av hendelsene du vil motta, og trykk «Opprett
abonnement».
- Trykk «Send test» for å sende et testvarsel til
URL-en.
- Kopier signaturhemmeligheten fra abonnementet. Den
bruker du til å verifisere at leveransen virkelig kommer fra oss.
Hemmeligheten vises bare i portalen - lagre den som en hemmelighet i
ditt eget system, ikke i koden.
- «Siste leveranser» viser tid, abonnement, hendelse,
status, HTTP-kode og antall forsøk, slik at du kan feilsøke mottaket
ditt.
Hendelser
| Hendelse |
Når den sendes |
Innhold (utvalg) |
call.completed |
En samtale er fullført. |
call_id, from_number, duration_seconds, status, summary |
booking.created |
En booking er opprettet. |
booking_id, caller_phone, starts_at, title |
booking.cancelled |
En booking er avbestilt. |
booking_id, starts_at, reason |
message.created |
En ny beskjed er tatt imot. |
message_id, caller_name, caller_phone, body, source |
chat.escalated |
Chatten ber om et menneske. |
session_id, visitor_name, visitor_email, visitor_phone |
Payload for call.completed
Hver leveranse er en POST med JSON. Hendelsen ligger øverst i kroppen,
og selve innholdet ligger under data:
POST https://crm.dittfirma.no/webhooks/svaria
Content-Type: application/json
X-Svaria-Event: call.completed
X-Svaria-Delivery: 4711
X-Svaria-Timestamp: 2026-09-23T19:42:11+00:00
X-Svaria-Signature: sha256=8f2c... (HMAC-SHA256 av rå body med hemmeligheten)
{
"event": "call.completed",
"delivery_id": 4711,
"created_at": "2026-09-23T19:42:11+00:00",
"data": {
"call_id": "k3F9xQ2mZp",
"from_number": "+4790000000",
"duration_seconds": 143,
"status": "completed",
"summary": "Kunden spurte om åpningstider og fikk avtalt tilbakeringing."
}
}
Bruk X-Svaria-Delivery til å gjøre mottaket idempotent -
samme leveranse kan komme flere ganger ved et nytt forsøk.
Verifiser signaturen
X-Svaria-Signature er sha256= etterfulgt av en
HMAC-SHA256 av den rå request-kroppen, med
signaturhemmeligheten som nøkkel. Regn ut signaturen selv og sammenlign
før du stoler på innholdet. Bruk den rå bodyen - ikke en re-serialisert
JSON - ellers blir signaturen ulik.
Node/Express
import crypto from 'crypto';
import express from 'express';
const app = express();
const hemmelighet = process.env.SVARIA_WEBHOOK_SECRET;
app.post('/webhooks/svaria', express.raw({ type: 'application/json' }), (req, res) => {
const raaBody = req.body; // Buffer - ikke parse før signaturen er sjekket.
const forventet = 'sha256=' + crypto
.createHmac('sha256', hemmelighet)
.update(raaBody)
.digest('hex');
const mottatt = req.get('X-Svaria-Signature') ?? '';
const gyldig = forventet.length === mottatt.length
&& crypto.timingSafeEqual(Buffer.from(forventet), Buffer.from(mottatt));
if (!gyldig) return res.status(401).json({ error: 'ugyldig signatur' });
const hendelse = JSON.parse(raaBody.toString('utf8'));
console.log(hendelse.event, hendelse.data);
res.json({ ok: true });
});
PHP
<?php
$raaBody = file_get_contents('php://input');
$hemmelighet = getenv('SVARIA_WEBHOOK_SECRET');
$forventet = 'sha256='.hash_hmac('sha256', $raaBody, $hemmelighet);
$mottatt = $_SERVER['HTTP_X_SVARIA_SIGNATURE'] ?? '';
if (! hash_equals($forventet, $mottatt)) {
http_response_code(401);
exit(json_encode(['error' => 'ugyldig signatur']));
}
$hendelse = json_decode($raaBody, true);
// $hendelse['event'] og $hendelse['data']
http_response_code(200);
echo json_encode(['ok' => true]);
Forsøk på nytt
Vi regner HTTP 2xx som levert. Alt annet - nettverksfeil, timeout eller
annen statuskode - prøves på nytt opptil fire forsøk totalt (ett
førsteforsøk og tre nye) med pause på 10, 60 og 300 sekunder mellom
forsøkene. Til slutt merkes leveransen som feilet, og du ser status,
HTTP-kode og antall forsøk under «Siste leveranser» i
portalen. Svar raskt med 2xx, og gjør tungt arbeid etter at du har svart.
Make og n8n
Verken Make eller n8n krever kode. Du kan både motta Svaria-hendelser og
svare på agentens oppslag.
Motta Svaria-hendelser
- Make: lag et nytt scenario og velg triggeren
Webhooks → Custom webhook. n8n: lag
en ny workflow og legg til Webhook-noden. Kopier
URL-en triggeren viser.
- I Svaria: gå til Portal → Webhooks, lim inn URL-en
under «URL for levering (https)», gi abonnementet et
navn og huk av hendelsene du vil motta. Trykk «Opprett
abonnement».
- Trykk «Send test» på abonnementet. Triggeren i
Make/n8n skal nå vise kroppen med
event og
data.
- Koble på flere moduler eller noder for å opprette CRM-oppføringer,
sende e-post, varsle i Slack eller hva du trenger.
Svare på oppslag
For oppslag må webhook-triggeren svare med data med en gang.
Legg til modulen Webhooks: Webhook response (Make) eller
noden Respond to Webhook (n8n) etter oppslaget mot
systemet ditt, og la svaret være JSON på samme form som i
Bygg et endepunkt. Lim inn webhook-URL-en
som base-URL i portalen - da bruker agenten Make/n8n som endepunktet sitt
uten at du skriver kode.
Zapier
Zapier tar imot Svaria-hendelser med triggeren Webhooks → Catch
Hook. Lag en Zap, kopier catch hook-URL-en og lim den inn i
Portal → Webhooks. Deretter kobler du på handlinger som
oppretter rader i regneark eller CRM, sender e-post eller varsler i
Slack.
Til oppslag kan ikke Zapier brukes alene: en Catch Hook-Zap
svarer bare {"status":"success"} og kan ikke returnere
synkrone data fra systemet ditt. Bruk Make eller n8n med webhook-svar,
eller et eget HTTPS-endepunkt - se
Bygg et endepunkt.
MCP-servere
MCP (Model Context Protocol) er en åpen standard der systemleverandører
eksponerer verktøyene sine på én URL. Har systemet du bruker allerede en
MCP-server, kan du koble den til i stedet for å bygge et eget endepunkt.
Slik kobler du til
- Gå til Portal → Integrasjoner → Ny integrasjon og velg MCP-server.
- Lim inn server-URL-en og velg autentisering: bearer, egen header eller
brukernavn/passord. HMAC støttes ikke for MCP.
- Trykk «Test tilkobling» for å bekrefte at vi når serveren.
- Åpne integrasjonen og trykk «Synk verktøy» for å hente verktøylisten.
Protokollversjoner
Vi støtter moderne stateless MCP (2026-07-28) og eldre servere med
initialize-håndtrykk. Versjonen velges automatisk. Legacy-servere
støttes også, uten ekstra oppsett.
Etter synk
Nye verktøy er av til du aktiverer dem i portalen. Beskrivelsen du
skriver er instruksjonen agenten leser for å avgjøre når verktøyet skal brukes -
bruk tid på den. Verktøy som forsvinner fra serveren merkes
«Utilgjengelig hos serveren» og slås av automatisk. MCP-servere kan eksponere
verktøy som endrer data. Slå av verktøy du ikke vil at agenten skal bruke.
Eksempel: minimal MCP-server i Node
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
const server = new McpServer({ name: 'dittfirma', version: '1.0.0' });
server.registerTool(
'ordrestatus',
{
description: 'Bruk denne når kunden spør om status på en ordre og har oppgitt ordrenummer.',
inputSchema: { ordrenummer: z.string() },
},
async ({ ordrenummer }) => ({
content: [{ type: 'text', text: JSON.stringify(await hentOrdre(ordrenummer)) }],
}),
);
// Stateless: ny transport per kall, ingen sesjon å holde rede på.
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(3000);
Serveren bør validere autentiseringsheaderen du satte opp i portalen. Les mer på
modelcontextprotocol.io.
Sikkerhet og personvern
MCP-verktøy følger de samme personvernreglene som HTTP-verktøy: vi lagrer
metadata om hvert kall og kan lagre et kort responsutdrag for feilsøking, utdrag
slettes automatisk etter 30 dager, og alt knyttet til en samtale slettes når
samtalen slettes. Nøkler og andre hemmeligheter havner aldri i logger eller
kallhistorikk.
Autentisering
- Bearer:
Authorization: Bearer <token>
- Egen header: valgfritt navn og verdi, f.eks.
X-Api-Key
- Brukernavn/passord: HTTP Basic
- HMAC: vi signerer
{timestamp}.{method}.{path}.{body} med
SHA-256 og sender X-Svaria-Timestamp og X-Svaria-Signature.
{path} er URL-stien uten query-streng, for eksempel
/svaria/ordrestatus.
Feilsøking
- 401/403: sjekk nøkkelen i portalen.
- Timeout: vi gir opp etter 4 sekunder i tale. Optimaliser oppslaget.
- Blokkert adresse: private og lokale adresser avvises. Bruk offentlig HTTPS.
- Tomt svar: sjekk respons-utdraget og at JSON-feltet finnes.
- Siste kall: åpne integrasjonen i portalen og se listen over kall.
Personvern
Vi lagrer metadata om hvert oppslag (tid, status, varighet) og kan lagre et kort
responsutdrag for feilsøking. Du kan skru av responsutdrag per verktøy, og utdrag
slettes automatisk etter 30 dager. Ved sletting av en samtale slettes også
oppslagene knyttet til den.
Vi lagrer parametrene som ble sendt (f.eks. ordrenummer) sammen med
oppslaget. De slettes når samtalen slettes, og responsutdrag slettes i
tillegg automatisk etter 30 dager. Du kan skru av responsutdrag per verktøy.
Systemfelter sendes bare når du har huket dem av i verktøyet.
Webhook-leveringer slettes automatisk etter 90 dager.