Попробовать API
</>
DX публичного API: quickstart, триал, webhook-симулятор
sdkКейс
14 мин

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.

bash
# 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
# 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, рядом кнопка «Перевыпустить», которая аннулирует старый и сразу показывает новый. Одна транзакция:

php
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, просто значения ниже; те же коды ошибок, те же заголовки.

Три отличия триала от прода

Различия ровно в трёх местах, и все три — часть общей механики тарифов, а не спецкейсы:

  1. Лимиты тарифа. api_rate_per_minute в триале ниже (30), у бизнес-тарифа — 2000; api_rate_per_day считается там же. Разработчик на триале сталкивается ровно с тем же механизмом rate-limit, что и его пользователи на проде, просто с более жёстким порогом.
  2. Toggle записи. В кабинете триала владелец проекта может выставить api_write_enabled = false — кабинет становится read-only, все POST/PUT/DELETE возвращают 403 с понятным кодом. Удобно для смоук-тестов чтения без риска что-то изменить. Тот же тогл доступен платным клиентам.
  3. Срок жизни. Триал — 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 на удаление. Никакой связи с основной платформой по бизнес-логике, никакой авторизации — намеренно, чтобы инспектор оставался изолированным песочничным инструментом.

sql
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. Это детерминированный вход для интеграционных тестов. В связке шаги закрывают цикл, и каждый запускается независимо:

  1. Создать bin на webhook.botix.pro, получить структуру реального события, изучить headers и подпись.
  2. Написать обработчик и прогнать его локально — тот же payload на свой localhost через curl.
  3. Когда готов — отправить тестовый 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 для разработчиков нужен в первый день, не в первый месяц.