Zero-deps SDK, webhook-симулятор и open-source
TL;DR: если после `composer require` интегратор тащит к себе ещё четырнадцать чужих пакетов — это не SDK, а технический долг с нашим логотипом. Рассказываем честно, с граблями: как выкинули из трёх SDK все зависимости до нуля, как заодно построили открытый webhook-симулятор `webhook.botix.pro`, и почему всё в итоге легло в open-source на GitHub. Всё, что ниже — реальный код и реальные цифры, без маркетинга.
Коротко
- Zero-dependency SDK. Три SDK (PHP, Python, Node) не тянут ни одной транзитивной зависимости — только stdlib языка.
composer audit/npm audit/safety checkзелёные не по везению, а по дизайну. - Webhook-симулятор.
webhook.botix.proвыдаёт временный публичный URL и показывает всё, что в него приходит — headers, body, подписьX-Botix-Signature. Обработчик доводится до готовности до подключения реального канала. - Open-source. SDK, CLI и три example-репозитория выложены на GitHub. Это не подарок сообществу, а снятие блокировки security-команд и сигнал зрелости API.
Zero-dependency SDK: почему 14 зависимостей = технический долг интегратора
SDK для публичного API — это не приложение, а тонкая обёртка над HTTP. Каждая её зависимость становится головной болью того, кто её установит. Разработчик заходит на страницу документации, видит pip install / composer require / npm install, ставит — и через минуту в requirements.txt или composer.lock появляется не одна новая строка, а четырнадцать. SDK притащил HTTP-клиент, JSON-валидатор, библиотеку для UUID, retry-механизм, ещё один логгер и пару утилитарных пакетов, которые поддерживает один мейнтейнер в свободное время.
Мы это прошли на себе. Первое, что сделали при выпуске SDK на трёх языках, — взяли guzzlehttp/guzzle для PHP, requests для Python, axios для Node, написали тонкую обёртку. А потом попробовали поставить эту красоту в чужой проект, и каждая зависимость выстрелила ровно тремя классами проблем.
Конфликты версий. Первый интегратор — Laravel 11. Поставили botix-pro/sdk, получили конфликт версий Guzzle: у них ^7.5, у нас ^7.8. Композер не разрулил, выдал красный текст. В Python это ещё болезненнее: глобальное пространство имён пакетов не позволяет держать две версии requests одновременно, поэтому мягкий pin (>=2.20,<3.0) — это отложенная мина, которая рванёт, когда интегратор через полгода обновит свой фреймворк.
Supply-chain. Разработчик опубликовал скриншот npm audit после установки @botix/sdk: 47 предупреждений, 3 high severity. Наш собственный код — ноль. Остальное — транзитивщина axios. То есть мы своим SDK принесли клиенту 47 проблем, к которым имеем очень опосредованное отношение, а фиксить их он не может: он не контролирует SDK, через который они пришли. event-stream, ua-parser-js, colors.js — история индустрии полна примеров, когда транзитивная зависимость превращалась в вектор атаки.
Устаревание. Зависимость, свежая год назад, сегодня имеет известную CVE. SDK, чтобы оставаться безопасным к установке, обязан их обновлять. Если поддерживается лениво — каждая зависимость становится миной под интегратором.
В команде решение прозвучало с реакцией «ну зачем, есть же удобные библиотеки». Дальше был часовой спор про то, что удобно разработчику SDK, а что удобно интегратору. И мы признали, что путали эти две вещи. Удобно разработчику SDK — requests.get(url), две строки. Удобно интегратору — composer audit без предупреждений, нулевая поддержка. Эти удобства живут в разных вселенных: SDK после первого мажора уже не правят, а интегратор живёт с ним в production каждый день.
Так появилась версия 1.1.0 с нулевыми зависимостями:
// composer.json
{
"name": "botix-pro/sdk",
"require": {
"php": "^8.1",
"ext-curl": "*",
"ext-json": "*"
}
}# pyproject.toml — секция dependencies
dependencies = []// package.json
{
"dependencies": {}
}В PHP — curl через расширение из стандартной поставки. В Python — urllib.request из stdlib. В Node — встроенный https. JSON-парсинг везде из stdlib. UUID-генерация — десять строк собственного кода. Retry-логика — цикл с экспоненциальным бэкоффом за полчаса.
Zero-deps оказался не бесплатным. Где именно наступили на грабли:
- Таймауты в
urllib. Стандартный модуль Python поддерживает один таймаут на весь запрос — никаких отдельных connect/read, как вrequests. Решали через явный socket: лишняя сложность в коде, который должен был стать проще. - HTTP keep-alive в Node
https. Реализован, но настройки пула по умолчанию неоптимальные. Пришлось явно создаватьhttps.AgentсkeepAlive: trueиmaxSockets, чтобы при batch-операциях не открывать новое TCP-соединение на каждый запрос. - multipart/form-data в Python. В stdlib нет высокоуровневого билдера. Свой через
email.mime— тридцать строк вместо одной сrequests. Зато ноль зависимостей. - Тестирование. Моки HTTP без
responsesилиnock— вручную. Решили через генерацию SDK из единогоopenapi.yaml(openapi-generator-cli): шаблоны настроены один раз, сгенерированный код идентичен по структуре во всех трёх языках. Подробнее про этот подход — почему SDK должен генерироваться из OpenAPI. - Документация. Примеры в
requestsкрасивее, чем вurllib. В доках показываемfrom botix import Client; client.contacts.create(...), а stdlib-кишки прячем внутрь SDK.
Что стало проще: установка перестала быть событием в логе CI/CD. Одна строка, одна папка, никаких конфликтов. npm audit не ругается, safety check молчит, composer audit тоже. Размер бандла Node-приложения после установки @botix/sdk растёт на 30–40 КБ, а не на 2 МБ.
Практическое правило, которое мы вывели для себя и рекомендуем всем: перед установкой любого SDK откройте его package.json / composer.json / pyproject.toml на GitHub и посмотрите размер секции dependencies. 0–2 пакета — SDK с уважением к интегратору. 5–7 — приемлемо, если это мейнстрим, а не маргинальные пакеты от одного автора. 10+ — SDK, который кладёт на вас груз поддержки чужой экосистемы. Это не редкое решение и не наша выдумка — Stripe, Cloudflare, GitHub-команды движутся в ту же сторону. Про то, как быстро потрогать наш SDK руками, — в quickstart за 5 минут.
Webhook-симулятор: тестовый payload до подключения канала
Параллельно с SDK копилась другая боль. Любой интегратор упирается в webhook'и. Платформа шлёт ему события — message.created, payment.confirmed и ещё двадцать типов, у каждого свой payload. Чтобы написать обработчик, нужно увидеть payload. Чтобы увидеть — либо реальный канал (часы на подключение Telegram-бота и ожидание тестового сообщения), либо сторонний инструмент.
При этом сам обработчик к каналу не привязан. Ему нужен лишь корректно сформированный HTTP-запрос с теми же headers, тем же payload и той же подписью, что отправил бы прод. Никакого специального состояния «канал подключён» внутри обработчика нет. Значит, весь блок подключения канала на этапе разработки — лишний, и решается он инструментом, который в индустрии устоялся как webhook-инспектор: сервис выдаёт временный публичный URL и показывает всё, что в него приходит — метод, путь, query, headers, body, время прихода.
Самый известный публичный инструмент такого рода — webhook.site, мы им пользовались, сервис хороший. Но три ограничения упёрлись в стену. Часть корпоративных интеграторов не имеет права по регламенту отправлять туда тестовые данные. Внешний сервис не знает нашего формата подписи. И мы не могли дать ссылку «вот штатный инструмент» в собственной документации.
Сели и за пару недель построили webhook.botix.pro — отдельный поддомен с одной задачей. Разработчик заходит на главную, нажимает «Create new URL» и получает что-то вроде https://webhook.botix.pro/r/8f3a1c9b. TTL по умолчанию — 7 дней, после этого bin и все накопленные запросы удаляются автоматически (чистка по cron). На странице bin'а — список входящих запросов с возможностью раскрыть любой и увидеть полное содержимое.
С точки зрения архитектуры это намеренно маленький сервис: один nginx server-блок, два эндпоинта (создание bin'а и приём входящего), две таблицы в БД (webhook_bins + webhook_bin_requests с CASCADE на удаление). Никакой связи с бизнес-логикой платформы: bin не знает, к какому проекту привязан, и не требует авторизации. Это сделано специально, чтобы инспектор оставался изолированным песочничным инструментом, чья доступность не зависит от состояния остальной платформы.
Теперь важная часть, ради которой всё и затевалось. Наши webhook'и подписаны, и симулятор показывает подпись как есть. Платформа шлёт три заголовка — X-Botix-Event, X-Botix-Signature (HMAC-SHA256 от тела запроса на секрете подписки) и X-Botix-Request-Id. Увидев в инспекторе реальную подпись, обработчик проверяют локально — например, через встроенный в SDK verifyWebhook:
import { Router, raw } from 'express';
import { botix } from '../botix-client';
const router = Router();
router.post('/webhook/botix', raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('X-Botix-Signature') || '';
const rawBody = req.body; // Buffer (благодаря raw middleware)
const ok = botix.verifyWebhook(
rawBody,
signature,
process.env.BOTIX_WEBHOOK_SECRET! // btx_whsec_XXXX… держать в env, не в коде
);
if (!ok) return res.status(401).json({ error: 'invalid signature' });
const event = JSON.parse(rawBody.toString());
// Защита от replay: timestamp в payload, не старше 5 минут
const eventAge = Date.now() / 1000 - (event.timestamp || 0);
if (eventAge > 300) return res.status(401).json({ error: 'event too old' });
res.status(200).json({ received: true });
});
export default router;Важная деталь: подпись считается от сырого тела (raw middleware отдаёт Buffer). Если пропустить body через JSON-парсер до проверки, порядок ключей и пробелы поменяются, и HMAC не сойдётся — типовая грабля, на которой спотыкаются все. Про полную схему подписи, retry и dead-letter — в разборе проверки подписи webhook: HMAC, retry, dead-letter.
Инспектор закрывает первую половину задачи — увидеть структуру запроса. Вторую половину (отладку самого кода обработчика) закрывает mock-режим: на странице app.botix.pro/developers в разделе webhook delivery dashboard есть кнопка «Тестовая отправка», которая шлёт payload известной структуры на указанный URL. Это даёт детерминированный вход для интеграционных тестов. В связке оба инструмента замыкают цикл:
- Создаёте bin на
webhook.botix.pro, получаете структуру реального события, изучаете headers и подпись. - Пишете обработчик и проверяете его локально, отправляя на свой
localhostтот же payload черезcurl(запрос из инспектора копируется как curl). - Когда обработчик готов — жмёте «Тестовая отправка» в delivery dashboard и убеждаетесь, что вся цепочка от платформы до приёмника отрабатывает.
Главная ценность здесь — снижение порога входа. Подключение канала и тестирование обработчика — две разные задачи, и их совмещение («подключи канал и попроси кого-нибудь отправить сообщение») вставляет несколько часов чужих действий между «открыл редактор» и «увидел реальный payload». Инспектор разрывает эту последовательность: payload у разработчика через 30 секунд после старта. Это типовое требование зрелого DX — каждый шаг запускается независимо: проверяю подписи, не подключая канал; пишу обработчик, не имея источника; гоняю тесты, не дёргая внешние сервисы. Что бы сделали иначе — добавили бы экспорт в формат Postman Collection (несколько раз просили) и WebSocket для real-time вместо кнопки «Обновить». В плане. Смежный сюжет — polling или webhook: критерии выбора.
Зачем компании открывать исходный код своих SDK
К моменту, когда SDK были готовы и симулятор работал, встал вопрос: открывать ли исходники. Спор был. «Зачем тратить ресурс на публичный репозиторий» против «без open-source любая корпоративная интеграция упрётся в security-команду, которая не пропускает чёрный ящик в production». Открыли. Не от широты души, а потому что open-source SDK — это инструмент с конкретными функциями, а не подарок сообществу.
Функция первая — security-аудит без участия поставщика. Корпоративная интеграция почти всегда проходит через security. Если библиотека закрыта, security видит чёрный ящик и чаще всего блокирует интеграцию, либо требует SOC 2 / ISO 27001 / DPA, либо просит код под NDA — всё три варианта дорогие и медленные. Открытый SDK закрывает их разом: security смотрит репозиторий, видит, что библиотека делает только заявленное, и пропускает. SOC 2 при этом всё равно нужен для инфраструктуры и процессов — но SDK как точка входа в чужой код перестаёт быть чёрным ящиком.
Функция вторая — bug fix не блокируется вендором. В закрытом SDK любая ошибка — тикет в поддержку и ожидание релиза через месяц-три. В open-source интегратор либо делает PR с фиксом, либо временно форкает и патчит через VCS-ссылку в composer.json / package.json, пока upstream не примет. Никакой блокировки.
Функция третья — сигнал зрелости API. Публикация SDK под открытой лицензией — сигнал, что у API есть стабильный контракт, который не стыдно показать. Закрытый SDK читается как «они там наколхозили, поэтому прячут». Сигнал работает не на сознательном уровне: покупатель не говорит «открытый код = зрелый продукт», он говорит «серьёзные люди» — после того, как его инженер посмотрел репозиторий и не нашёл ничего стыдного.
Функция четвёртая — контрибьюции. На старте это односторонняя отдача: поставщик пишет, сообщество потребляет. По мере роста появляются PR с типовыми улучшениями (retry, timeout, прокси). У зрелых SDK (Stripe, Twilio, Slack) — десятки внешних контрибьюторов. У молодых — ноль, и это нормально.
Как это устроено у нас. Сейчас в организации github.com/BOTIX-pro четыре публичных репозитория — sdk-php, sdk-python, sdk-node, cli — плюс три example-интеграции для популярных фреймворков: example-laravel, example-express, example-flask. Лицензия MIT. Пакеты опубликованы в трёх реестрах:
composer require botix-pro/sdk:^1.1
pip install botix==1.1.0
npm install @botix/sdk@1.1.0Один и тот же API представлен через три SDK с одинаковой структурой методов: client.contacts.create(...) выглядит одинаково в Python, Node и PHP, переключаясь между языками, переучиваться не нужно. Это не совпадение, а следствие генерации из единого openapi.yaml.
Про стоимость честно. Страх «код утащат» на практике не работает: SDK бесполезен без API, к которому обращается, а под чужой API его переписать легче, чем переиспользовать. Страх «раскроем архитектуру» решается дисциплиной — SDK делает ровно три вещи (формирует запрос, подписывает его, парсит ответ), вся бизнес-логика на стороне API, а формат работы с публичным API и так лежит в OpenAPI-спецификации. Реальная стоимость одна — поддержка: публичный репозиторий означает входящие issues, и без выделенного человека open-source SDK превращается в кладбище с просрочкой по 200 дней. У нас этот пункт в фазе становления, CONTRIBUTING.md уже на месте. Контрибьюций пока нет — оценку популярности делаем через 90 дней по установкам в Packagist / PyPI / npm и звёздам, до этого никаких выводов.
Что бы сделали иначе: заложили бы генерацию из openapi.yaml сразу (переделывали на втором релизе), опубликовали бы в github.com/BOTIX-pro с самого начала (потом переносили из приватного — сломанные ссылки в старых тикетах), добавили бы CONTRIBUTING.md до первого внешнего вопроса, а не после. Общий вывод простой: закрытый SDK на горизонте года всё равно становится якорем — когда придёт первый клиент с требованием посмотреть код, выбора уже не будет. Смежные материалы по DX и ключам — что должно быть в quickstart и где хранить ключи и хранение API-ключей: антипаттерны.
Попробуйте API BOTIX
Получите ключ, отправьте первый запрос и поймайте webhook — прямо в браузере, без боевого канала.
Попробовать API бесплатноЧитайте также
Почему в SDK ноль зависимостей и генерация из OpenAPI
Онбординг разработчика: quickstart, sandbox и API-ключи
Как надёжно доставлять webhook: подпись, retry, симулятор
SDK BOTIX 1.1: zero-deps, генерация из OpenAPI и триал
Первый запрос к API за 5 минут: SDK из OpenAPI и триал
