Перейти к содержимому

Вебхуки

MoreLogin может отправлять JSON-событие методом POST на ваш HTTPS-эндпоинт сразу после того, как операция с облачным телефоном или облачным браузером достигла итогового результата, поэтому опрашивать результат больше не нужно. На команду хранится один URL обратного вызова, и Local API с Open API пишут в одну и ту же запись.

Настройка URL обратного вызова

Оба контура предоставляют одну и ту же операцию. Open API:

curl -X POST "https://api.morelogin.com/webhook/config" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "callbackUrl": "https://example.com/morelogin/webhook",
    "enabled": true
  }'

Local API:

curl -X POST "http://127.0.0.1:40000/api/webhook/config" \
  -H "Content-Type: application/json" \
  -d '{
    "callbackUrl": "https://example.com/morelogin/webhook",
    "enabled": true
  }'

Маршрут Local API обслуживает настольный клиент, поэтому нужен клиент MoreLogin v2.66.0 или новее. Эндпоинт Open API от клиента не зависит.

teamId никогда не читается из запроса. Каждый вызов настраивает команду из серверного контекста, поэтому направить события другой команды на свой эндпоинт нельзя.

URL должен быть HTTPS, длиной от 1 до 1024 символов, с именем хоста, без данных пользователя, без фрагмента и с корректным портом. Все адреса, в которые он разрешается, должны маршрутизироваться в интернете: диапазоны localhost, частные, link-local, CGNAT, метаданных облака, зарезервированные протоколом, документационные и 6to4 отклоняются с кодом 41001. Та же проверка повторяется в момент, когда доставка открывает соединение, поэтому имя хоста, которое позже разрешится в частный диапазон, приведёт к неудаче этой доставки, а не к обращению в вашу сеть.

Сохранение — это upsert по команде, поэтому отправить одно и то же тело дважды безвредно.

Отключение доставки

enabled: false сохраняет URL и прекращает создание новых событий. Уже поставленные в очередь события отменяются по одному, в тот момент, когда каждое из них должно быть авторизовано, и только пока конфигурация всё ещё отключена — поэтому при быстром повторном включении события из очереди продолжат доставляться, а не будут отброшены.

Как выглядит доставка

Каждая доставка — это POST со следующими заголовками:

ЗаголовокЗначение
Content-Typeapplication/json
User-AgentMoreLogin-Webhook/1.0
X-Webhook-Event-IdТо же, что eventId в теле
X-Webhook-Event-TypeТо же, что eventType в теле

Два заголовка X-Webhook-* намеренно дублируют поля тела, чтобы маршрутизатор или очередь могли распределять запросы по ним без разбора JSON.

{
  "eventId": "c7f3a2b10d8e4f6a9c1b2d3e4f5a6b7c",
  "eventType": "cloud_phone.power_off",
  "eventTime": "1787241600000",
  "data": {
    "teamId": "10001",
    "cloudPhoneId": "20001",
    "reason": "MONEY_SAVING"
  },
  "result": {
    "success": true,
    "code": "0",
    "message": null
  }
}
ПолеОписание
eventIdУникальный ID события. Повторы и внутренние воспроизведения того же события используют его повторно, поэтому это ключ идемпотентности
eventTypeСтабильное имя события из таблицы ниже
eventTimeКогда наступил итоговый результат, в миллисекундах эпохи Unix. Передаётся строкой
data.teamIdКоманда, которой принадлежит ресурс. Передаётся строкой
data.cloudPhoneIdОблачный телефон, к которому относится событие. Передаётся строкой
data.cloudBrowserIdПрофиль облачного браузера, к которому относится событие. Передаётся строкой
data.runIdЭкземпляр запуска облачного браузера, к которому относится событие
data.reasonТолько в cloud_phone.power_off и cloud_browser.stop
result.successЗавершилась ли операция успешно
result.code0 при успехе; иначе стабильный бизнес-код ошибки, не зависящий от языка
result.messagenull при успехе; иначе пояснение на языке команды с переходом на en-US

Все числовые идентификаторы и eventTime приходят строками JSON, как и в остальной части API — см. Общий формат ответа. Разбирайте их как строки, а не как числа, иначе потеряете точность на 64-битных ID.

События

eventTypeИтоговый результат, о котором сообщается
cloud_phone.power_onВключение удалось или не удалось
cloud_phone.power_offВыключение удалось. Неудачное выключение события не создаёт
cloud_phone.restartПерезапуск удался или не удался
cloud_phone.resetСброс удался или не удался
cloud_phone.new_machineНовое устройство одним щелчком удалось или не удалось
cloud_browser.startЗапуск облачного браузера удался или не удался
cloud_browser.stopКаждая попытка остановки облачного браузера, удачная или нет

События облачного телефона содержат teamId и cloudPhoneId. События облачного браузера содержат teamId, cloudBrowserId и runId.

Включение, перезапуск, сброс и создание нового устройства одним щелчком сообщают success: true только после того, как обратный вызов поставщика прошёл успешно и локальная синхронизация была зафиксирована. Ошибка списания средств или асинхронная проверка прокси, которая завершилась неудачей и довела до конца свою очистку, сообщают о неудачном включении и не создают дополнительно событие выключения. Отложенные задачи, например установка предустановленных приложений или удаление устаревших файлов, результат не меняют.

Если выключение прервало загрузку, эта попытка не создаёт события включения; успешное выключение позже всё равно создаст собственное cloud_phone.power_off.

Причины остановки

cloud_phone.power_off добавляет data.reason:

ЗначениеСмысл
NORMALОбычное выключение
MONEY_SAVINGРежим экономии средств
OTHERЗадолженность, истечение срока, обслуживание или другая причина

cloud_browser.stop добавляет data.reason:

ЗначениеСмысл
NORMALОбычное закрытие
MONEY_SAVINGРежим экономии средств
ENERGY_SAVINGРежим энергосбережения
BROWSER_EXITEDБраузер завершился неожидано
OTHERЛюбая другая причина

Внутренняя ошибка архивирования меняет только result и никогда не перезаписывает reason, зафиксированный в начале остановки.

Доставка, повторы и порядок

Любой 2xx считается доставкой. Любой другой статус, а также тайм-ауты соединения и чтения и сетевые ошибки считаются неудачей. Тайм-аут соединения — 5 секунд, тайм-аут чтения — 30 секунд.

Неудачная доставка повторяется не более 6 раз: через 10 секунд, 30 секунд, 60 секунд, 3 минуты, 10 минут и 30 минут. Это не более 7 запросов на одно событие. После неудачи шестого повтора событие перестаёт повторяться автоматически.

События одного облачного телефона или одного облачного браузера доставляются в порядке создания, по одному. Разные ресурсы доставляются параллельно, поэтому не полагайтесь на какой-либо порядок между ними.

Повторы одного события всегда используют те же eventId, eventTime и тело. В пограничных сетевых или восстановительных ситуациях одно и то же событие может быть доставлено несколько раз с неизменными значениями, поэтому дедуплицируйте по eventId.

Для остановок облачного браузера одна попытка остановки даёт ровно одно событие. Последующий повтор вручную, принудительное освобождение вручную или принудительное освобождение, инициированное системой, — это новая операция, которая получает новый eventId. Две попытки никогда не объединяются в одно событие.

Операции облачного телефона определяются по ID задачи поставщика, traceId или стабильной бизнес-метке времени. Когда ни одного из них нет, сервис генерирует UUID и считает текущий итоговый обратный вызов новой операцией, поэтому вы получаете новый eventId, а две разные операции не сливаются в одну.

Реализация получателя

Возвращайте 2xx сразу после того, как сохранили событие, а реальную работу выполняйте асинхронно. Тело ответа игнорируется, а медленный обработчик израсходует ваши 30 секунд на чтение и отправит событие в расписание повторов, хотя вы его уже получили.

Дедуплицируйте по eventId. Считайте повтор уже обработанным, а не воспроизводите побочные эффекты заново.

Не считайте полезную нагрузку доверенным вводом только потому, что она пришла на ваш эндпоинт. В этом выпуске обратные вызовы не подписываются, поэтому отправить запрос может любой, кто узнал ваш URL. Используйте URL, который трудно угадать, и прежде чем выполнять что-то дорогое, подтвердите состояние через API — например POST /cloudphone/info для облачного телефона или POST /cloudbrowser/page для запуска облачного браузера.

result.code использует те же бизнес-коды, что и синхронный API, поэтому неудачу можно найти в Коды ошибок. result.message — читаемая человеком строка, формулировка и язык которой могут меняться; ветвитесь по result.success и result.code, но никогда по result.message.

Машиночитаемый контракт каждого события, включая схемы и примеры по событиям, публикуется вместе со спецификацией общих ресурсов: Shared Resources Open API.