MoreLogin может отправлять JSON-событие методом POST на ваш HTTPS-эндпоинт сразу после того, как операция с облачным телефоном или облачным браузером достигла итогового результата, поэтому опрашивать результат больше не нужно. На команду хранится один URL обратного вызова, и Local API с Open API пишут в одну и ту же запись.
Оба контура предоставляют одну и ту же операцию. 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-Type | application/json |
User-Agent | MoreLogin-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.code | 0 при успехе; иначе стабильный бизнес-код ошибки, не зависящий от языка |
result.message | null при успехе; иначе пояснение на языке команды с переходом на 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.