Webhooks
POST /indexes/{slug}/webhook — HMAC-signierter Dokument-Ingest für CMS und ereignisgetriebene Pipelines, mit Fünf-Minuten-Replay-Fenster.
POST /api/v1/indexes/{slug}/webhookEin signierter Ingest-Endpunkt für Systeme, die Dokumente nach eigenem Zeitplan pushen: CMS, Publish-Pipeline, Queue-Worker. Der Body trägt die Dokumente, die Signatur beweist die Kenntnis eines gemeinsamen Geheimnisses, und der Mandant kommt aus dem API-Schlüssel — nie aus der Payload.
Erfordert den Scope documents:write.
Signatur
Zwei Pflicht-Header:
| Header | Wert |
|---|---|
x-aac-timestamp | Unix-Zeit in Sekunden |
x-aac-signature | HMAC-SHA256(secret, "{timestamp}.{rawBody}") als Hex; ein optionales Präfix sha256= wird akzeptiert |
Das Geheimnis ist die Index-Einstellung webhookSecret, ersatzweise das installationsweite SEARCH_WEBHOOK_SECRET. Der Zeitstempel darf maximal ±300 Sekunden von der Serverzeit abweichen, was das Replay eines abgefangenen Requests begrenzt.
BODY='{"documents":[{"external_id":"release-1.8","title":"Release 1.8","content":"Semantische Suche ist allgemein verfügbar.","url":"https://example.com/changelog/1-8"}],"action":"upsert"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
curl -sS -X POST "$AAC_API/indexes/documentation/webhook" \
-H "Authorization: Bearer $AAC_KEY" \
-H "Content-Type: application/json" \
-H "x-aac-timestamp: $TS" \
-H "x-aac-signature: sha256=$SIG" \
--data "$BODY"{
"index": "documentation",
"indexed": 1,
"failed": [],
"usage": {
"plan": "pro",
"period": "2026-10",
"resetsAt": "2026-11-01T00:00:00.000Z",
"documents": { "used": 140, "limit": 50000 },
"searches": { "used": 18420, "limit": 500000 }
},
"request_id": "req_8b0d2f4a6c8e0b2d4f6a8c0e"
}Die Signatur wird über den rohen Request-Body berechnet, vor jeder erneuten JSON-Serialisierung — Schlüsselreihenfolge und Leerzeichen zählen. In Node.js:
import { createHmac } from "node:crypto";
const rawBody = JSON.stringify({ documents, action: "upsert" });
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
await fetch(`${api}/indexes/documentation/webhook`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AAC_KEY}`,
"Content-Type": "application/json",
"x-aac-timestamp": timestamp,
"x-aac-signature": `sha256=${signature}`,
},
body: rawBody,
});Body
| Feld | Pflicht | Hinweis |
|---|---|---|
documents | ja | 1–1000 Dokumente, gleiche Struktur wie bei POST /documents |
action | nein | upsert (Standard), create, update, emplace |
Dokumente werden mit source_type webhook importiert, der Lauf landet als webhook.ingest im Audit-Log, inklusive Dokumentzahl und Anzahl der indizierten Dokumente.
Fehlerfälle
| Status | Code | Ursache |
|---|---|---|
400 | INVALID_JSON | Body ist kein gültiges JSON — prüfen Sie, was Sie signiert haben |
401 | UNAUTHORIZED | x-aac-timestamp oder x-aac-signature fehlt |
401 | STALE_SIGNATURE | Zeitstempel außerhalb des 300-Sekunden-Fensters |
401 | INVALID_SIGNATURE | HMAC passt nicht zur Payload |
422 | INVALID_DOCUMENT | documents[] fehlt, ist leer oder enthält mehr als 1000 Einträge |
503 | WEBHOOK_NOT_CONFIGURED | Kein webhookSecret am Index und kein SEARCH_WEBHOOK_SECRET am Server |
{
"error": {
"code": "INVALID_SIGNATURE",
"message": "Signature does not match the payload",
"request_id": "req_1d3f5b7a9c1e3d5f7a9b1c3e"
}
}Der Signaturvergleich läuft in konstanter Zeit, ein Fast-Treffer verrät also nichts über den erwarteten Wert.
Wiederholungen und Reihenfolge
Der Endpunkt ist nicht von sich aus idempotent — ein wiederholter Request importiert dieselben Dokumente erneut, was bei upsert harmlos ist (gleiche external_id, gleicher Inhalt), aber keine Exactly-once-Semantik liefert. Wenn Sie die brauchen, hängen Sie eine eigene Sequenznummer an metadata und gleichen Sie ab, oder nutzen Sie POST /documents mit Idempotency-Key.
Innerhalb eines Aufrufs werden Dokumente in der empfangenen Reihenfolge indiziert. Über Aufrufe hinweg gibt es keine Reihenfolgegarantie; ein Publisher, der außer der Reihe liefern kann, sollte ein Versionsfeld im Dokument mitführen und serverseitig entscheiden, welche Kopie gewinnt.
Status und Discovery
GET /status, /health, /capabilities, /openapi.json und /metrics — die nicht authentifizierte Oberfläche für Monitore, Statusseiten und Clients, die die Fähigkeiten des Knotens kennen müssen.
Fehler und Limits
Der Fehler-Umschlag, jeder Code mit Status, Rate-Limit-Semantik, Tarifkontingente und das Verhalten des Wallets bei Überschreitung.