DX публичного API: quickstart, триал, webhook-симулятор
TL;DR: публичный API меряют не числом эндпоинтов, а временем «от регистрации до первого 200 OK». В BOTIX этот путь собран из трёх кусков: quickstart с ключом на той же странице, триал вместо отдельного sandbox-контура и webhook-симулятор `webhook.botix.pro`. Ниже — воронка, код, таблицы и честные провалы первой версии.
Коротко
- Quickstart, а не сокращённая дока. Отдельный вход
developers.botix.pro/quickstart: ключ выдаётся на странице, первый блок кода — curl без скролла, реальные значения вместоYOUR_API_KEY. Цель — медиана до первого успешного запроса < 5 минут. - Триал = sandbox. Отдельного
sandbox.api.*нет: триал живёт на том же production-контуре, те же миграции и валидаторы, отличается только лимитами тарифа, тоглом записи и сроком жизни. Это убирает класс ошибок «в sandbox 200, в проде 422». - Свой webhook-симулятор.
webhook.botix.proвыдаёт временный bin-URL, показывает метод/headers/body и знает наш формат подписиX-Botix-Signature(HMAC-SHA256 от raw body) — в отличие от webhook.site.
Quickstart за 5 минут: точка, где теряют интегратора
Первая версия onboarding требовала полную регистрацию, верификацию email и телефона, выбор тарифа и прохождение мастера (выбор ниши, подключение Telegram, импорт каталога) — и только потом раздел «API-ключи». Воронка получилась с обрывом: 1000 заходов на главную → 200 нажатий «Начать триал» → 80 регистраций → 30 верификаций телефона → 12 завершённых мастеров → 4 захода в раздел API.
Разработчик пришёл проверить гипотезу «подходит ли этот API под мою задачу», ему не нужен ассистент-консультант для барбершопа. Каждая страница мастера — препятствие; на третьей он закрывал вкладку. У разработчика и владельца бизнеса разные задачи в первое касание: владелец хочет увидеть работающего бота под нишу, разработчик — понять формат запросов/ответов и проверить подпись webhook. Один мастер для обоих — компромисс, в котором теряют оба.
Quickstart — это не «короткая версия гайда». Документация отвечает на вопрос «как устроена система», её читают последовательно и возвращаются много раз. Quickstart отвечает на вопрос «как получить первый ответ от API прямо сейчас», его читают один раз и по диагонали. Признаки правильного quickstart, которые легко проверить глазами:
- Первый блок кода виден без скролла, на первом экране после заголовка.
- API-ключ выдаётся прямо на этой же странице. Не «зарегистрируйтесь, подтвердите email, найдите вкладку API».
- Snippet содержит реальные значения, а не плейсхолдеры вроде
YOUR_API_KEYилиhttps://api.example.com. - Есть переключатель языка: curl и два-три популярных языка с SDK.
- Между копированием кода и видимым результатом нет промежуточных шагов.
Что сделали в BOTIX. При регистрации с пометкой «я разработчик» (btx_users.registration_intent = 'developer', флаг is_developer_trial на проекте) система обходит мастер и сразу открывает /developers/quickstart. На странице — выданный API-ключ (показывается один раз), четыре snippet-таба (curl, PHP, Python, Node), ссылки на полную документацию и Swagger UI. Авторизация везде одна — заголовок Authorization: Bearer btx_live_…, ключ привязан к одному project_id и имеет список scopes.
# curl
curl -X POST https://api.botix.pro/public/v1/contacts \
-H "Authorization: Bearer btx_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"phone":"+79991234567","name":"Test"}'# Python через SDK
from botix import Client
client = Client(api_key="XXXXXXXX…")
contact = client.contacts.create(phone="+79991234567", name="Test")Никакого выбора ниши и настройки сценария. Если триал должен решить вопрос «совместим ли API с моими нуждами» — он должен решать именно его, а не «умеете ли вы пользоваться платформой». Разбор самого первого запроса на Python вынесен в отдельный туториал — Первый запрос к API за 5 минут: quickstart на Python.
Одноразовый показ ключа и кнопка «Перевыпустить»
Стандартный паттерн — генерировать ключ, показать один раз, дальше хранить только хеш (bcrypt) и key_prefix из 12 символов для отображения в списке. Если БД утечёт — восстановить рабочие ключи невозможно. Но для quickstart это создаёт трение: разработчик потерял ключ между окнами, получил 401 — нужно перегенерировать. Компромисс: ключ показывается один раз на странице quickstart, рядом кнопка «Перевыпустить», которая аннулирует старый и сразу показывает новый. Одна транзакция:
public function rotate(): JsonResponse
{
$projectId = Auth::projectId();
Auth::assertProjectAccess($projectId);
DB::transaction(function () use ($projectId) {
ApiKey::where('project_id', $projectId)
->where('revoked_at', null)
->update(['revoked_at' => now()]);
$plain = 'btx_live_' . Str::random(48);
ApiKey::create([
'project_id' => $projectId,
'token_hash' => password_hash($plain, PASSWORD_BCRYPT),
'last4' => substr($plain, -4),
'scopes' => json_encode(['*']),
]);
Cache::put("quickstart_key_one_time:{$projectId}", $plain, 60);
});
return response()->json([
'api_key' => Cache::pull("quickstart_key_one_time:{$projectId}"),
'message' => 'Save it now — it will not be shown again',
]);
}Правило «не хранить ключ в открытом виде» сохранено, трение для тестовых сценариев убрано. Антипаттерны хранения ключа уже на стороне интегратора — в разборе хранение API-ключей и multi-tenant интеграция.
Что должно быть в первом ответе API
Первый запрос разработчика почти всегда заканчивается одним из четырёх исходов, и каждый требует подсказки:
- 200 — возвращённый объект содержит достаточно полей, чтобы было видно, что произошло. Не
{"id": 123}, а полный объект со временем создания, статусом и ссылкой на детальный endpoint. - 401 — ответ явно говорит, что не так с ключом: отсутствует, истёк, перевыпущен, не имеет нужного
scope. Без этого разработчик гадает. - 422 — список ошибок валидации по полям с конкретным полем и машинным кодом (
VALIDATION_ERROR, доменные вродеNO_CHANNEL_AVAILABLE,CONTACT_BUSY). Стандарт RFC 7807 (Problem Details for HTTP APIs) — хорошая отправная точка. - 5xx — уникальный
request_id(UUID) в заголовкеX-Botix-Request-Id, который разработчик прикладывает к обращению в поддержку. Без него диагностика на стороне поставщика занимает в десять раз дольше.
Rate-limit виден во всех ответах — X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, на 429 дополнительно Retry-After. Тот же формат заголовков разработчик увидит и на триале, и на проде.
Триал как sandbox: почему второй контур не нужен
Первое решение команды при выпуске публичного API часто звучит: «нужен sandbox». Отдельный домен sandbox.api.example.com, отдельная база, отдельные ключи, отдельный CI-пайплайн. Логика понятная — дать разработчику место, где можно ломать всё, не задевая боевые данные. Проблема в том, что любой второй контур — это не половина прода, а отдельный продукт со своей жизнью, которая со временем расходится с продом:
- Дублирование инфраструктуры. Свои серверы БД, очередей, кеша, воркеров. Экономишь — sandbox медленнее и отдаёт другие тайминги, тесты становятся ненадёжными.
- Дублирование миграций и фич. Любая фича должна попадать в оба контура; реальная команда забывает. Через полгода в sandbox устаревшие поля и старая логика ошибок, заводится тикет «синхронизировать sandbox с prod».
- Расхождение поведения — самый болезненный пункт. В sandbox отключают часть проверок, разработчик пишет интеграцию, она работает, переключается на prod — падает на первом запросе. Классический «у меня работало в sandbox».
Подход BOTIX иначе. Отдельного sandbox нет. Триальный кабинет физически живёт на production-контуре, но с жёсткими лимитами:
- Триальный проект пишет в ту же базу, что и платный.
- Запросы идут через те же FPM-воркеры, те же FastAPI-сервисы, ту же Redis-очередь.
- Те же миграции, те же фичи, та же логика обработки ошибок.
- Те же rate-limit, просто значения ниже; те же коды ошибок, те же заголовки.
Три отличия триала от прода
Различия ровно в трёх местах, и все три — часть общей механики тарифов, а не спецкейсы:
- Лимиты тарифа.
api_rate_per_minuteв триале ниже (30), у бизнес-тарифа — 2000;api_rate_per_dayсчитается там же. Разработчик на триале сталкивается ровно с тем же механизмом rate-limit, что и его пользователи на проде, просто с более жёстким порогом. - Toggle записи. В кабинете триала владелец проекта может выставить
api_write_enabled = false— кабинет становится read-only, все POST/PUT/DELETE возвращают 403 с понятным кодом. Удобно для смоук-тестов чтения без риска что-то изменить. Тот же тогл доступен платным клиентам. - Срок жизни. Триал — 14 дней, потом кабинет переходит в read-only до оплаты. Не успел — можно завести второй триал или оплатить минимальный тариф.
Почему работает: разработчик тестирует ту же систему, которую увидит на проде, — не её модель, не урезанную копию, не сборку трёхмесячной давности. Это убирает целый класс ошибок: «поле было в sandbox, в проде нет» невозможно (контур один); «в sandbox 200, в проде 422» невозможно (валидаторы те же); «webhook в sandbox уходит, в проде нет» невозможно (код обработчика тот же). Идея «безопасной среды» реализуется через изоляцию проекта, а не инфраструктуры: API-ключ триала видит только данные своего проекта, Auth::assertProjectAccess() защищает то же самое, что у платных клиентов. Бонус — команда платформы тратит ноль усилий на синхронизацию контуров, потому что синхронизировать нечего.
Стоимость поддержки при двух подходах:
| Затрата | dev/staging/prod | Триал-как-sandbox |
|---|---|---|
| Серверы | х2–х3 | х1 |
| Миграции | Каждую применить дважды | Один раз |
| Тестовые данные | Поддерживать отдельно | Не нужны |
| Документация | «Различия sandbox и prod» | Не нужна |
| Тикеты «в sandbox работает, в prod нет» | Регулярно | Не возникают |
Где подход неприменим
Не любой API подходит для такой модели. Отдельный sandbox оправдан там, где у запроса есть необратимый внешний эффект:
- Финансовые операции с реальным движением денег. Если POST к
/payments/chargeсписывает реальные деньги — нужен test-mode флаг на уровне ключа с тестовым эквайринговым шлюзом. - Побочные эффекты на третьих лиц. SMS, push, email на боевые номера клиентов недопустимы — нужен «test recipient» или режим «логируется, не доставляется».
- Тяжёлые операции с физическим миром (оборудование, печать на завод, доставка грузов) — тестировать на проде нельзя в принципе, здесь dev/staging/prod единственный путь.
В BOTIX все три случая обходятся флагами в самих модулях. Например, у bot-preview есть $context['preview']=true — модуль пишет в лог, но не отправляет реальное сообщение в Telegram. Это снимает риск побочных эффектов без второго контура.
Webhook-симулятор: webhook.botix.pro
Типичный сценарий разработки приёмника webhook. Чтобы проверить, что обработчик валидирует подпись, разбирает JSON, кладёт событие в очередь и отвечает 200 OK за допустимое время — нужен реальный входящий запрос. И здесь начинается лишняя работа: завести аккаунт у внешнего сервиса, получить токен, настроить бота, попросить кого-то отправить сообщение, дождаться доставки. Часы вместо минут. При этом сам обработчик к каналу не привязан — ему нужен лишь корректно сформированный HTTP-запрос с теми же headers, тем же payload и той же подписью, что отправит прод. Значит, весь блок подключения канала на этапе разработки — лишний.
Самый известный публичный инспектор — webhook.site. Базовую потребность он закрывает хорошо. Но как только он становится частью повседневной разработки, появляются ограничения: внешний сервис не знает формата подписи конкретного провайдера, не подскажет, что именно сломано в обработчике, и есть класс компаний, которым держать секреты (пусть и тестовые) на чужой инфраструктуре нежелательно по внутренним требованиям.
Мы сделали свой — webhook.botix.pro. Разработчик нажимает «Create new URL», получает https://webhook.botix.pro/r/8f3a1c9b. TTL по умолчанию 7 дней, потом bin и все накопленные запросы удаляются автоматически. На странице bin'а видно: метод, путь, headers, body, время прихода каждого запроса. Архитектурно это маленький сервис: один nginx server-блок, два эндпоинта (создание bin'а и приём входящего запроса), две таблицы с CASCADE на удаление. Никакой связи с основной платформой по бизнес-логике, никакой авторизации — намеренно, чтобы инспектор оставался изолированным песочничным инструментом.
CREATE TABLE webhook_bins (
id CHAR(8) PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL
);
CREATE TABLE webhook_bin_requests (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
bin_id CHAR(8) NOT NULL,
method VARCHAR(10) NOT NULL,
headers JSON NOT NULL,
body LONGBLOB NOT NULL,
received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (bin_id) REFERENCES webhook_bins(id) ON DELETE CASCADE
);Чем свой инспектор отличается от webhook.site
Он знает наш формат. Подпись — HMAC-SHA256 от raw body, заголовок X-Botix-Signature; рядом идут X-Botix-Event (тип события) и X-Botix-Request-Id (UUID). Инспектор показывает подпись как валидную или невалидную и объясняет почему — если порядок ключей JSON изменён парсингом, если timestamp вне окна. Плюс контроль над инфраструктурой: понадобится экспорт собранных запросов в Postman Collection — дописывается прямо в коде сервиса, с внешним сервисом такие доработки невозможны.
Инспектор закрывает первую половину задачи — увидеть структуру запроса. Вторую половину, отладку кода обработчика, закрывает delivery dashboard: на app.botix.pro/developers в разделе webhook delivery есть кнопка «Тестовая отправка» — она шлёт фиктивное событие test известной структуры на указанный URL и возвращает request_id. Это детерминированный вход для интеграционных тестов. В связке шаги закрывают цикл, и каждый запускается независимо:
- Создать bin на
webhook.botix.pro, получить структуру реального события, изучить headers и подпись. - Написать обработчик и прогнать его локально — тот же payload на свой localhost через curl.
- Когда готов — отправить тестовый webhook через delivery dashboard и убедиться, что цепочка от платформы до приёмника отрабатывает.
Каждый шаг самодостаточен: подпись проверяю, не подключая канал; обработчик пишу, не имея реального источника; интеграционные тесты гоняю, не дёргая внешние сервисы. Схему самой доставки — подписи, retry, dead-letter — разбираем отдельно в HMAC-подпись, retry и dead-letter доставки webhook.
Что не сработало в первой версии
Мы решили, что разработчику удобнее SDK-инсталляция первым шагом: composer require botix-pro/sdk. Оказалось — нет. Первый шаг должен быть curl. Причина простая: composer install занимает 10–30 секунд минимум, за это время разработчик успевает усомниться в выборе. Curl — копировать-вставить-Enter, ответ через секунду. Поменяли порядок табов: первый curl, дальше PHP/Python/Node, и уже после первого успешного curl предлагаем установить SDK. Воронка до первого 200 OK сократилась. Почему SDK всё же нужен и почему без лишних зависимостей — в zero-dependency SDK и генерация из OpenAPI.
Цифры
- 92 code-sample'а в
openapi.yaml— по 3–4 языка на каждом из эндпоинтов (curl / PHP / Python / Node). - 4 публичных репозитория с примерами в
github.com/BOTIX-pro:example-laravel,example-express,example-flask(django запланирован на v1.2). - Webhook-симулятор обработал ~12 тыс. тестовых запросов за первый месяц после запуска.
- Триал 14 дней, без email-верификации, без выбора тарифа.
Что бы сделали иначе — запустили бы quickstart одновременно с v1.0, а не через две недели после. В эти две недели работал «вход через регистрацию с мастером», и аналитику onboarding было не прочитать: половина разработчиков уже ушла, половина забрала ключ через тикет в поддержку. Если запускаешь публичный API, отдельный onboarding для разработчиков нужен в первый день, не в первый месяц.
Попробуйте API BOTIX
Получите ключ, отправьте первый запрос и поймайте webhook — прямо в браузере, без боевого канала.
Попробовать API бесплатноЧитайте также
Первый запрос к API за 5 минут: SDK из OpenAPI и триал
Почему в SDK ноль зависимостей и генерация из OpenAPI
Как надёжно доставлять webhook: подпись, retry, симулятор
Онбординг разработчика: quickstart, sandbox и API-ключи
