AACSearch
Справочник API

Вебхуки

POST /indexes/{slug}/webhook — приём документов с подписью HMAC для CMS и событийных конвейеров, с окном повтора в пять минут.

POST /api/v1/indexes/{slug}/webhook

Подписанный эндпоинт приёма для систем, которые присылают документы по своему расписанию: CMS, конвейер публикации, воркер очереди. Тело несёт документы, подпись доказывает знание общего секрета, а тенант берётся из API-ключа — никогда из полезной нагрузки.

Требуется право documents:write.

Подпись

Два обязательных заголовка:

ЗаголовокЗначение
x-aac-timestampUnix-время в секундах
x-aac-signatureHMAC-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 с числом документов и количеством проиндексированных.

Ошибки

СтатусКодПричина
400INVALID_JSONТело не является валидным JSON — проверьте, что именно вы подписывали
401UNAUTHORIZEDНет x-aac-timestamp или x-aac-signature
401STALE_SIGNATUREОтметка времени вне окна 300 секунд
401INVALID_SIGNATUREHMAC не совпадает с полезной нагрузкой
422INVALID_DOCUMENTdocuments[] отсутствует, пуст или содержит больше 1000 элементов
503WEBHOOK_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.

В пределах одного вызова документы индексируются в порядке получения. Гарантий порядка между вызовами нет, поэтому издатель, способный доставить данные не по порядку, должен передавать в документе номер версии и решать на своей стороне, какая копия побеждает.

On this page