Чеклист создания интеграции
Пошаговое руководство по созданию новой интеграции от генерации до production.
Обзор
Платформа integ-core предназначена для интеграции голосовых и текстовых AI-ассистентов Happ с внешними сервисами (CRM, телефония и т.д.).
Обязательные эндпоинты
Каждая интеграция имеет 6 обязательных эндпоинтов:
| Эндпоинт | Метод | Тип | Назначение |
|---|---|---|---|
/setup | POST | Built-in | Инфраструктура: миграции БД, секреты |
/health | GET | Built-in | Проверка D1, KV, Creds |
/cron/trigger | POST | Built-in (auto) | Ручной вызов cron handlers (создаётся при наличии scheduled) |
/webhook/call-originate | POST | Template | Инициация исходящего звонка через VA |
/webhook/call-events | POST | Template | Получение событий call_start/call_end от VA |
/webhook/agent-init | POST | Template | Контекст для агента |
/webhook/agent-postcall | POST | Template | AI анализ + CRM |
Workflow
flowchart TD
A[1. Генерация интеграции] --> B[2. Настройка credentials]
B --> C[3. Реализация call-originate]
C --> D[4. Реализация call-events]
D --> E[5. Реализация agent-init]
E --> F[6. Реализация agent-postcall]
F --> G[7. Создание миграций D1]
G --> H[8. Регистрация в integ-admin]
H --> I[9. Вызов /setup]
I --> J[10. Настройка секретов]
J --> K[11. Проверка /health]
K --> L[12. Тестирование webhooks]
L --> M[13. Деплой на dev]
M --> N[14. Деплой на production]Naming Conventions
Таблицы D1
Все таблицы интеграции должны иметь префикс с названием интеграции:
{integration}_{entity}Примеры:
| Интеграция | Таблица | Описание |
|---|---|---|
rozetka | rozetka_calls | Звонки |
rozetka | rozetka_users | Пользователи |
sofa | sofa_calls | Звонки |
booking | booking_orders | Заказы |
Ключи KV
Все ключи KV должны использовать формат с двоеточием:
{integration}:{entity}:{identifier}Примеры:
| Интеграция | Ключ | Описание |
|---|---|---|
rozetka | rozetka:calls:rate-limit | Rate limiting для звонков |
rozetka | rozetka:cache:user:123 | Кэш пользователя |
sofa | sofa:dedup:webhook:abc | Дедупликация вебхуков |
Этапы создания
Этап 1: Генерация интеграции
Создайте базовую структуру интеграции из шаблона:
npm run generate:integration rozetkaЭто создаст:
integrations/rozetka/
├── migrations/ # SQL миграции (.gitkeep)
├── src/
│ ├── index.ts # Entry point (createIntegration)
│ ├── types.ts # TypeScript типы
│ ├── config/
│ │ ├── types.ts # Интерфейсы конфигов
│ │ ├── defaults.ts # Дефолтные значения
│ │ └── index.ts # Barrel export
│ ├── handlers/
│ │ ├── index.ts # Barrel export
│ │ ├── call-originate.ts
│ │ ├── call-events.ts
│ │ ├── agent-init.ts
│ │ └── agent-postcall.ts
│ ├── migrations/
│ │ └── index.ts # Код миграций (MIGRATIONS array)
│ └── utils/
│ └── logger.ts # Логирование
├── package.json
├── tsconfig.json
├── README.md
└── wrangler.tomlЧеклист:
- [ ] Интеграция сгенерирована
- [ ] Название в lowercase без пробелов
Этап 2: Разработка хендлеров
Создайте хендлеры для бизнес-логики интеграции. Каждый handler использует defineHandler() из @happ-integ/core.
Пример: src/handlers/call-originate.ts
import { defineHandler } from "@happ-integ/core";
import { SaasApiClient } from "@happ-integ/saas-api";
import type { ICallOriginatePayload, ICallOriginateResponse, IRozetkaCredentials } from "../types";
export const callOriginateHandler = defineHandler<ICallOriginatePayload, ICallOriginateResponse>({
name: "call-originate",
retries: 3,
endpoint: "POST /webhook/call-originate",
concurrency: { limit: 3 },
async handler({ payload, env, step, ctx, db, creds, integrationName }) {
const { phone_number } = payload;
if (!phone_number) {
return { success: false, error: "phone_number is required" };
}
const { SAAS_API_URL, SAAS_ACCESS_TOKEN } = env;
const integCreds = await step.run("creds.get", () => creds.get<IRozetkaCredentials>(integrationName));
const saasClient = new SaasApiClient(SAAS_API_URL, SAAS_ACCESS_TOKEN);
await step.run("saas.originate", () =>
saasClient.originateCall(integCreds.SAAS_ASSISTANT_ID, phone_number)
);
return { success: true };
},
});Зарегистрируйте в src/index.ts:
import { createIntegration } from "@happ-integ/core";
import * as handlers from "./handlers";
import { MIGRATIONS } from "./migrations";
import { DEFAULT_CONFIGS } from "./config";
export default createIntegration({
name: "rozetka",
handlers: Object.values(handlers),
migrations: MIGRATIONS,
secrets: ["SAAS_ASSISTANT_ID", "CRM_API_KEY"],
defaultConfigs: DEFAULT_CONFIGS,
});Чеклист:
- [ ] Хендлеры созданы в
src/handlers/сdefineHandler() - [ ] Типы определены в
src/types.ts - [ ] Хендлеры экспортируются через
src/handlers/index.ts - [ ] Конфиг создан в
src/config/(types, defaults, index) - [ ]
defaultConfigsпередан вcreateIntegration() - [ ] Зарегистрированы через
createIntegration()вsrc/index.ts
Этап 3: Работа с секретами
Секреты хранятся в D1 базе integ-db в таблице creds с AES-256-GCM шифрованием.
Использование в handler (через DI контекст):
import { defineHandler } from "@happ-integ/core";
interface IRozetkaCredentials {
SAAS_ASSISTANT_ID: string;
CRM_API_KEY: string;
}
export const myHandler = defineHandler({
name: "my-handler",
endpoint: "POST /webhook/my-handler",
async handler({ creds, step, integrationName }) {
// Получаем секреты через handler context
const secrets = await step.run("creds.get", () =>
creds.get<IRozetkaCredentials>(integrationName)
);
// Используйте
console.log(secrets.CRM_API_KEY);
},
});Чеклист:
- [ ] Определён интерфейс секретов в
types.ts - [ ] Секреты получаются через
creds.get()из handler context - [ ] Список секретов указан в
secretsмассивеcreateIntegration()
Этап 4: Работа с базой данных D1
Для хранения данных используйте D1 через модуль @happ-integ/db.
Создание миграции в коде: src/migrations/index.ts
Миграции определяются как TypeScript объекты в src/migrations/index.ts и применяются через POST /setup. Директория migrations/ содержит только .gitkeep.
import type { IMigration } from "@happ-integ/core";
export const MIGRATIONS: IMigration[] = [
{
name: "rozetka/0001_create_orders",
sql: `CREATE TABLE IF NOT EXISTS rozetka_orders (
id TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'pending',
data TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)`,
},
{
name: "rozetka/0002_create_orders_index",
sql: `CREATE INDEX IF NOT EXISTS idx_rozetka_orders_status ON rozetka_orders(status)`,
},
];Использование в коде:
import { getDb } from "../utils/clients";
const db = getDb();
// Вставка (prefix добавляется автоматически: "orders" → "rozetka_orders")
await db.insert("orders", {
id: "order-123",
status: "pending",
data: JSON.stringify({ items: [] }),
});
// Запрос
const order = await db.queryOne<{ id: string; status: string }>(
"SELECT * FROM rozetka_orders WHERE id = ?",
["order-123"]
);
// Обновление
await db.execute("UPDATE rozetka_orders SET status = ? WHERE id = ?", ["completed", "order-123"]);Чеклист:
- [ ] Миграции определены в
src/migrations/index.tsкакIMigration[] - [ ] Массив
MIGRATIONSпередан вcreateIntegration() - [ ] Таблицы имеют префикс интеграции
- [ ] Индексы добавлены для частых запросов
- [ ] Миграции применяются через
POST /setup
Этап 5: Работа с KV (кэш)
Для кэширования используйте cache из handler context. Для конфигурации используйте config.
Использование в handler (через DI контекст):
export const myHandler = defineHandler({
name: "my-handler",
endpoint: "POST /webhook/my-handler",
async handler({ cache, step, integrationName }) {
// Кэширование через step.run для durability
const cached = await step.run(
"cache.get",
() => cache.get<IData>(`${integrationName}:cache:user:123`),
{ optional: true }
);
if (cached) return cached;
// ... получить данные ...
// Сохранить в кэш (TTL 1 час)
await step.run(
"cache.set",
() => cache.set(`${integrationName}:cache:user:123`, data, 3600),
{ optional: true, skipOutput: true }
);
},
});Чеклист:
- [ ] Ключи KV имеют префикс интеграции
- [ ] TTL установлен для всех кэшей
- [ ] Rate limiting настроен через handler config
Этап 6: Реализация 4 template handlers
Все handler'ы используют defineHandler(). /setup и /health — built-in (создаются автоматически core).
Очередь звонков: Для handlers
call-originate,call-eventsиretryиспользуй готовый пакет@happ-integ/call-queue. Он содержит concurrency-контроль, очередь, retry-окна и управление статусами звонков. Не нужно писать эту логику вручную. См. Integration Guide — Call Queue.
Handler call-originate (инициация звонка)
src/handlers/call-originate.ts — инициирует исходящий звонок через SaaS API Voice Assistant.
import { defineHandler } from "@happ-integ/core";
import { SaasApiClient } from "@happ-integ/saas-api";
import type { ICallOriginatePayload, ICallOriginateResponse, IRozetkaCredentials } from "../types";
export const callOriginateHandler = defineHandler<ICallOriginatePayload, ICallOriginateResponse>({
name: "call-originate",
retries: 3,
endpoint: "POST /webhook/call-originate",
concurrency: { limit: 3 },
async handler({ payload, env, step, creds, integrationName }) {
const { phone_number } = payload;
if (!phone_number) return { success: false, error: "phone_number is required" };
const integCreds = await step.run("creds.get", () => creds.get<IRozetkaCredentials>(integrationName));
const saasClient = new SaasApiClient(env.SAAS_API_URL, env.SAAS_ACCESS_TOKEN);
await step.run("saas.originate", () =>
saasClient.originateCall(integCreds.SAAS_ASSISTANT_ID, phone_number)
);
return { success: true };
},
});Handler agent-init (контекст для агента)
src/handlers/agent-init.ts — поиск контакта в CRM, возвращает dynamic_variables для VA.
import { defineHandler } from "@happ-integ/core";
import type { IAgentInitPayload, IAgentInitResponse, IRozetkaCredentials } from "../types";
export const agentInitHandler = defineHandler<IAgentInitPayload, IAgentInitResponse>({
name: "agent-init",
retries: 0,
endpoint: "POST /webhook/agent-init",
async handler({ payload, step, cache, creds, integrationName }) {
const { phone_number } = payload;
if (!phone_number) return getDefaultResponse();
const cachedContext = await step.run(
"cache.get",
() => cache.get<IAgentInitResponse>(`context:${phone_number}`),
{ optional: true }
);
if (cachedContext) return cachedContext;
const secrets = await step.run("creds.get", () => creds.get<IRozetkaCredentials>(integrationName), { skipOutput: true });
// TODO: CRM lookup using secrets
const response: IAgentInitResponse = {
type: "conversation_initiation_client_data",
dynamic_variables: { first_message: "Привіт!", client_name: "Клієнт" },
};
await step.run("cache.set", () => cache.set(`context:${phone_number}`, response, 3600), { optional: true, skipOutput: true });
return response;
},
});Handler agent-postcall (AI анализ + CRM)
src/handlers/agent-postcall.ts — AI анализ транскрипции, обновление CRM.
Handler call-events (статусы от VA)
src/handlers/call-events.ts — обрабатывает call_start/call_end события, планирует retry.
Чеклист:
- [ ]
call-originateинициирует звонок через SaaS API - [ ]
call-eventsобрабатывает события от VA - [ ]
agent-initвозвращает контекст для ассистента - [ ]
agent-postcallAI анализ + обновление CRM - [ ]
/setupи/health— built-in, не требуют реализации
Этап 7: Регистрация в integ-admin
Откройте integ-admin на локальном порту:
http://localhost:4200Шаги:
- Перейдите в раздел Integrations
- Нажмите Create Integration
- Введите название:
rozetka - Система отобразит зарегистрированные handlers интеграции
Чеклист:
- [ ] integ-admin запущен на localhost:4200
- [ ] Интеграция создана
- [ ] Все handlers видны
Этап 8: Инициализация через /setup
В integ-admin или через curl:
curl -X POST http://localhost:8787/rozetka/setup \
-H "Content-Type: application/json" \
-d '{"force": false}'Ожидаемый результат:
{
"success": true,
"database": {
"migrated": true,
"tables": ["rozetka_calls"]
},
"secrets": {
"created": ["API_KEY", "API_SECRET", "WEBHOOK_URL"],
"existing": []
},
"configs": {
"created": ["crm_fields", "retry", "call_handling"],
"existing": []
},
"errors": []
}/setup автоматически создаёт дефолтные конфиги из defaultConfigs, переданных в createIntegration(). Если конфиг уже существует — не перезаписывает (используйте "force": true для перезаписи).
Чеклист:
- [ ] /setup выполнен успешно
- [ ] Миграции применены к D1
- [ ] Placeholder секреты созданы
- [ ] Дефолтные конфиги созданы в KV
Этап 9: Настройка секретов
В integ-admin:
- Перейдите на страницу /secrets
- Выберите интеграцию
rozetka - Заполните все необходимые секреты:
API_KEY— ключ APIAPI_SECRET— секрет APIWEBHOOK_URL— URL для webhook-ов
Чеклист:
- [ ] Все секреты заполнены
- [ ] Значения корректны (проверены с заказчиком)
Этап 10: Проверка через /health
Проверьте все компоненты:
curl http://localhost:8787/rozetka/healthОжидаемый результат:
{
"timestamp": "2024-01-15T10:00:00.000Z",
"testId": "test_1234567890",
"success": true,
"checks": {
"d1": { "success": true, "details": { "tableExists": true } },
"kv": { "success": true, "details": { "writeRead": true } },
"creds": { "success": true, "details": { "keysFound": ["API_KEY", "API_SECRET"] } }
}
}Чеклист:
- [ ] D1 — success
- [ ] KV — success
- [ ] Creds — success
Этап 11: Тестирование webhooks
Протестируйте основные эндпоинты:
1. Тест /webhook/agent-init:
curl -X POST http://localhost:8787/rozetka/webhook/agent-init \
-H "Content-Type: application/json" \
-d '{"phone_number": "+380501234567"}'Ожидаемый ответ:
{
"type": "conversation_initiation_client_data",
"dynamic_variables": {
"first_message": "Привіт!",
"client_name": "Клієнт"
}
}2. Тест /webhook/call-originate:
curl -X POST http://localhost:8787/rozetka/webhook/call-originate \
-H "Content-Type: application/json" \
-d '{"phone_number": "+380501234567"}'Ожидаемый ответ:
{
"success": true
}Чеклист:
- [ ] /webhook/agent-init возвращает контекст
- [ ] /webhook/call-originate инициирует звонок
- [ ] Данные сохраняются в D1
Этап 12: Деплой на dev
1. Commit и push:
git add .
git commit -m "feat(rozetka): add integration"
git push origin dev2. CI/CD:
- Push в
devветку автоматически запустит деплой на dev окружение
3. Настройка в integ-admin.dev:
- Откройте
https://admin.integ.dev.happ.tools - Повторите шаги 7-10 для dev окружения:
- Create Integration
- Вызвать /setup
- Настройте секреты
- Проверить /health
4. Проверка:
# Health check
curl https://integ.dev.happ.tools/rozetka/healthЧеклист:
- [ ] Код запушен в
dev - [ ] CI/CD прошёл успешно
- [ ] Интеграция настроена в admin.integ.dev.happ.tools
- [ ] /health проходит на dev
Этап 13: Деплой на production
1. Создайте Pull Request:
# Из dev в main
gh pr create --base main --head dev --title "feat(rozetka): add integration"2. После merge:
- Push в
mainавтоматически задеплоит на production
3. Настройка в production integ-admin:
- Откройте
https://admin.integ.happ.tools - Повторите шаги 7-10 для production:
- Create Integration
- Вызвать /setup
- Настройте секреты (production значения!)
- Проверить /health
4. Финальная проверка:
# Health check
curl https://integ.happ.tools/rozetka/healthЧеклист:
- [ ] PR создан и одобрен
- [ ] Merge в main выполнен
- [ ] Интеграция настроена в admin.integ.happ.tools
- [ ] Production секреты установлены
- [ ] /health проходит на production
- [ ] Мониторинг настроен
Окружения
| Окружение | integ-core | integ-admin |
|---|---|---|
| Local | localhost:8787 | localhost:4200 |
| Dev | integ.dev.happ.tools | admin.integ.dev.happ.tools |
| Production | integ.happ.tools | admin.integ.happ.tools |
Быстрый чеклист
[ ] 1. pnpm generate:integration {name}
[ ] 2. Определить credentials в src/types.ts
[ ] 3. Создать config/ (types, defaults, index) — вынести все переменные бизнес-логики
[ ] 4. Реализовать call-originate (инициация звонка)
[ ] 5. Реализовать call-events (события от VA)
[ ] 6. Реализовать agent-init (контекст клиента)
[ ] 7. Реализовать agent-postcall (AI анализ + CRM)
[ ] 8. Создать миграции D1
[ ] 9. Настроить cron (если нужен) — scheduled[] в createIntegration + [triggers] в wrangler.toml
[ ] 10. integ-admin → Create Integration
[ ] 11. Вызвать /setup → создать БД, секреты, дефолтные конфиги
[ ] 12. Проверить /health → все компоненты green
[ ] 13. Протестировать все 4 webhook эндпоинта
[ ] 14. DeployОбязательные эндпоинты (summary)
| Эндпоинт | Метод | Тип | Когда вызывается | Что делает |
|---|---|---|---|---|
/setup | POST | Built-in | При инициализации | Создает таблицы, секреты, дефолтные конфиги |
/health | GET | Built-in | Мониторинг | Проверяет D1, KV, Creds |
/webhook/call-originate | POST | Template | CRM trigger | Инициирует исходящий звонок через VA |
/webhook/call-events | POST | Template | От VA сервера | Обрабатывает события call_start/call_end |
/webhook/agent-init | POST | Template | От 11labs агента | Возвращает контекст клиента |
/webhook/agent-postcall | POST | Template | От 11labs агента | AI анализ транскрипции, CRM update |
Связанные документы
- ARCHITECTURE.md — архитектура системы
- DEVELOPMENT.md — локальная разработка
- SECRETS.md — управление секретами
- CODE_RULES.md — правила написания кода
- ENVIRONMENTS.md — окружения