Вебхуки
POST /indexes/{slug}/webhook — приём документов с подписью HMAC для CMS и событийных конвейеров, с окном повтора в пять минут.
POST /api/v1/indexes/{slug}/webhookПодписанный эндпоинт приёма для систем, которые присылают документы по своему расписанию: CMS, конвейер публикации, воркер очереди. Тело несёт документы, подпись доказывает знание общего секрета, а тенант берётся из API-ключа — никогда из полезной нагрузки.
Требуется право documents:write.
Подпись
Два обязательных заголовка:
| Заголовок | Значение |
|---|---|
x-aac-timestamp | Unix-время в секундах |
x-aac-signature | HMAC-SHA256(secret, "{timestamp}.{rawBody}") в hex; префикс sha256= допускается |
Секрет — настройка индекса webhookSecret, либо общий для развёртывания SEARCH_WEBHOOK_SECRET. Отметка времени должна отличаться от серверного времени не более чем на ±300 секунд, что ограничивает повтор перехваченного запроса.
BODY='{"documents":[{"external_id":"release-1.8","title":"Релиз 1.8","content":"Семантический поиск доступен всем.","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"
}Подпись считается по сырому телу запроса, до любой повторной сериализации JSON — порядок ключей и пробелы имеют значение. В 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,
});Тело
| Поле | Обязательно | Комментарий |
|---|---|---|
documents | да | 1–1000 документов той же структуры, что в POST /documents |
action | нет | upsert (по умолчанию), create, update, emplace |
Документы импортируются с source_type webhook, а запуск пишется в журнал аудита как webhook.ingest с числом документов и количеством проиндексированных.
Ошибки
| Статус | Код | Причина |
|---|---|---|
400 | INVALID_JSON | Тело не является валидным JSON — проверьте, что именно вы подписывали |
401 | UNAUTHORIZED | Нет x-aac-timestamp или x-aac-signature |
401 | STALE_SIGNATURE | Отметка времени вне окна 300 секунд |
401 | INVALID_SIGNATURE | HMAC не совпадает с полезной нагрузкой |
422 | INVALID_DOCUMENT | documents[] отсутствует, пуст или содержит больше 1000 элементов |
503 | WEBHOOK_NOT_CONFIGURED | Нет webhookSecret у индекса и нет SEARCH_WEBHOOK_SECRET на сервере |
{
"error": {
"code": "INVALID_SIGNATURE",
"message": "Signature does not match the payload",
"request_id": "req_1d3f5b7a9c1e3d5f7a9b1c3e"
}
}Сравнение подписей выполняется за постоянное время, поэтому почти совпавшая подпись не выдаёт ожидаемое значение.
Повторы и порядок
Эндпоинт не идемпотентен сам по себе: повторный запрос заново импортирует те же документы, что безвредно для upsert (тот же external_id, то же содержимое), но не гарантирует однократность. Если она нужна, добавьте собственный номер последовательности в metadata и сверяйте его, либо используйте POST /documents с Idempotency-Key.
В пределах одного вызова документы индексируются в порядке получения. Гарантий порядка между вызовами нет, поэтому издатель, способный доставить данные не по порядку, должен передавать в документе номер версии и решать на своей стороне, какая копия побеждает.
Статус и обнаружение
GET /status, /health, /capabilities, /openapi.json и /metrics — публичная поверхность для мониторинга и для клиентов, которым нужно знать возможности узла.
Ошибки и лимиты
Конверт ошибок, все коды API со статусами, семантика лимитов запросов, квоты тарифов и поведение кошелька при превышении.