Lovely Router文档
获取密钥
浏览器智能体

Webhooks

当浏览器智能体的运行开始等待人工或结束时,接收一个 HTTPS POST 请求,请求带有签名,方便你验证它确实来自 Lovely Router。

与其轮询,不如给运行设置一个 webhook URL。运行开始等待人工以及运行结束时,Lovely Router 都会向它发送 POST 请求。每个请求都使用该运行独有的密钥签名。

为运行添加 Webhook#

启动运行时,把一个 https URL 作为 webhook 传入。响应中包含 webhook_secret。它只会在这里返回,所以请把它与运行 id 一起保存。

curl https://lovelyrouter.com/v1/browser/runs \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "goal": "Get a car insurance quote for the details below and tell me the quote reference and monthly premium. ...",
    "start_url": "https://insure.example.com/quote",
    "webhook": "https://api.example.com/hooks/browser"
  }'

响应(节选):

JSON
{
  "id": "brun_9c2e4a6b8d0f1a3c5e7b9d1f",
  "object": "browser_run",
  "status": "running",
  "webhook_secret": "4f1d0c9b8a7e6d5c4b3a29180f7e6d5c4b3a2918a7b6c5d4",
  "poll": "/v1/browser/runs/brun_9c2e4a6b8d0f1a3c5e7b9d1f?wait=30",
  "charge_usd": null,
  "reserved_usd": 0.445
}

不是 https URL 的 webhook 会返回 400,错误为 "webhook" must be an https URL。Python 和 Node.js 示例沿用运行你的第一个任务中的 API、H 和 headers 设置。

事件#

事件发送时机
run.waiting运行开始等待人工:提问、批准或移交。每次暂停发送一次。
run.finished运行以任一最终状态结束:succeeded、failed、timed_out 或 stopped。

请求内容#

每个事件都是一个 POST 请求,其 JSON 请求体包含事件名称和该时刻的运行对象:

HTTP
POST /hooks/browser HTTP/1.1
Host: api.example.com
content-type: application/json
x-lovely-event: run.waiting
x-lovely-signature: sha256=8d6f0c3b2a1e9f8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d
JSON
{
  "event": "run.waiting",
  "run": {
    "id": "brun_9c2e4a6b8d0f1a3c5e7b9d1f",
    "object": "browser_run",
    "status": "waiting_for_human",
    "goal": "Get a car insurance quote for the details below and tell me the quote reference and monthly premium. ...",
    "start_url": "https://insure.example.com/quote",
    "engine": "chromium",
    "mode": "fast",
    "model": null,
    "steps": 14,
    "max_steps": 50,
    "billable_time_s": 0,
    "time_usd": 0,
    "current_url": "https://insure.example.com/quote/review",
    "answer": null,
    "data": null,
    "error": null,
    "needs": {
      "kind": "approval",
      "prompt": "Approve: click button \"Get my quote\" on Review your details?",
      "fields": ["Full name: \"John Smith\"", "Coverage: \"Standard\""],
      "action": "Get my quote",
      "screenshot": "/v1/browser/runs/brun_9c2e4a6b8d0f1a3c5e7b9d1f/screenshot",
      "since": "2026-10-11T09:41:03.000Z",
      "reply": "/v1/browser/runs/brun_9c2e4a6b8d0f1a3c5e7b9d1f/reply"
    },
    "profile_saved": null,
    "usage": { "decision_calls": 16, "llm_calls": 3 },
    "created_at": "2026-10-11T09:40:21.000Z",
    "finished_at": null
  }
}

Webhook 中的运行对象不包含 charge_usd 或 reserved_usd:扣费会在运行结束几秒后才结算。要获取扣费金额,请用 GET /v1/browser/runs/{id} 获取该运行。

验证签名#

x-lovely-signature 的值是 sha256= 后接原始请求体的 HMAC-SHA256 十六进制值,密钥为该运行的 webhook_secret(作为 UTF-8 字符串)。验证请求的步骤:

  1. 在解析之前读取原始请求体。重新序列化后的 JSON 无法匹配。
  2. 在请求体中找到运行 id(run.id),查出你为它保存的 webhook_secret。拒绝你不认识的 id。
  3. 计算 HMAC,并用常数时间比较将其与请求头进行比对。
import hashlib
import hmac
import json

from flask import Flask, abort, request

app = Flask(__name__)

@app.post("/hooks/browser")
def browser_hook():
    body = request.get_data()  # raw bytes
    event = json.loads(body)
    secret = load_secret(event["run"]["id"])  # what you stored at run creation
    if secret is None:
        abort(404)
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("x-lovely-signature", "")):
        abort(401)

    run = event["run"]
    if event["event"] == "run.waiting":
        notify_person(run["id"], run["needs"])  # e.g. send needs.prompt to the right person
    elif event["event"] == "run.finished":
        save_result(run["id"], run["status"], run["answer"], run["data"])
    return "", 204

投递#

  • 每个事件只发送一次。没有重试,非 2xx 的响应也不会重发。
  • Lovely Router 最多等待你的响应 5 秒。请立即返回 2xx,把耗时的工作(例如通知某个人)放到后台处理。
  • 同一运行的事件按顺序发送,但请把 Webhook 当作一个信号,而不是唯一可信的数据来源。如果错过了某个事件,运行仍然存在:用 GET /v1/browser/runs/{id} 获取即可。

对于不能丢失的任务,请准备一个兜底方案:定期检查一段时间后仍处于 running 或 waiting_for_human 的运行。GET /v1/browser/runs 会列出你最近的 50 次运行。

通过 Webhook 回复#

run.waiting 事件包含询问对方所需的全部信息:needs.prompt、批准时的 needs.fields、移交时的 needs.live_view_url,以及 needs.screenshot 路径。对方回答后,向 needs.reply 发送 POST 请求:

cURL
curl https://lovelyrouter.com/v1/browser/runs/brun_9c2e4a6b8d0f1a3c5e7b9d1f/reply \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"approve": true}'

运行会继续,并在结束时发送 run.finished。人工介入介绍了所有类型的回复。