Skip to content

Чеклист создания интеграции

Пошаговое руководство по созданию новой интеграции от генерации до production.


Обзор

Платформа integ-core предназначена для интеграции голосовых и текстовых AI-ассистентов Happ с внешними сервисами (CRM, телефония и т.д.).

Обязательные эндпоинты

Каждая интеграция имеет 6 обязательных эндпоинтов:

ЭндпоинтМетодТипНазначение
/setupPOSTBuilt-inИнфраструктура: миграции БД, секреты
/healthGETBuilt-inПроверка D1, KV, Creds
/cron/triggerPOSTBuilt-in (auto)Ручной вызов cron handlers (создаётся при наличии scheduled)
/webhook/call-originatePOSTTemplateИнициация исходящего звонка через VA
/webhook/call-eventsPOSTTemplateПолучение событий call_start/call_end от VA
/webhook/agent-initPOSTTemplateКонтекст для агента
/webhook/agent-postcallPOSTTemplateAI анализ + CRM

Workflow

mermaid
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}

Примеры:

ИнтеграцияТаблицаОписание
rozetkarozetka_callsЗвонки
rozetkarozetka_usersПользователи
sofasofa_callsЗвонки
bookingbooking_ordersЗаказы

Ключи KV

Все ключи KV должны использовать формат с двоеточием:

{integration}:{entity}:{identifier}

Примеры:

ИнтеграцияКлючОписание
rozetkarozetka:calls:rate-limitRate limiting для звонков
rozetkarozetka:cache:user:123Кэш пользователя
sofasofa:dedup:webhook:abcДедупликация вебхуков

Этапы создания

Этап 1: Генерация интеграции

Создайте базовую структуру интеграции из шаблона:

bash
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

typescript
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:

typescript
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 контекст):

typescript
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.

typescript
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)`,
  },
];

Использование в коде:

typescript
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 контекст):

typescript
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.

typescript
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.

typescript
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-postcall AI анализ + обновление CRM
  • [ ] /setup и /health — built-in, не требуют реализации

Этап 7: Регистрация в integ-admin

Откройте integ-admin на локальном порту:

http://localhost:4200

Шаги:

  1. Перейдите в раздел Integrations
  2. Нажмите Create Integration
  3. Введите название: rozetka
  4. Система отобразит зарегистрированные handlers интеграции

Чеклист:

  • [ ] integ-admin запущен на localhost:4200
  • [ ] Интеграция создана
  • [ ] Все handlers видны

Этап 8: Инициализация через /setup

В integ-admin или через curl:

bash
curl -X POST http://localhost:8787/rozetka/setup \
  -H "Content-Type: application/json" \
  -d '{"force": false}'

Ожидаемый результат:

json
{
	"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:

  1. Перейдите на страницу /secrets
  2. Выберите интеграцию rozetka
  3. Заполните все необходимые секреты:
    • API_KEY — ключ API
    • API_SECRET — секрет API
    • WEBHOOK_URL — URL для webhook-ов

Чеклист:

  • [ ] Все секреты заполнены
  • [ ] Значения корректны (проверены с заказчиком)

Этап 10: Проверка через /health

Проверьте все компоненты:

bash
curl http://localhost:8787/rozetka/health

Ожидаемый результат:

json
{
	"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:

bash
curl -X POST http://localhost:8787/rozetka/webhook/agent-init \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "+380501234567"}'

Ожидаемый ответ:

json
{
  "type": "conversation_initiation_client_data",
  "dynamic_variables": {
    "first_message": "Привіт!",
    "client_name": "Клієнт"
  }
}

2. Тест /webhook/call-originate:

bash
curl -X POST http://localhost:8787/rozetka/webhook/call-originate \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "+380501234567"}'

Ожидаемый ответ:

json
{
  "success": true
}

Чеклист:

  • [ ] /webhook/agent-init возвращает контекст
  • [ ] /webhook/call-originate инициирует звонок
  • [ ] Данные сохраняются в D1

Этап 12: Деплой на dev

1. Commit и push:

bash
git add .
git commit -m "feat(rozetka): add integration"
git push origin dev

2. CI/CD:

  • Push в dev ветку автоматически запустит деплой на dev окружение

3. Настройка в integ-admin.dev:

  1. Откройте https://admin.integ.dev.happ.tools
  2. Повторите шаги 7-10 для dev окружения:
    • Create Integration
    • Вызвать /setup
    • Настройте секреты
    • Проверить /health

4. Проверка:

bash
# 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:

bash
# Из dev в main
gh pr create --base main --head dev --title "feat(rozetka): add integration"

2. После merge:

  • Push в main автоматически задеплоит на production

3. Настройка в production integ-admin:

  1. Откройте https://admin.integ.happ.tools
  2. Повторите шаги 7-10 для production:
    • Create Integration
    • Вызвать /setup
    • Настройте секреты (production значения!)
    • Проверить /health

4. Финальная проверка:

bash
# Health check
curl https://integ.happ.tools/rozetka/health

Чеклист:

  • [ ] PR создан и одобрен
  • [ ] Merge в main выполнен
  • [ ] Интеграция настроена в admin.integ.happ.tools
  • [ ] Production секреты установлены
  • [ ] /health проходит на production
  • [ ] Мониторинг настроен

Окружения

Окружениеinteg-coreinteg-admin
Locallocalhost:8787localhost:4200
Devinteg.dev.happ.toolsadmin.integ.dev.happ.tools
Productioninteg.happ.toolsadmin.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)

ЭндпоинтМетодТипКогда вызываетсяЧто делает
/setupPOSTBuilt-inПри инициализацииСоздает таблицы, секреты, дефолтные конфиги
/healthGETBuilt-inМониторингПроверяет D1, KV, Creds
/webhook/call-originatePOSTTemplateCRM triggerИнициирует исходящий звонок через VA
/webhook/call-eventsPOSTTemplateОт VA сервераОбрабатывает события call_start/call_end
/webhook/agent-initPOSTTemplateОт 11labs агентаВозвращает контекст клиента
/webhook/agent-postcallPOSTTemplateОт 11labs агентаAI анализ транскрипции, CRM update

Связанные документы