Webhook 同事件
API 版本 2026-10-affable-towhee
Lingara 會將你帳戶啲課程計劃同用量發生嘅事記錄做事件,亦都會接收你嘅遊戲或者應用程式送嚟嘅事件。每個事件,無論點樣傳送,都用同一種封包,事件目錄列晒全部事件。
封包
每個事件都帶住六個欄位。id 以 lgr_evt_ 開頭,獨一無二,係用嚟去除重複嘅鍵。type 講明係咩事件。created_at 係事件發生嘅時間。api_version 係 data 所用嘅版本:即係你用戶端固定咗嘅版本;喺事件源同事件串流度,就係你個請求喺 Lingara-Version 指定嘅版本。subject 以 lgr_sub_ 開頭,講明個事件係關於邊個:對你嘅用戶端嚟講佢唔會變,但係每個用戶端都唔同,而且永遠唔會係電郵地址、名或者帳戶 ID。data 好細,只係講明資源而唔會複製資源:要用所需嘅權限範圍去攞資源。
有兩種類型要各講一句。lesson_plan.ready 對同一份計劃可能會到兩次,第一次 data.status 係 partial,之後係 complete:你可以按第一次嚟攞一份用得嘅計劃,或者等 complete 先攞齊每一組內容。usage.threshold_reached 只會為按用量收費嘅帳戶同用戶端傳送,而一次過咗幾個門檻嘅話只會報告過咗嘅最高嗰個,所以唔好預咗每個門檻都有一個事件。
一份記錄,三種收法
Webhook 適合有公開 HTTPS 端點嘅伺服器。事件源同事件串流適合冇公開端點嘅程式,例如喺玩家部機運行嘅遊戲。每種方式嘅封包都一樣,所以程式可以先用事件源,之後先轉用 Webhook,都唔使改讀事件嘅方法。事件路由係俾原生程式用嘅。喺瀏覽器入面運行嘅遊戲暫時仲調用唔到佢哋,因為 /v1/ 唔會回應跨來源預檢請求。
邊個會收到事件
用戶端要有 events:read 同埋該事件類型本身嘅權限範圍(事件目錄有列),而且事件係關於該用戶端嘅擁有者,佢先會收到個事件。喺事件源同事件串流度,存取權杖嘅權限範圍會再收窄範圍,而 types 會將佢收窄到你指名嘅類型。webhook.test 只會送去佢指定嘅端點,永遠唔會入事件源,亦都唔可以訂閱。app.installed 同 app.uninstalled 只會送俾個應用程式自己嘅用戶端,永遠唔會送俾同一個帳戶嘅其他用戶端。
註冊端點
喺 Lingara 網頁應用程式嘅 Webhook 頁面(網址係 app.getlingara.com/admin/webhooks)註冊端點,要先揀用戶端。端點 URL 一定要喺連接埠 443 用 https,而且主機只可以解析到公開地址。揀要傳送嘅事件:只會列出個用戶端權限範圍容許嘅類型。URL 同要傳送嘅事件之後都改唔到:請加一個新端點,再刪除舊嗰個。簽署密鑰以 lgr_whsec_ 開頭,只會顯示一次。
驗證傳送
每次傳送都係一個 POST,跟 Standard Webhooks 規格,帶住三個標頭:webhook-id(事件嘅 id)、webhook-timestamp 同 webhook-signature。HMAC 金鑰係簽署密鑰入面 lgr_whsec_ 後面嗰部分經 base64 解碼嘅結果,永遠唔係成個密鑰字串。請喺解析內容之前,用佢嘅原始位元組驗證,好似下面噉。時間戳記同而家相差超過五分鐘嘅傳送要拒絕,呢個係 Standard Webhooks 程式庫嘅預設值:噉樣就可以防止被截取嘅傳送俾人重播。
signed = webhook-id + "." + webhook-timestamp + "." + raw request body
key = base64_decode(the secret after its prefix)
expected = "v1," + base64(hmac_sha256(key, signed))
accept if |now - webhook-timestamp| <= 5 minutes
and some entry of webhook-signature (space-separated) equals expected
(compare in constant time)Standard Webhooks 為大部分語言發佈咗驗證程式庫。佢哋預期密鑰寫成 whsec_ 加 base64,或者淨係 base64,所以要將 Lingara 密鑰入面 lgr_whsec_ 後面嗰部分交俾佢哋。Lingara 自己嘅程式庫接受成個密鑰。
快啲回應,預咗會重試
請喺 10 秒內用任何 2xx 回應,之後先做嘢。其他任何結果,包括逾時或者 3xx(唔會跟重新導向),都會用越嚟越長嘅間隔重試大約一日。對自動傳送回應 410 會即刻停用個端點;對測試或者重新傳送回應 410 就唔會。連續五日傳送失敗之後,端點都會被停用。無論係邊種情況,都會電郵通知佢嘅擁有者。Lingara 嗰邊嘅故障永遠唔會計入停用端點。喺 Webhook 頁面,你可以傳送測試,或者重新傳送最近 30 日內嘅任何一次傳送。每次都只試一次,永遠唔會重試,就算端點已經停用都會照送。
傳送最少一次,而且唔保證次序。同一個事件可能會到兩次,重試亦可能喺之後嘅事件後面先到。webhook-id 喺每次重試都一樣,喺 30 日內嘅重新傳送都一樣。請將你處理過嘅每個 id 記錄 30 日,重複嘅就唔使理。如果次序重要,就按 created_at 排。
輪換簽署密鑰
一個端點可以同時有兩個簽署密鑰。兩個都有效嗰陣,webhook-signature 會帶住兩個 v1, 項目,接受任何一個嘅接收端都可以照常運作。請將新密鑰加落你嘅伺服器並部署,然後先撤銷舊嗰個。
事件源
用用戶端嘅存取權杖調用 GET /v1/events,會傳回 items(封包)、next_cursor 同 has_more。權杖需要 events:read 同埋你想收嘅每種類型嘅權限範圍:淨係有 events:read 嘅話,事件源係空嘅。佢由而家開始。傳入 start=oldest 就會攞到大約最近 30 日嘅事件。佢唔需要公開端點,亦唔需要簽署密鑰:存取權杖已經證明咗邊個喺度問。下面嘅換取要求咗課程計劃事件需要嘅兩個權限範圍。
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=events:read lesson_plans:read" | jq -r '.access_token // error(.error)')"curl "https://api.getlingara.com/v1/events" \
-H "Authorization: Bearer $LINGARA_TOKEN"next_cursor 一定會有:請儲存佢,再當做 cursor 傳返。佢係不透明嘅。has_more 係 true 即係要即刻再調用,係 false 即係你已經追上:遲啲再輪詢,或者開事件串流。超過 30 日嘅游標會被拒絕,傳回 410 同 cursor_expired。冇游標嘅話,事件源由而家開始,中間嘅事件會被跳過。想攞返佢哋,就用 start=oldest 調用,佢會追溯到事件保留嘅最早時間,再跳過你已經處理過嘅 id 值。
事件串流
GET /v1/events/stream 用伺服器傳送事件嘅形式傳送同樣嘅事件。每個 event 框嘅 data 係一個封包,每個框嘅 id: 係一個游標,同 next_cursor 係同一種值,所以你可以喺事件源同事件串流之間切換而唔會漏。連線斷咗、收到 done 框(事件串流會不時自己結束)或者 error 框之後,請將 Last-Event-ID 設做你收到嘅最後一個 id:,再重新連線。大部分 SSE 用戶端都會幫你做呢一步,Lingara 程式庫嘅 tailEvents 都係(入面嘅 streamEvents 只係單一連線)。佢係游標,唔係事件嘅 id。事件串流會傳送心跳,所以一條冇聲氣嘅連線就係斷咗嘅連線。
curl -N "https://api.getlingara.com/v1/events/stream" \
-H "Authorization: Bearer $LINGARA_TOKEN"向 Lingara 傳送事件
用 events:write 調用 POST /v1/events,以 {type, data} 嘅形式向 Lingara 傳送事件:world.context_changed(一個 scene、source_lang、target_lang、level,同埋可以加一個帶 name 同 persona 嘅 npc,仲有 tags)或者 world.practice_requested(一個 topic,同埋同樣嘅語言同程度)。Idempotency-Key 係必須嘅:最多 255 個可見 ASCII 字元,例如 UUID。冇佢嘅話,回應係 400 同 idempotency_key_required。每個事件設定一次,重試嗰陣傳送同一個鍵。一個鍵就係一個事件:一日之內,用同一個鍵嘅第二個請求會攞到第一次嘅回應(以 JSON 嚟講相等,唔係逐個位元組一樣),就算內容唔同都係;之後佢會攞到同一個事件,好似下面噉。傳入嘅事件唔會簽署:你嘅存取權杖就係證明。請描述世界,唔好描述玩家:scene、npc、topic 或者 tags 入面唔好有名或者傾偈內容。
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=events:write lesson_plans:write" | jq -r '.access_token // error(.error)')"curl -X POST "https://api.getlingara.com/v1/events" \
-H "Authorization: Bearer $LINGARA_TOKEN" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"world.context_changed","data":{"scene":"A night market in Taipei, just after rain","npc":{"name":"Auntie Lin","persona":"a street-food vendor who likes to haggle"},"source_lang":"en","target_lang":"zh","level":3,"tags":["market","food","chapter-2"],"generate":true}}'每個文字欄位都係一行可見字元,去咗頭尾空白之後計:scene 同 topic 最多 160,npc.name 最多 32,npc.persona 最多 120。換行、定位字元同其他控制字元會被拒絕,隱形字元同格式字元都一樣:方向覆寫字元、零寬度字元(某啲文字同表情符號需要嘅連接字元除外)、標籤區塊同私人使用區字元。tags 最多有 8 個細階機器記號,每個最多 24 個字元,而且永遠唔會入到課程計劃。level 係 1 到 9,兩種語言一定要唔同。超出呢啲限制嘅請求會被拒絕,傳回 400,而且唔會記錄任何事件。
用 "generate": true(world.practice_requested 嘅預設值)嗰陣,權杖仲需要 lesson_plans:write。冇佢嘅話,請求會被拒絕,傳回 403,唔會記錄任何事件。有佢嘅話,Lingara 會開始一份課程計劃,檢查同收費都同直接建立一份一樣,而 202 回應入面嘅 reaction 會講發生咗咩事。如果係 started 而 plan_status 係 generating,之後喺你用緊嘅每種方式都會收到一個 lesson_plan.ready 或者 lesson_plan.failed,佢嘅 data.plan_id 就係回應入面嘅 plan_id。如果係 partial 或者 complete,個計劃係由計劃庫嚟嘅,而家就讀得,而且唔保證會有事件:可能仍然會有一個到,所以只有 generating 先值得等。如果係 refused 或者 failed,個事件仍然成立。佢唔會用同一個鍵重試,所以想再試就傳送一個新事件。
一日之內嘅重試會攞返第一次嘅回應。之後嘅重試會由儲存咗嘅事件重新砌出嚟,佢保留咗事件開始嘅計劃,但唔會保留反應點解被拒絕。所以一個遲咗嘅重試可能會用 reaction failed 同 internal 回應:意思係第一次嘅結果冇被記錄,唔係話冇計劃。如果你保留咗 plan_id,就用佢讀個計劃,或者傳送一個新事件。
Tidewater Games:冇伺服器嘅遊戲
Tidewater Games 係一間虛構嘅工作室,用 Godot 整緊一隻俾玩家探索夜市嘅遊戲。佢哋嘅開發者喺自己部機、用自己嘅用戶端運行呢隻遊戲。
玩家行入一檔麵檔。遊戲用 "generate": true 發出 world.context_changed(即係上面「向 Lingara 傳送事件」嗰節嘅指令),並保留回應入面嘅 plan_id。
如果 plan_status 係 generating,遊戲就讀事件串流,或者輪詢事件源,直到帶住嗰個 plan_id 嘅 lesson_plan.ready 到為止。然後佢用 lesson_plans:read 讀個計劃,好似下面噉。如果個計劃已經係 complete,佢就即刻讀。
export LINGARA_TOKEN="$(curl -sS --fail-with-body -X POST "https://api.getlingara.com/oauth/token" \
-u "$LINGARA_CLIENT_ID:$LINGARA_CLIENT_SECRET" \
-d "grant_type=client_credentials" \
--data-urlencode "scope=lesson_plans:read" | jq -r '.access_token // error(.error)')"curl "https://api.getlingara.com/v1/lesson-plans/$ID" \
-H "Authorization: Bearer $LINGARA_TOKEN"後來工作室加咗一部有 HTTPS 端點嘅細伺服器,並為 lesson_plan.ready 註冊咗佢。同一個事件會帶住同一個 id 送到嗰度,而遊戲讀事件嘅程式碼唔使改。
用戶端密鑰永遠唔可以跟遊戲版本一齊發佈,因為玩家裝置上面乜嘢都讀得到。喺 Lingara 支援代表玩家登入之前,喺玩家部機運行嘅遊戲要同自己嘅伺服器溝通,只有開發者自己嗰份先直接同 Lingara 溝通。
事件點收費
同每次 /v1/ 調用一樣,你用戶端嘅用量會計每個被接受嘅傳入事件、每次事件源調用同每次開啟嘅事件串流。用 "generate": true 傳送嘅事件亦都會計做一份課程計劃。每次 Webhook 傳送按每個事件每個端點計一次,喺佢第一次 2xx 嗰陣計,無論嗰次係第幾次嘗試。之後永遠唔會再計,測試亦永遠唔計。GET /v1/usage 會顯示今個月到而家嘅用量。
如果譯本同英文參考文件有出入,以英文參考文件為準。