跳转到内容

Webhook

云手机或云浏览器的操作一旦得到最终结果,MoreLogin 就会向你自己的 HTTPS 地址 POST 一个 JSON 事件,你不必再轮询结果。每个团队只保存一个回调地址,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 不会从请求中读取。每次调用配置的都是服务端上下文所属的团队,因此你无法把其他团队的事件指向自己的地址。

地址必须是 HTTPS、1 到 1024 个字符,带主机名,不含用户信息与片段,端口合法。它解析到的每个地址都必须是公网可路由的:localhost、内网、链路本地、CGNAT、云元数据、协议保留、文档示例与 6to4 等范围会被 41001 拒绝。实际投递建连时会再校验一次,因此某个域名若之后解析到内网范围,只会导致该次投递失败,而不会打到你的内网。

保存是按团队的 upsert,因此同样的请求体发两次没有副作用。

关闭投递

enabled: false 会保留已存的地址并停止创建新事件。已排队的事件会被逐条取消,取消发生在每个事件下次被授权的时刻,且仅当配置那时仍处于停用状态——所以如果你很快重新启用,排队中的事件会继续投递,而不会被丢弃。

一次投递长什么样

每次投递都是带以下请求头的 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_offcloud_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云浏览器每一次关闭尝试,无论成功与否

云手机事件携带 teamIdcloudPhoneId。云浏览器事件携带 teamIdcloudBrowserIdrunId

开机、重启、重置与一键新机只有在供应商回调成功且本地同步已提交后才报告 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 次请求。第 6 次重试仍失败后,该事件不再自动重试。

同一台云手机、或同一个云浏览器的事件,按创建顺序逐个投递。不同资源之间并行投递,因此不要假设它们之间有任何先后顺序。

同一事件的重试始终复用相同的 eventIdeventTime 和请求正文。在网络或服务恢复的极端场景下,同一事件可能被投递多次且这些值保持不变,因此请eventId 去重

对云浏览器关闭来说,一次关闭尝试恰好产生一个事件。之后的人工重试、人工强制释放,或系统发起的强制释放都属于新操作,会得到新的 eventId。两次尝试绝不会被合并成一个事件。

云手机操作通过供应商任务 ID、traceId 或稳定的业务时间来识别。三者都不存在时,服务会生成一个 UUID 并把当前的最终回调视为一次新操作,因此你会收到新的 eventId,而不会让两次不同的操作被合并成一个。

编写接收端

存下事件后就尽快返回 2xx,真正的处理放到异步。响应正文会被忽略,而处理缓慢会耗掉你 30 秒的读取预算,即使你已经收到了事件,它也会被推入重试队列。

eventId 去重。遇到重复请直接视为已处理,而不要重复执行副作用。

不要因为请求打到了你的地址就把正文当作可信输入。本期不对回调签名,因此任何知道你地址的人都能向它发送请求。请使用难以猜测的地址,并且在执行代价较大的动作之前,先通过 API 确认状态——例如云手机用 POST /cloudphone/info,云浏览器运行实例用 POST /cloudbrowser/page

result.code 使用与同步接口相同的业务错误码,因此失败可以在 错误码 中查到。result.message 是给人看的字符串,措辞与语言都可能变化;请按 result.successresult.code 分支,绝不要按 result.message 分支。

每个事件的机器可读契约,包括 schema 与逐事件示例,随共享资源规格一起发布,见 Shared Resources Open API