> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Oversigt over webhooks

> 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 vurderingskørsel fuldføres, en advarsel 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="Forældet webhook med én URL" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Én URL pr. organisation. Indeholder opkaldslivscyklus-hændelserne, herunder de
    **blokerende** konfigurationsudvekslinger. Administreres via `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Alle ti hændelsestyper i [hændelseskataloget](/da/webhooks/events) leveres
via webhook-slutpunkter. De seks opkaldslivscyklus-hændelser
(`telephony.incoming`, `telephony.complete`, `telephony.tool`,
`web.incoming`, `web.complete`, `web.tool`) sendes **også** til det
forældede webhook med én URL — hvis du både har en forældet 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 forældede sti; hver levering til et slutpunkt er en
send-og-glem-notifikation.

## Payloadformat

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

```json theme={null}
{
  "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 ud fra den.

Det forældede webhook med én URL sender samme `type` og `data`, men
**uden** `event_id`:

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

Under overførsel serialiseres hver body kanonisk — nøgler sorteres
alfabetisk, ingen mellemrum, UTF-8. De pænt formaterede eksempler i
denne dokumentation er kun for læsbarhed.

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

## Signaturverificering

Hver anmodning indeholder en HMAC-SHA256-signatur over den **rå anmodnings-
body** i headeren `X-ThunderPhone-Signature`. Signeringsnøglen er
endpointets `secret` (eller din organisations webhook-`secret` for
ældre leveringer).

### Trin

1. Læs den rå anmodnings-body **før** enhver parsing.
2. Beregn `hmac_sha256(secret, body).hexdigest()`.
3. Sammenlign i konstant tid med headeren `X-ThunderPhone-Signature`.

Vi signerer præcis de bytes, vi sender, og disse bytes er den
kanoniske JSON-serialisering (sorterede nøgler, kompakte separatorer).
Derfor virker verificering mod den rå body altid — og hvis dit framework
kun giver dig parset JSON, producerer genserialisering med sorterede
nøgler og kompakte separatorer identiske bytes. Begge fremgangsmåder er
beskrevet i [verificeringsvejledningen](/da/guides/verify-webhook-signatures).

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_signature(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(
          secret.encode("utf-8"),
          body,
          hashlib.sha256,
      ).hexdigest()
      return hmac.compare_digest(expected, signature or "")

  # Example Flask handler
  from flask import Flask, request, abort
  app = Flask(__name__)

  @app.post("/thunderphone-webhook")
  def handle():
      body = request.get_data()
      sig = request.headers.get("X-ThunderPhone-Signature", "")
      if not verify_signature(body, sig, WEBHOOK_SECRET):
          abort(401)
      event = request.get_json()
      # dispatch on event["type"] …
      return "", 204
  ```

  ```javascript Node.js (Express) theme={null}
  import crypto from "node:crypto";
  import express from "express";

  function verifySignature(body, signature, secret) {
    const expected = crypto
      .createHmac("sha256", secret)
      .update(body)
      .digest("hex");
    if (!signature || expected.length !== signature.length) return false;
    return crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature),
    );
  }

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

## Leveringssemantik

Denne semantik gælder for leveringer til **endepunkter**. Den ældre webhook med én URL
er ét enkelt synkront forsøg uden genforsøg.

<AccordionGroup>
  <Accordion title="Genforsøg">
    Hver hændelse forsøges leveret én gang med det samme. Ethvert `2xx`-svar
    bekræfter leveringen. Ved ethvert andet resultat (ikke-2xx,
    forbindelsesfejl, timeout) forsøger vi igen **1 min., 5 min., 30 min., 2 t., 6 t.,
    12 t. og 24 t. efter det første forsøg** — 8 forsøg fordelt over
    24 timer. Hvis alle forsøg mislykkes, stopper leveringen, og endepunktet
    markeres med `status="failing"` i
    [webhook-endepunkter](/da/webhooks/endpoints). Returner `2xx`, så snart
    payloadet er varigt accepteret; behandl det asynkront.
  </Accordion>

  <Accordion title="Rækkefølge">
    Leveringsrækkefølgen er best effort. 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 indeholder 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="Tidsgrænser">
    Leveringer til endepunkter har en tidsgrænse på **30 sek.** pr. forsøg. På den
    ældre sti har blokerende anmodninger, der styrer aktive opkald — den
    [`telephony.incoming` / `web.incoming`](/da/webhooks/call-incoming)
    konfigurationsudveksling — en tidsgrænse på **10 sek.**, men 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 sek.
  </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                                                                    | 8 forsøg over 24 t.                             |
| Konvolut                            | `type` + `data`                                                          | `type` + `data` + `event_id`                    |
| Hemmelighedsrotation                | Erstatter enkelt hemmelighed                                             | Hemmelighed pr. endepunkt                       |
| Deaktiver uden at slette            | —                                                                        | `status=disabled`                               |
| Statussynlighed                     | —                                                                        | `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 modtage hændelser via endepunktbaserede
webhooks. Behold (eller tilføj) kun en ældre URL, hvis du konfigurerer opkald
dynamisk på tidspunktet for besvarelse eller bruger værktøjsafsendelse 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 payloads.
  </Card>

  <Card title="Webhook-endepunkter" icon="bolt" href="/da/webhooks/endpoints">
    Administrer flere endepunkter, hændelsesfiltre og hemmeligheder.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/da/webhooks/call-incoming">
    Den blokerende anmodning, som din server skal besvare for at konfigurere opkald.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/da/webhooks/call-complete">
    Payload efter opkaldet med transskription, optagelse og målinger.
  </Card>
</CardGroup>
