Ga naar hoofdinhoud
Audact
Bekijk het aanbod

Webhookbezorging

Hoe Audact uitgaande webhookbezorgingen ondertekent, opnieuw probeert en in de dead-letter-opslag plaatst. Behandelt het HMAC-SHA256-handtekeningschema, de rotatieovergangsperiode, het herhaalprogramma en de resend-API.

Dit document beschrijft het webhookbezorgingsmechanisme: ondertekening, herhaling en dead-letter-gedrag. Elke uitgaande webhookbezorging bevat de volgende headers en volgt het onderstaande herhaal- en dead-letter-schema.

Handtekeningschema

Elke bezorging wordt ondertekend met HMAC-SHA256 en bevat vier headers:

HeaderBetekenis
X-Audact-Signaturet=<unix_seconds>,v1=<hex_hmac> (zie hieronder voor de dubbele-v1=-rotatievorm)
X-Audact-TimestampDezelfde <unix_seconds>-waarde als afzonderlijke header
X-Audact-EventHet gebeurtenistype (bijv. call_started, call_summary)
X-Audact-Delivery-IdEen per-poging-UUID; uniek bij elke herhalingspoging van dezelfde gebeurtenis

Verwar deze header niet met de tool-dispatch-header. Uitgaande tool-aanroepen (agent → tenant-tool-eindpunt) worden ondertekend met een andere, niet-gerelateerde header, X-Signature — zie tool-dispatch. Zoeken naar X-Signature in deze specificatie zal het webhookschema niet vinden.

Wat wordt ondertekend. De exacte tekenreeks "<unix_timestamp>.<raw_request_body>" — de tijdstempel, een letterlijke ., daarna de ruwe JSON-body-bytes precies zoals verzonden (geen her-serialisatie). De MAC is HMAC-SHA256, gecodeerd als kleine letters hexadecimaal.

Enkelvoudige-sleutel-headerwaarde:

t=1750000000,v1=5f4dcc3b5aa765d61d8327deb882cf99...

Dubbele-handtekening rotatieovergangsperiode (72 uur). POST /v1/webhooks/secrets/rotate behoudt de vorige ondertekeningssleutel gedurende 72 uur na rotatie als geldig. In dat venster bevat elke bezorging twee v1=-waarden in dezelfde header — één HMAC onder de oude sleutel, één onder de nieuwe:

t=1750000000,v1=<hmac_under_old_key>,v1=<hmac_under_new_key>

Een verificateur dient de handtekening te accepteren als een van de v1=-waarden overeenkomt (dit is precies wat de serverzijdige verificateur doet — hij itereert alle v1=-waarden en vergelijkt elke met constante tijd). Dit betekent dat een ontvanger alleen zijn momenteel bekende geheim hoeft op te slaan en dit tegen elke v1=-waarde te controleren; het hoeft niet te weten welke "huidig" en welke "oud" is. GET /v1/webhooks/secrets/status rapporteert of een rotatie loopt en de grace_period_ends-tijdstempel.

Replay-bescherming. De handtekeningheader verloopt niet zelf — de verificateur is verantwoordelijk voor het afwijzen van verouderde tijdstempels. Gebruik een tolerantiecontrole met een aanbevolen maximale leeftijd van 300 seconden (5 minuten) tussen de t=-waarde en "nu". De vergelijking van de HMAC-samenvatting moet in constante tijd plaatsvinden; doe hetzelfde aan de ontvangende kant om timing-zijkanaalaanvallen te voorkomen.

Het ondertekeningsgeheim verkrijgen (eenmalige onthulling)

Het per-tenant ondertekeningsgeheim wordt serverzijdig gegenereerd en uitsluitend in een secrets manager opgeslagen — het is achteraf nooit opvraagbaar, overeenkomstig het Stripe/Twilio-patroon. Het wordt in de JSON-responsebody, in platte tekst, precies eenmaal geretourneerd door twee aanroepen:

  • POST /v1/webhooks — wanneer deze aanroep het ondertekeningsgeheim van de tenant voor de eerste keer inricht (d.w.z. de tenant had eerder geen webhookeindpunt en dus geen geheim), bevat de respons signing_secret naast het aangemaakte eindpunt. Elke volgende POST /v1/webhooks-aanroep voor dezelfde tenant hergebruikt het reeds ingerichte geheim en laat signing_secret weg uit de respons.
  • POST /v1/webhooks/secrets/rotate — elke succesvolle rotatie retourneert de nieuw gegenereerde sleutel als signing_secret in de responsebody. Dit is de enige manier om een ondertekeningsgeheim na de eerste onthulling opnieuw te zien — roteer en noteer de nieuwe waarde onmiddellijk.

Geen enkel ander eindpunt retourneert het geheim. In het bijzonder rapporteert GET /v1/webhooks/secrets/status uitsluitend rotatiemetadata (in_rotation, grace_period_ends, created_at) — nooit het sleutelmateriaal zelf. Sla de onthuld waarde veilig op (bijv. in uw eigen secrets manager); als het verloren gaat, is het enige herstelpad POST /v1/webhooks/secrets/rotate, wat de oude sleutel na de hierboven beschreven 72-uur dubbele-handtekening overgangsperiode ongeldig maakt.

Kopieer-plak-verificatiefunctie (Go)

go

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"
)

func verifyAudactWebhook(secret []byte, sigHeader string, rawBody []byte) bool {
	var ts int64
	var sigs []string
	for _, part := range strings.Split(sigHeader, ",") {
		if strings.HasPrefix(part, "t=") {
			ts, _ = strconv.ParseInt(strings.TrimPrefix(part, "t="), 10, 64)
		} else if strings.HasPrefix(part, "v1=") {
			sigs = append(sigs, strings.TrimPrefix(part, "v1="))
		}
	}
	age := time.Now().Unix() - ts
	if age < 0 {
		age = -age
	}
	if age > 300 {
		return false
	}
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(fmt.Sprintf("%d.%s", ts, rawBody)))
	expected := hex.EncodeToString(mac.Sum(nil))
	for _, sig := range sigs {
		if hmac.Equal([]byte(sig), []byte(expected)) {
			return true
		}
	}
	return false
}

Opnieuw implementeren zonder de Python-SDK-helper: parseer t= en elke v1=-waarde uit de header, herbereken hex(hmac_sha256(secret, fmt.Sprintf("%d.%s", ts, rawBody))) en accepteer als het in constante tijd overeenkomt met een van de v1=-waarden en abs(now - ts) <= 300s.

Doelregels (SSRF-beveiliging)

Webhookontvangers moeten openbare HTTPS-eindpunten zijn.

  • Afgewezen bij aanmaken/bijwerken (400, details.reason = "blocked_destination"): IP-literals in loopback-, privé- (RFC 1918), link-local- (inclusief het cloudmetadata-adres 169.254.169.254), CGNAT-, documentatie-, benchmarking-, gereserveerde, multicast- en NAT64-bereiken en hun IPv6-equivalenten; de loopback-hostnaam; enkellabelige hostnamen (container-servicenamen zoals redis); namen die eindigen op .internal, .local, .localdomain, .home.arpa, .lan of .corp; en namen die worden omgezet naar een niet-openbaar adres.
  • Afgedwongen bij elke bezorging, resend en testping: het adres wordt opnieuw gecontroleerd op verbindingstijd, na hostnaamresolutie. Dit geldt ook voor webhooks die zijn aangemaakt vóór deze beveiliging bestond. Dergelijke bezorgingen mislukken met blocked_destination.
  • Omleidingen worden niet gevolgd. Een 3xx-respons geldt als een niet-2xx-fout. Wijs de webhook op de uiteindelijke URL.
  • Foutrapportage: een fout op netwerkniveau wordt (in test_webhook en in last_error van het bezorgingslog) gerapporteerd als een van blocked_destination, timeout, tls_error, dns_error of connection_failed. De onderliggende netwerkfout wordt nooit geretourneerd.

Herhaalprogramma

  • Maximaal 4 pogingen totaal (1 initiële bezorging + 3 herhalingen). Dit maximum is momenteel vast, niet per tenant configureerbaar.
  • Basisvertragingen tussen pogingen: 1s, 10s, 60s — toegepast vóór pogingen 2, 3 en 4.
  • Jitter: elke basisvertraging wordt vermenigvuldigd met een willekeurige factor in [0.7, 1.3] (±30%), zodat de werkelijke vertragingen ongeveer 0,7–1,3 s, 7–13 s, 42–78 s bedragen.
  • HTTP-time-out per poging: 10 seconden.
  • Elke niet-2xx-respons of netwerk-/time-outfout activeert een herhaling (onderhevig aan het bovengenoemde maximum).

Dead-letter en resend

  • Na alle 4 pogingen is uitgeput, wordt de bezorging geschreven naar een dead-letter-opslag met de originele payload ongewijzigd bewaard.
  • Bewaartermijn: 90 dagen vanaf het moment dat de vermelding in de dead-letter-opslag is geplaatst.
  • Herstel een dead-letter-bezorging met POST /v1/webhooks/deliveries/{id}/resend — dit plaatst de oorspronkelijke gebeurtenis opnieuw in het normale verzendpad (nieuwe bezorgingspogingen, nieuwe X-Audact-Delivery-Id-waarden).
  • Geef dead-letter-bezorgingen voor een tenant op via het dead-letter-listingseindpunt om monitoring/alerting op te bouwen.

Praktische gevolgen

  • Behandel bezorging als minstens eenmaal: een ontvanger kan dezelfde X-Audact-Delivery-Id meer dan eenmaal zien als het zijn eigen verwerking herhaalt nadat al een 2xx was geretourneerd aan Audact en Audacts eigen herhalingslogica opnieuw wordt geactiveerd (bijv. een netwerkstoring nadat uw 2xx was verzonden maar voordat Audact het ontving) — ontwerp handlers idempotent op X-Audact-Delivery-Id ongeacht.
  • Als uw eindpunt langer dan het volledige herhalingvenster (~90 seconden) plus de tijd die u nodig heeft om het te merken is uitgevallen, belandt de bezorging in de dead-letter-opslag; bewaak deze en roep het resend-eindpunt aan zodra uw eindpunt weer gezond is in plaats van te vertrouwen op onbeperkte herhalingen door Audact.

Bron bijgewerkt: 19 oktober 2026