Разработка и публикация интеграций
Соберите свою интеграцию: определение и бандл, публикация с проверкой sha256, каталог определений в воркспейсе, установка на бота и регистрация вебхука.
Интеграция подключает бота к внешнему сервису. Она даёт канал (входящие и исходящие сообщения), действия и события. Это отдельный TypeScript-проект с тем же контрактом исполнения, что и у бота: декларация плюс бандл. Платформа загружает бандл и запускает его в облаке.
На этой странице — как написать свою интеграцию, а не только поставить готовую из каталога.
Проект состоит из двух частей:
- Определение — файл
integration.definition.ts. Имя, версия, схема конфигурации, каналы, действия, события. Публичный «паспорт» интеграции. - Реализация — код в
src/. Обработчикиregister/unregister, исходящие сообщения каналов и входящийhandlerвебхука.
brt build собирает проект в один самодостаточный бандл .botpress/dist/index.cjs.
Публикация регистрирует определение в каталоге вашего воркспейса и загружает
бандл. После этого интеграцию ставят на ботов — как первопартийную.
Создайте проект
Заведите проект из стартового шаблона:
brt init --type integration --template webhook-message --name my-channel
cd my-channelШаблоны: empty (пустой каркас), hello-world (одно действие),
webhook-message (канал с входящим вебхуком). Минимальный набор файлов:
package.json объявляет имя проекта, скрипты brt и зависимость от SDK. Имя и
версию самой интеграции задаёт определение (name/version в
integration.definition.ts), а не package.json:
{
"name": "my-channel",
"type": "module",
"scripts": {
"build": "brt build",
"publish": "brt integrations publish"
},
"dependencies": {
"@holocronlab/botruntime-sdk": "^6.11.2"
}
}Определение
Опишите интерфейс интеграции в integration.definition.ts. Схему конфигурации
задаёт zod. При публикации она превращается в JSON-схему каталога — по ней
валидируется конфиг на установке.
import { z, IntegrationDefinition } from '@holocronlab/botruntime-sdk'
export default new IntegrationDefinition({
name: 'my-channel',
version: '0.1.0',
configuration: {
schema: z.object({
webhookUrl: z.string().describe('URL, куда постить ответы бота.'),
}),
},
channels: {
webhook: {
messages: {
text: { schema: z.object({ text: z.string() }) },
},
conversation: {
tags: {
id: { title: 'Conversation ID', description: 'ID диалога во внешнем сервисе' },
},
},
},
},
user: {
tags: {
id: { title: 'User ID', description: 'ID пользователя во внешнем сервисе' },
},
},
})Ключевые блоки:
| Блок | Назначение |
|---|---|
configuration.schema | поля установки (URL, идентификаторы); секретные значения запечатываются при установке |
channels | каналы диалога: какие типы сообщений интеграция принимает и отправляет |
actions | вызываемые операции (input/output через zod) — доступны боту как инструменты |
events | именованные события, которые интеграция эмитит внутрь платформы |
user.tags / conversation.tags | внешние идентификаторы, по которым платформа склеивает диалоги и пользователей |
Реализация
Реализация — это new Integration({...}) в src/index.ts. register и
unregister вызываются при сохранении и снятии конфигурации: в них проверяют
доступы и заводят или убирают ресурсы во внешнем сервисе. Исходящие сообщения
канала уходят наружу. Входящий handler принимает вебхук и заводит сообщение в
диалог.
import * as sdk from '@holocronlab/botruntime-sdk'
import axios from 'axios'
export default new sdk.Integration({
register: async ({ ctx }) => {
// Проверьте конфигурацию и доступ к внешнему сервису.
if (!ctx.configuration.webhookUrl) {
throw new sdk.RuntimeError('webhookUrl обязателен')
}
},
unregister: async () => {},
actions: {},
channels: {
webhook: {
messages: {
text: async ({ ctx, conversation, payload }) => {
await axios.post(ctx.configuration.webhookUrl, {
conversationId: conversation.tags.id,
text: payload.text,
})
},
},
},
},
handler: async ({ client, req }) => {
if (!req.body) {
return { status: 400, body: JSON.stringify({ error: 'No body' }) }
}
const parsed = sdk.z
.object({ userId: sdk.z.string(), conversationId: sdk.z.string(), text: sdk.z.string() })
.safeParse(JSON.parse(req.body))
if (!parsed.success) {
return { status: 400, body: JSON.stringify({ error: 'Invalid body' }) }
}
const { userId, conversationId, text } = parsed.data
const { conversation } = await client.getOrCreateConversation({
channel: 'webhook',
tags: { id: conversationId },
})
const { user } = await client.getOrCreateUser({ tags: { id: userId } })
await client.createMessage({
type: 'text',
conversationId: conversation.id,
userId: user.id,
payload: { text },
tags: {},
})
return { status: 200, body: JSON.stringify({ ok: true }) }
},
})Соберите бандл
brt build упаковывает проект в один самодостаточный файл. SDK, клиент API и
раннер лежат внутри — платформе не нужны зависимости проекта:
brt build
# → .botpress/dist/index.cjsБандл экспортирует default (реализацию) и handler (входящий вебхук). Именно
этот файл читает публикация. Платформа исполняет его в отдельном процессе.
Опубликуйте
brt integrations publish публикует integration-проект через тот же
Botpress-shaped deploy-контракт, что и brt deploy. Метаданные Hub, actions,
channels, events, attributes, network и bundle принадлежат одной integration-
сущности и не могут попасть в разные каталожные записи. Бот для публикации не
нужен: команда работает под profile PAT в скоупе воркспейса.
brt integrations publish --dryRun
brt integrations publishПо шагам команда:
- читает полный
integration.definition.tsи собирает bundle; - добавляет handle воркспейса к имени:
yookassaстановитсяbotruntime/yookassa; - ищет свою версию в owned-реестре, а публичные зависимости — в Hub;
- создаёт или обновляет одну integration-сущность с
visibility=public.
--dryRun вызывает те же серверные проверки через validate endpoint, но не
пишет определение и bundle. Для приватной версии используйте обычный
brt deploy: его visibility по умолчанию — private. Публичная публикация
доступна платформенному воркспейсу; другой workspace получает явную ошибку, а
не скрытый private deploy.
Флаги:
| Флаг | Назначение |
|---|---|
--noBuild | не пересобирать — использовать уже собранный .botpress/dist/index.cjs |
--dryRun | проверить create/update без записи |
--api-url <url> | выбрать прокси или стенд для разработки платформы |
Команда работает только из integration-проекта. Флаги --name,
--version-number, --config-schema-file и --noBundle удалены: они обходили
полное определение и позволяли опубликовать схему без метаданных или кода.
icon должен ссылаться на SVG — это тот же контракт, который проверяет
Botpress CLI. PNG/ICO сначала преобразуйте или заверните в SVG-контейнер.
Owned-реестр и публичный Hub
Owned-реестр содержит только интеграции текущего воркспейса. Публичные версии других владельцев читаются через Hub. Такое разделение не даёт CLI принять чужую публичную карточку за свою и обновить неверный ID.
| Метод и путь | Действие |
|---|---|
GET /v1/admin/integrations | список интеграций текущего воркспейса |
POST /v1/admin/integrations | создать полное определение и bundle |
PUT /v1/admin/integrations/{id} | обновить свою версию |
POST /v1/admin/integrations/validate | проверить создание без записи |
PUT /v1/admin/integrations/{id}/validate | проверить обновление без записи |
DELETE /v1/admin/integrations/{id} | удалить свою версию; установка блокирует удаление с 409 |
GET /v1/admin/hub/integrations | список публичных интеграций всех воркспейсов |
GET /v1/admin/hub/integrations/{name}/{version} | получить публичную версию и её ownerWorkspace |
Режимы inbound-аутентификации не взаимозаменяемы:
shared_secret— kernel проверяет общий секрет до запуска бандла;provider_verified— для провайдера без подписи: platform-owned handler повторно читает объект через API провайдера до эмита события;handler_verified— для first-party HTTP API со своей per-user auth на каждом route (Chat используетx-user-key/HS256 JWT). Kernel по-прежнему резолвит webhook и tenant, но не требует общего webhook secret.
handler_verified нельзя использовать как короткий путь для внешнего webhook:
обычная пользовательская интеграция должна выбрать shared_secret.
Установите на бота
Определение из каталога ставится на конкретного бота. Публикация адресовала
воркспейс — установка адресует бота. Привяжите проект к боту
(brt link --bot-id <id>), затем поставьте интеграцию с её конфигом:
В исходном IntegrationDefinition имя остаётся одним локальным сегментом.
Публичный каталог добавляет handle публикующего воркспейса: исходное
name: 'yookassa' устанавливается как botruntime/yookassa@0.1.0. Bare-ref
name@version остаётся допустимым для приватных определений.
printf '%s' "$BOT_KEY" | brt link --bot-id "$BOT_ID" --key-stdin
printf '{"webhookUrl":"https://example.com/hook"}' \
| brt integrations install my-channel@0.1.0 --config-stdin
export WEBHOOK_ID='wh_replace_from_install_output'
brt integrations register "$WEBHOOK_ID"install возвращает webhookId (публичный) и, для стандартного режима,
webhookSecret (показывается один раз). register регистрирует вебхук во
внешнем сервисе на адрес …/hooks/{webhookId} с этим секретом. Входящий трафик
платформа проверяет по нему.
У встроенных провайдеров без webhook-подписи определение может использовать
webhookAuthMode: "provider_verified". Такой режим доступен только
platform-owned каталогу: секрет не создаётся, а бандл обязан повторно запросить
объект через API провайдера и сверить статус и значимые поля до эмита события.
Одного доверия к телу webhook недостаточно.
Режим авторизации фиксируется при создании name@version. Его нельзя сменить
редеплоем той же версии: сервер отклонит запрос до публикации бандла. Repoint
инсталляции между версиями с разными режимами также запрещён — новую версию
нужно установить заново, чтобы секрет и проверяющий код не разошлись.
Для agent-проекта install/register меняют состояние в Cloud и обновляют
dependency snapshot, но не собирают и не загружают bot bundle. После регистрации выполните
brt deploy --adk и только затем проверяйте production-канал. Для fresh agent
target канонический порядок начинается с первого deploy, а не с ручного link:
deploy → install/register → второй deploy → приёмочная проверка.
Секреты никогда не идут через аргументы командной строки — только через stdin или
--config-file/--value-file. В режиме shared_secret webhookSecret
выдаётся один раз при установке.
Потеряли — снимите установку и поставьте заново.
Эндпоинты установки адресуют бота — заголовок x-bot-id:
| Метод и путь | Действие |
|---|---|
POST /v1/admin/integrations/install | поставить интеграцию на бота; тело {name, version, alias?, config} → {installationId, webhookId, webhookSecret} |
POST /v1/admin/integrations/{webhookId}/register | зарегистрировать вебхук установки → {ok, webhookId, webhookUrl} |
Реестр интеграций
Owned-реестр возвращает только интеграции текущего воркспейса. Публичные версии читаются через Hub:
curl -sS https://botruntime.ru/v1/admin/integrations/my-channel/0.1.0 \
-H "Authorization: Bearer $BRT_TOKEN"| Метод и путь | Действие |
|---|---|
GET /v1/admin/integrations | список своих интеграций |
GET /v1/admin/integrations/{name}/{version} | получить свою интеграцию по имени и версии |
GET /v1/admin/integrations/{id} | получить свою интеграцию по идентификатору |
GET /v1/admin/hub/integrations/{name}/{version} | получить публичную интеграцию |
Те же чтения есть в CLI: brt integrations get <ref>, brt integrations list,
brt integrations delete <ref>.