Lovely Routerドキュメント
キーを取得
ブラウザエージェント

Webhook

ブラウザエージェントの実行が人の対応待ちになったときや終了したときに、HTTPS の POST を受け取れます。署名付きなので、Lovely Router から送られたことを検証できます。

ポーリングする代わりに、実行に webhook URL を指定できます。Lovely Router は、実行が人の対応待ちになったときと終了したときに、その URL へ 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実行が人の対応待ち(質問、承認、引き渡し)になったとき。一時停止ごとに 1 回送信されます。
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 を 16 進数で表したものです。鍵には実行の 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

配信#

  • 各イベントは 1 回だけ送信されます。再試行はなく、2xx 以外のレスポンスが返っても再送されません。
  • Lovely Router はレスポンスを最大 5 秒待ちます。すぐに 2xx を返し、人への通知など時間のかかる処理はバックグラウンドで行ってください。
  • 1 つの実行のイベントは順番どおりに送られますが、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 を送信します。すべての種類の返信については人による確認を参照してください。