---
title: "Oversigt over webhooks"
description: "Sådan leverer ThunderPhone hændelser i realtid, hvordan du verificerer signaturer, og hvordan de ældre og endpoint-baserede leveringsmodeller sammenlignes."
---

ThunderPhone sender HTTP-`POST`-anmodninger til din server, når der sker
noget under et opkald — et indgående opkald starter, et opkald slutter, en
evalueringskørsel fuldføres, en alarm udløses osv. Der er **to
leveringsmodeller**:

<CardGroup cols={2}>
  <Card title="Webhook-slutpunkter (anbefalet)" icon="bolt" href="/da/webhooks/endpoints">
    Flere URL'er, hemmeligheder pr. slutpunkt, hændelsesfiltre pr. slutpunkt
    og automatiske genforsøg.
    Administrer via `GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints`.
  </Card>
  <Card title="Ældre webhook med enkelt URL" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Én URL pr. organisation. Indeholder opkaldslivscyklushændelserne, herunder
    de **blokerende** konfigurationsudvekslinger. Administreres via `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Alle angivne hændelsestyper i [hændelseskataloget](/da/webhooks/events) leveres
via webhook-slutpunkter. De seks opkaldslivscyklushændelser
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) sendes **også** til den
ældre webhook med enkelt URL — hvis du både har en ældre URL og et
matchende slutpunkt, modtager du hændelsen på **begge** stier. Blokerende
adfærd ([`telephony.incoming` / `web.incoming`-konfigurationsudvekslingen](/da/webhooks/call-incoming)
og [værktøjsafsendelse](/da/tools/overview) i webhooktilstand)
findes udelukkende på den ældre sti; hver levering til et slutpunkt er en
fire-and-forget-notifikation.

## Payloadformat

Leveringer til slutpunkter er et JSON-objekt med `data`, `event_id` og
`type`:

```json
{
  "data": {
    "call_id": 987654321,
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  },
  "event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
  "type": "telephony.incoming"
}
```

`event_id` er unik for hver udsendt hændelse. Den er identisk på tværs af genforsøg
**og** på tværs af hvert slutpunkt, der modtager hændelsen — dedupliker efter den.

Den ældre webhook med enkelt URL sender samme `type` og `data`, men
**uden** `event_id`:

```json
{
  "type": "telephony.incoming",
  "data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}
```

På netværket serialiseres hver body kanonisk — nøgler sorteres
alfabetisk, uden mellemrum, UTF-8. De formaterede eksempler i denne
dokumentation er kun for læsbarhed.

Se [hændelseskataloget](/da/webhooks/events) for den fulde liste over
hændelsestyper og payloadfelter.

## Signaturverificering

Hver anmodning indeholder to signaturer, begge HMAC-SHA256 med endpointets
`secret` som nøgle (eller din organisations webhook-`secret` for ældre
leveringer):

- `X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>` signerer
  tidsstemplet, metoden, stien og forespørgslen samt den rå body. Verificer
  denne, og afvis anmodninger, hvis `t` er mere end **5 minutter** fra
  dit ur, så en opsnappet anmodning ikke kan afspilles igen senere. Gentagelser
  signeres igen, når de sendes.
- `X-ThunderPhone-Signature` signerer kun den rå body. Den sendes stadig
  uændret, så eksisterende verifikatorer fortsat fungerer.

### Trin

1. Læs headeren, metoden, den rå sti og forespørgsel samt den rå
   body **før** enhver parsing.
2. Beregn den hexadecimale HMAC-SHA256 for
   `<t>\n<METHOD>\n<path and query>\n<body>` med din hemmelighed.
3. Afvis anmodningen, hvis `t` afviger med mere end 300 sekunder, eller hvis
   resultatet ikke matcher `v1` i en sammenligning med konstant tid.

Bodies er den kanoniske JSON-serialisering (sorterede nøgler, kompakte
separatorer), og vi signerer nøjagtigt de bytes, vi sender. [Guiden til
verificering](/da/guides/verify-webhook-signatures) indeholder den nøjagtige
signerede streng, et gennemarbejdet eksempel, kode uden SDK'et og den ældre
header.

<CodeGroup>
```python Python (Flask)
from flask import Flask, abort, request
from thunderphone import verify_signature

app = Flask(__name__)

@app.post("/thunderphone-webhook")
def handle():
    query = request.query_string.decode()
    if not verify_signature(
        request.headers.get("X-ThunderPhone-Signature-V2"),
        secret=WEBHOOK_SECRET,
        method=request.method,
        path=request.path + (f"?{query}" if query else ""),
        body=request.get_data(),  # raw bytes
    ):
        abort(401)
    event = request.get_json()
    # dispatch on event["type"] …
    return "", 204
```

```javascript Node.js (Express)
import express from "express";
import { verifySignature } from "@thunderphone/sdk";

const app = express();
app.post(
  "/thunderphone-webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ok = verifySignature({
      header: req.header("X-ThunderPhone-Signature-V2"),
      secret: process.env.WEBHOOK_SECRET,
      method: req.method,
      path: req.originalUrl,
      body: req.body,
    });
    if (!ok) return res.sendStatus(401);
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type …
    res.sendStatus(204);
  },
);
```
</CodeGroup>

## Leveringssemantik

Disse semantikker gælder for leverancer til **endpoint**. Den ældre webhook med én URL
er ét synkront forsøg uden genforsøg, bortset fra
konfigurationsudvekslingen for telefonopkaldet
[`telephony.incoming`](/da/webhooks/call-incoming), som genforsøges én gang.

<AccordionGroup>
  <Accordion title="Genforsøg">
    Hver hændelse forsøges leveret én gang med det samme. Ethvert `2xx`-svar
    bekræfter leverancen. Ved ethvert andet udfald (ikke-2xx,
    forbindelsesfejl, timeout) genforsøger vi efter **1 m, 5 m, 30 m, 2 h, 6 h,
    12 h og 24 h efter det første forsøg** — 8 forsøg fordelt over
    24 timer. Hvis alle forsøg fejler, stopper leverancen, og endpointet
    markeres med `status="failing"` i
    [webhook-endpoints](/da/webhooks/endpoints); vi sender en e-mail til din organisations
    ejere og administratorer og viser det i dashboardet. Returner `2xx`, så snart
    payloaden er accepteret varigt; behandl den asynkront.
    Testafsendelser fra endpoint-test-API'et får ét genforsøg cirka et minut efter
    det første forsøg og ændrer aldrig endpointets status.
  </Accordion>

  <Accordion title="Rækkefølge">
    Leveringsrækkefølgen er efter bedste evne. I praksis leverer vi i den
    rækkefølge, hændelser udsendes, men genforsøg kan ændre rækkefølgen ved fejl.
    Dedupliker og afstem altid efter `call_id` / objekt-id.
  </Accordion>

  <Accordion title="Dubletter">
    Levering er **mindst én gang**: et genforsøg efter et svar, vi aldrig
    modtog, kan duplikere en hændelse. Hvert genforsøg har samme
    `event_id`, så gem behandlede id'er, og spring gentagelser over. `event_id` deles
    også på tværs af endepunkter — to endepunkter, der abonnerer på den
    samme hændelse, modtager den samme `event_id`.
  </Accordion>

  <Accordion title="Timeouts">
    Leverancer til endpoints har en timeout på **30 s** pr. forsøg. På den
    ældre sti er blokerende anmodninger, der styrer aktiv opkaldsadfærd —
    konfigurationsudvekslingen
    [`telephony.incoming` / `web.incoming`](/da/webhooks/call-incoming) —
    begrænset til **10 s** for et telefonopkalds første forsøg og **13 s**
    for hele udvekslingen inklusive det ene genforsøg, og til **10 s** for
    en widgetsession. Et langsomt svar forsinker besvarelsen af opkaldet,
    så sigt efter at svare inden for et par sekunder. [Værktøjsafsendelse](/da/tools/overview) i webhooktilstand tillader 20 s
    som standard, og værktøjsdeklarationer kan angive en `timeout` på øverste niveau.
  </Accordion>

  <Accordion title="Kilde-IP'er">
    Udgående webhooks kommer fra ThunderPhones cloud-IP-område.
    Hvis din firewall kræver en tilladelsesliste, skal du kontakte support, så
    deler vi de aktuelle områder.
  </Accordion>
</AccordionGroup>

## Valg mellem ældre og endepunktbaserede webhooks

| Funktion | Ældre (`/v1/webhook`) | Endepunkter (`/v1/developer/webhook-endpoints`) |
|---------|------------------------|----------------------------------------------|
| Antal URL'er | 1 pr. organisation | Mange pr. organisation |
| Hændelsesdækning | Kun `telephony.*` / `web.*` | Alle 10 hændelsestyper |
| Hændelsesfilter | — | Pr. endepunkt |
| Genforsøg | Ingen, undtagen ét for konfigurationsudvekslingen for telefonopkald | 8 forsøg over 24 timer |
| Konvolut | `type` + `data` | `type` + `data` + `event_id` |
| Rotation af hemmelighed | Erstatter én hemmelighed | Hemmelighed pr. endepunkt |
| Deaktiver uden at slette | `PUT /v1/webhook` med `{"url": ""}` | `status=disabled` |
| Synlighed af status | — | `active` / `disabled` / `failing` |
| Blokerende konfigurationsudveksling | Ja ([`telephony.incoming` / `web.incoming`](/da/webhooks/call-incoming)) | Aldrig — kun notifikationer |
| Bedst til | Dynamisk opkaldskonfiguration | Hændelsesforbrug i produktion |

Nye integrationer bør forbruge hændelser via endepunktbaserede
webhooks. Behold (eller tilføj) kun en ældre URL, hvis du konfigurerer opkald
dynamisk ved besvarelse eller bruger værktøjsafvikling i webhooktilstand — disse
anmodnings-/svarudvekslinger kører kun på den ældre sti.

---

## Relateret

<CardGroup cols={2}>
  <Card title="Hændelseskatalog" icon="list" href="/da/webhooks/events">
    Alle hændelsestyper og deres nyttelaster.
  </Card>
  <Card title="Webhook-slutpunkter" icon="bolt" href="/da/webhooks/endpoints">
    Administrer flere slutpunkter, hændelsesfiltre og hemmeligheder.
  </Card>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/da/webhooks/call-incoming">
    Den blokerende anmodning, din server skal besvare for at konfigurere opkald.
  </Card>
  <Card title="telephony.complete / web.complete" icon="phone" href="/da/webhooks/call-complete">
    Nyttelast efter opkald med transskription, optagelse og målinger.
  </Card>
</CardGroup>
