Lingara Lingara Tài liệu Cẩm nang API Thư viện Ứng dụng Tạo Ứng dụng web
Ngôn ngữ: Tiếng Việt

Webhook và sự kiện

Phiên bản API 2026-10-affable-towhee

Lingara ghi lại những gì xảy ra với giáo án và mức sử dụng của tài khoản bạn dưới dạng sự kiện, và nhận sự kiện từ trò chơi hoặc ứng dụng của bạn. Mọi sự kiện, dù đi theo đường nào, đều có cùng một phong bì, và danh mục sự kiện liệt kê tất cả.

Phong bì

Mỗi sự kiện mang sáu trường. id bắt đầu bằng lgr_evt_, là duy nhất, và là khóa để loại trùng lặp. type cho biết loại sự kiện. created_at là thời điểm sự kiện xảy ra. api_version là phiên bản mà data được định hình theo: phiên bản được ghim của ứng dụng khách, hoặc, trên nguồn cấp và luồng, phiên bản mà yêu cầu của bạn nêu trong Lingara-Version. subject bắt đầu bằng lgr_sub_ và cho biết sự kiện nói về ai: giá trị này ổn định với ứng dụng khách của bạn nhưng khác nhau giữa các ứng dụng khách, và không bao giờ là email, tên hay ID tài khoản. data nhỏ và nêu tên tài nguyên thay vì sao chép chúng: hãy lấy tài nguyên bằng phạm vi mà nó cần.

Có hai loại cần giải thích thêm một câu. lesson_plan.ready có thể đến hai lần cho một giáo án, lần đầu với data.status là partial rồi complete: hãy xử lý lần đầu nếu cần một giáo án dùng được, hoặc chờ complete để có đủ mọi bộ. usage.threshold_reached chỉ được gửi cho tài khoản và ứng dụng khách trả theo mức dùng, và khi vượt qua nhiều ngưỡng cùng lúc thì chỉ ngưỡng cao nhất bị vượt được báo, nên đừng mong mỗi ngưỡng một sự kiện.

Một nhật ký, ba cách nhận

Webhook phù hợp với máy chủ có điểm cuối HTTPS công khai. Nguồn cấp và luồng phù hợp với chương trình không có điểm cuối như vậy, chẳng hạn trò chơi trên máy của người chơi. Phong bì giống nhau ở mọi đường, nên một chương trình có thể bắt đầu với nguồn cấp và chuyển sang webhook sau này mà không đổi cách đọc sự kiện. Các tuyến sự kiện phục vụ chương trình gốc. Trò chơi chạy trong trình duyệt chưa thể gọi chúng, vì /v1/ không trả lời yêu cầu preflight khác nguồn gốc.

Ai nhận được sự kiện

Một ứng dụng khách nhận được sự kiện khi nó có events:read và phạm vi riêng của loại sự kiện đó (được nêu trong danh mục), và khi sự kiện nói về chủ sở hữu của ứng dụng khách. Trên nguồn cấp và luồng, các phạm vi của token truy cập thu hẹp thêm, và types thu hẹp lại chỉ còn các loại bạn nêu. webhook.test chỉ đến điểm cuối mà nó được gửi tới, không bao giờ đến nguồn cấp, và không thể đăng ký nhận. app.installed và app.uninstalled chỉ đến ứng dụng khách của chính ứng dụng đó, không bao giờ đến ứng dụng khách khác của cùng tài khoản.

Đăng ký một điểm cuối

Đăng ký điểm cuối trên trang Webhook của ứng dụng web Lingara, tại app.getlingara.com/admin/webhooks, sau khi chọn ứng dụng khách trước. URL phải dùng https trên cổng 443, và tên máy chủ chỉ được phân giải tới địa chỉ công khai. Chọn Sự kiện cần gửi: chỉ những loại mà phạm vi của ứng dụng khách cho phép mới được hiển thị. Không thể sửa URL và các sự kiện về sau: hãy thêm điểm cuối mới và xóa điểm cuối cũ. Khóa bí mật ký bắt đầu bằng lgr_whsec_ và chỉ hiển thị một lần.

Xác minh một lượt gửi

Mỗi lượt gửi là một POST với ba header, theo đặc tả Standard Webhooks: webhook-id (id của sự kiện), webhook-timestamp và webhook-signature. Khóa HMAC là phần sau lgr_whsec_ của khóa bí mật, được giải mã base64, không bao giờ là khóa bí mật dưới dạng chuỗi. Hãy xác minh trước khi phân tích phần thân, trên các byte thô của nó, như bên dưới. Từ chối lượt gửi có dấu thời gian lệch quá năm phút so với hiện tại, mặc định của các thư viện Standard Webhooks: điều đó ngăn một lượt gửi bị bắt được bị phát lại.

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 phát hành bộ xác minh cho hầu hết các ngôn ngữ. Chúng mong khóa bí mật được viết dưới dạng whsec_ theo sau là base64, hoặc chỉ base64, nên hãy truyền cho chúng phần khóa bí mật Lingara sau lgr_whsec_. Các thư viện của chính Lingara chấp nhận toàn bộ khóa bí mật.

Trả lời nhanh, sẵn sàng cho việc thử lại

Trả lời bằng bất kỳ 2xx nào trong vòng 10 giây, rồi xử lý công việc sau đó. Mọi phản hồi khác, kể cả hết thời gian chờ hay 3xx (chuyển hướng không được theo), sẽ được thử lại với khoảng cách tăng dần trong khoảng một ngày. Phản hồi 410 cho một lượt gửi tự động sẽ tắt điểm cuối ngay lập tức; 410 cho một lần gửi thử hoặc gửi lại thì không. Sau năm ngày gửi thất bại, điểm cuối cũng bị tắt. Dù theo cách nào, chủ sở hữu đều nhận được email. Sự cố từ phía Lingara không bao giờ được tính vào việc tắt điểm cuối. Từ trang Webhook, bạn có thể Gửi thử, hoặc Gửi lại bất kỳ lượt gửi nào trong 30 ngày qua. Mỗi thao tác là một lần thử, không bao giờ được thử lại, và vẫn được gửi kể cả đến điểm cuối đã tắt.

Lượt gửi diễn ra ít nhất một lần và không theo thứ tự. Cùng một sự kiện có thể đến hai lần, và một lần thử lại có thể đến sau một sự kiện mới hơn. webhook-id giống nhau ở mọi lần thử lại, và ở lần gửi lại trong vòng 30 ngày sau đó. Hãy ghi lại mỗi id bạn đã xử lý trong 30 ngày, và bỏ qua bản lặp. Sắp xếp theo created_at nếu thứ tự quan trọng.

Xoay vòng khóa bí mật ký

Một điểm cuối có thể giữ hai khóa bí mật ký cùng lúc. Khi cả hai còn hiệu lực, webhook-signature mang hai mục v1,, và bên nhận chấp nhận một trong hai sẽ tiếp tục hoạt động. Thêm khóa bí mật mới vào máy chủ của bạn, triển khai nó, rồi thu hồi khóa cũ.

Nguồn cấp

GET /v1/events với token truy cập của một ứng dụng khách trả về items (các phong bì), next_cursor và has_more. Token cần events:read và phạm vi của từng loại bạn muốn nhận: chỉ với events:read, nguồn cấp sẽ trống. Nguồn cấp bắt đầu từ bây giờ. Truyền start=oldest để lấy các sự kiện trong khoảng 30 ngày qua. Nó không cần điểm cuối công khai hay khóa bí mật ký: token truy cập chứng minh ai đang hỏi. Lần đổi bên dưới yêu cầu cả hai phạm vi mà các sự kiện giáo án cần.

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 luôn có mặt: hãy lưu nó và truyền lại dưới dạng cursor. Giá trị này không trong suốt. has_more là true nghĩa là gọi lại ngay, còn false nghĩa là bạn đã bắt kịp: thăm dò lại sau, hoặc mở luồng. Con trỏ cũ hơn 30 ngày bị từ chối với 410 và cursor_expired. Không có con trỏ, nguồn cấp bắt đầu từ bây giờ, và các sự kiện ở giữa bị bỏ qua. Để lấy lại chúng, hãy gọi với start=oldest, lùi xa nhất tới mức sự kiện còn được lưu, và bỏ qua các giá trị id bạn đã xử lý.

Luồng

GET /v1/events/stream mang cùng các sự kiện dưới dạng server-sent events. data của mỗi khung event là một phong bì, và id: của mỗi khung là một con trỏ, cùng loại token với next_cursor, nên bạn có thể chuyển giữa nguồn cấp và luồng mà không bị hở. Sau khi mất kết nối, sau một khung done (luồng thỉnh thoảng tự kết thúc) hoặc một khung error, hãy kết nối lại với Last-Event-ID đặt thành id: cuối cùng bạn nhận được. Hầu hết các client SSE tự làm việc này cho bạn, tailEvents trong các thư viện của Lingara cũng vậy (streamEvents ở đó là một kết nối đơn). Đó là con trỏ, không phải id của sự kiện. Luồng gửi nhịp tim, nên một kết nối im lặng là một kết nối đã chết.

curl -N "https://api.getlingara.com/v1/events/stream" \
  -H "Authorization: Bearer $LINGARA_TOKEN"

Gửi sự kiện tới Lingara

POST /v1/events với events:write gửi một sự kiện tới Lingara dưới dạng {type, data}: world.context_changed (một scene, source_lang, target_lang, level, và tùy chọn một npc có name và persona, cùng tags) hoặc world.practice_requested (một topic cùng các ngôn ngữ và cấp độ như trên). Idempotency-Key là bắt buộc: tối đa 255 ký tự ASCII hiển thị được, chẳng hạn một UUID. Thiếu nó, phản hồi là 400 và idempotency_key_required. Đặt nó một lần cho mỗi sự kiện, và gửi cùng khóa khi thử lại. Một khóa là một sự kiện: trong vòng một ngày, yêu cầu thứ hai với cùng khóa nhận phản hồi đầu tiên (bằng nhau dưới dạng JSON, không phải từng byte), kể cả khi phần thân khác, và sau đó nó nhận cùng sự kiện, như bên dưới. Sự kiện gửi vào không được ký: token truy cập của bạn là bằng chứng. Hãy mô tả thế giới, không bao giờ mô tả người chơi: không có tên hay đoạn chat trong scene, npc, topic hoặc 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}}'

Mỗi trường văn bản là một dòng gồm các ký tự hiển thị được, đếm sau khi cắt khoảng trắng: scene và topic tối đa 160, npc.name tối đa 32, và npc.persona tối đa 120. Ngắt dòng, tab và các ký tự điều khiển khác bị từ chối, các ký tự vô hình và ký tự định dạng cũng vậy: ký tự ghi đè hướng, ký tự độ rộng bằng không trừ các ký tự nối mà một số hệ chữ và emoji cần, khối thẻ và ký tự dùng riêng. tags chứa tối đa 8 token máy viết thường, mỗi token tối đa 24 ký tự, và không bao giờ đi vào giáo án. level từ 1 đến 9, và hai ngôn ngữ phải khác nhau. Yêu cầu vượt ngoài các giới hạn này bị từ chối với 400 và không ghi sự kiện nào.

Với "generate": true (mặc định cho world.practice_requested), token cũng cần lesson_plans:write. Thiếu nó, yêu cầu bị từ chối với 403 và không ghi sự kiện nào. Có nó, Lingara bắt đầu một giáo án, với cùng các bước kiểm tra và cùng mức phí như khi tạo trực tiếp, và reaction của phản hồi 202 cho biết điều gì đã xảy ra. Với started và plan_status là generating, một lesson_plan.ready hoặc lesson_plan.failed có data.plan_id là plan_id của phản hồi sẽ theo sau, trên mọi đường bạn dùng. Với partial hoặc complete, giáo án đến từ thư viện và có thể đọc ngay, và không có sự kiện nào được hứa hẹn: vẫn có thể có một sự kiện đến, nên chỉ generating là đáng chờ. Với refused hoặc failed, sự kiện vẫn có hiệu lực. Nó không được thử lại dưới cùng khóa, nên hãy gửi một sự kiện mới để thử lại.

Lần thử lại trong vòng một ngày nhận lại phản hồi đầu tiên. Lần thử lại sau đó được dựng lại từ sự kiện đã lưu, vốn giữ giáo án mà nó đã bắt đầu nhưng không giữ lý do một phản ứng bị từ chối. Vì vậy, một lần thử lại muộn có thể trả về reaction failed và internal: điều đó có nghĩa là kết quả đầu tiên không được ghi lại, chứ không phải không có giáo án nào. Hãy đọc giáo án bằng plan_id nếu bạn đã giữ nó, hoặc gửi một sự kiện mới.

Tidewater Games: trò chơi không có máy chủ

Tidewater Games, một studio hư cấu, làm một trò chơi Godot trong đó người chơi khám phá một khu chợ đêm. Nhà phát triển của studio chạy trò chơi trên máy của chính mình, với ứng dụng khách của chính mình.

Người chơi bước vào một quầy mì. Trò chơi gửi world.context_changed với "generate": true, đúng lệnh trong mục “Gửi sự kiện tới Lingara” ở trên, và giữ plan_id từ phản hồi.

Nếu plan_status là generating, trò chơi đọc luồng, hoặc thăm dò nguồn cấp, cho đến khi một lesson_plan.ready với plan_id đó đến. Sau đó nó đọc giáo án bằng lesson_plans:read, như bên dưới. Nếu giáo án đã complete, nó đọc ngay.

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"

Sau này studio thêm một máy chủ nhỏ có điểm cuối HTTPS và đăng ký nó cho lesson_plan.ready. Cùng sự kiện đó đến đây, với cùng id, và mã đọc sự kiện của trò chơi không thay đổi.

Khóa bí mật của ứng dụng khách không bao giờ được nằm trong bản dựng trò chơi, vì mọi thứ trên thiết bị của người chơi đều có thể bị đọc. Cho đến khi Lingara hỗ trợ đăng nhập thay mặt người chơi, trò chơi trên máy của người chơi sẽ nói chuyện với máy chủ riêng của studio, và chỉ bản của chính nhà phát triển mới nói chuyện trực tiếp với Lingara.

Chi phí của sự kiện

Mức sử dụng của ứng dụng khách tính mỗi sự kiện gửi vào được chấp nhận, mỗi lệnh gọi nguồn cấp và mỗi luồng được mở, như cách nó tính mọi lệnh gọi /v1/. Sự kiện gửi với "generate": true cũng được tính là một giáo án. Mỗi lượt gửi webhook được tính một lần cho mỗi sự kiện trên mỗi điểm cuối, tại 2xx đầu tiên, bất kể đó là lần thử thứ mấy. Nó không bao giờ được tính lại, và lần gửi thử không bao giờ được tính. GET /v1/usage hiển thị mức dùng của tháng tính đến nay.

Nếu bản dịch và tài liệu tham chiếu tiếng Anh khác nhau, tài liệu tham chiếu tiếng Anh là bản đúng.