Webhook'lar ve olaylar
API sürümü 2026-10-affable-towhee
Lingara, hesabınızın ders planlarına ve kullanımına olanları olay olarak kaydeder ve oyununuzdan veya uygulamanızdan olay kabul eder. Hangi yoldan iletilirse iletilsin her olayın zarfı aynıdır ve olay kataloğu hepsini listeler.
Zarf
Her olay altı alan taşır. id, lgr_evt_ ile başlar, benzersizdir ve yinelenenleri ayıklamak için kullanılacak anahtardır. type olayı adlandırır. created_at olayın ne zaman gerçekleştiğidir. api_version, data alanının biçimlendirildiği sürümdür: istemcinizin sabitlendiği sürüm ya da beslemede ve akışta isteğinizin Lingara-Version içinde belirttiği sürüm. subject, lgr_sub_ ile başlar ve olayın kimi ilgilendirdiğini söyler: istemciniz için sabittir ama her istemci için farklıdır ve asla bir e-posta, ad ya da hesap kimliği değildir. data küçüktür ve kaynakları kopyalamak yerine adlandırır: bir kaynağı, gerektirdiği kapsamla getirin.
İki tür birer cümleyi hak eder. lesson_plan.ready bir plan için iki kez gelebilir, önce data.status partial ile, sonra complete ile: kullanılabilir bir plan için ilkine göre hareket edin ya da her sete sahip olmak için complete gelmesini bekleyin. usage.threshold_reached yalnızca kullanım başına ücretlendirilen hesaplar ve istemciler için gönderilir ve birkaç eşiği birden aşan bir sıçrama yalnızca aşılan en yüksek eşiği bildirir; bu yüzden eşik başına bir olay beklemeyin.
Tek kayıt, onu duymanın üç yolu
Webhook'lar, herkese açık bir HTTPS uç noktası olan bir sunucuya uygundur. Besleme ve akış, böyle bir uç noktası olmayan bir programa, örneğin bir oyuncunun makinesindeki bir oyuna uygundur. Zarf her yolda aynıdır; bu yüzden bir program beslemeyle başlayıp daha sonra bir olayı okuma biçimini değiştirmeden webhook'lara geçebilir. Olay yolları yerel programlara yanıt verir. Tarayıcıda çalışan bir oyun bunları henüz çağıramaz, çünkü /v1/ hiçbir çapraz kaynak ön denetimine (preflight) yanıt vermez.
Bir olayı kim duyar
Bir istemci, events:read kapsamına ve katalogda listelenen olay türünün kendi kapsamına sahip olduğunda ve olay istemcinin sahibiyle ilgili olduğunda o olayı duyar. Beslemede ve akışta erişim token'ının kapsamları bunu daha da daraltır, types ise adını verdiğiniz türlerle sınırlar. webhook.test yalnızca gönderildiği uç noktaya gider, asla beslemeye gitmez ve ona abone olunamaz. app.installed ve app.uninstalled yalnızca uygulamanın kendi istemcisine gider, asla aynı hesabın başka bir istemcisine gitmez.
Bir uç nokta kaydedin
Lingara web uygulamasının Webhook'lar sayfasında, app.getlingara.com/admin/webhooks adresinde, önce istemciyi seçerek bir uç nokta kaydedin. URL'si 443 numaralı bağlantı noktasında https kullanmalı ve ana makine adı yalnızca herkese açık adreslere çözümlenmelidir. Gönderilecek olayları seçin: yalnızca istemcinin kapsamlarının izin verdiği türler sunulur. URL ve olaylar sonradan düzenlenemez: yeni bir uç nokta ekleyin ve eskisini silin. İmzalama gizli anahtarı lgr_whsec_ ile başlar ve yalnızca bir kez gösterilir.
Bir teslimi doğrulayın
Her teslim, Standard Webhooks belirtimine uygun olarak üç başlık içeren bir POST isteğidir: webhook-id (olayın id değeri), webhook-timestamp ve webhook-signature. HMAC anahtarı, gizli anahtarın lgr_whsec_ sonrasındaki kısmının base64 ile çözülmüş halidir; asla dize olarak gizli anahtarın kendisi değildir. Gövdeyi ayrıştırmadan önce, ham baytları üzerinde, aşağıdaki gibi doğrulayın. Zaman damgası şu andan beş dakikadan fazla uzak olan bir teslimi reddedin; bu, Standard Webhooks kütüphanelerinin varsayılanıdır: böylece ele geçirilmiş bir teslim yeniden oynatılamaz.
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 çoğu dil için doğrulayıcılar yayımlar. Bunlar, whsec_ ve ardından base64 olarak ya da yalın base64 olarak yazılmış bir gizli anahtar bekler; bu yüzden onlara Lingara gizli anahtarının lgr_whsec_ sonrasındaki kısmını verin. Lingara'nın kendi kütüphaneleri gizli anahtarın tamamını kabul eder.
Hızlı yanıt verin, yeniden denemeler bekleyin
10 saniye içinde herhangi bir 2xx ile yanıt verin ve işi sonra yapın. Zaman aşımı veya 3xx (yönlendirmeler izlenmez) dahil diğer her şey, yaklaşık bir gün boyunca giderek artan aralıklarla yeniden denenir. Otomatik bir teslime verilen 410 yanıtı uç noktayı hemen devre dışı bırakır; bir teste veya yeniden teslime verilen 410 yanıtı bırakmaz. Beş gün boyunca başarısız teslimlerden sonra da uç nokta devre dışı bırakılır. Her iki durumda da sahibine e-posta gönderilir. Lingara tarafındaki bir hata, bir uç noktanın devre dışı bırakılmasında asla hesaba katılmaz. Webhook'lar sayfasından test gönderebilir ya da son 30 günün herhangi bir teslimini yeniden teslim edebilirsiniz. Her biri tek bir denemedir, asla yeniden denenmez ve devre dışı bir uç noktaya bile gönderilir.
Teslim en az bir kez ve sırasız yapılır. Aynı olay iki kez gelebilir ve bir yeniden deneme daha sonraki bir olaydan sonra gelebilir. webhook-id her yeniden denemede ve 30 güne kadar sonraki bir yeniden teslimde aynıdır. İşlediğiniz her id değerini 30 gün boyunca kaydedin ve tekrarı yok sayın. Sıra önemliyse created_at alanına göre sıralayın.
İmzalama gizli anahtarını yenileyin
Bir uç noktanın aynı anda iki imzalama gizli anahtarı olabilir. İkisi de etkinken webhook-signature iki v1, girdisi taşır ve ikisinden birini kabul eden bir alıcı çalışmaya devam eder. Yeni gizli anahtarı sunucunuza ekleyin, dağıtın, ardından eskisini iptal edin.
Besleme
Bir istemcinin erişim token'ıyla GET /v1/events, items (zarflar), next_cursor ve has_more döndürür. Token'ın events:read kapsamına ve duymak istediğiniz her türün kapsamına ihtiyacı vardır: yalnızca events:read ile besleme boştur. Besleme şu andan başlar. Yaklaşık son 30 günün olayları için start=oldest gönderin. Herkese açık bir uç nokta veya imzalama gizli anahtarı gerektirmez: erişim token'ı kimin sorduğunu kanıtlar. Aşağıdaki değişim, ders planı olaylarının gerektirdiği iki kapsamı birden ister.
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 her zaman bulunur: onu saklayın ve cursor olarak geri gönderin. Opaktır. has_more true şimdi yeniden çağırın, false ise güncel olduğunuz anlamına gelir: daha sonra yeniden sorgulayın ya da akışı açın. 30 günden eski bir imleç 410 ve cursor_expired ile reddedilir. İmleç olmadan besleme şu andan başlar ve aradaki olaylar atlanır. Onları kurtarmak için, olayların saklandığı süre kadar geriye uzanan start=oldest ile çağırın ve zaten işlediğiniz id değerlerini atlayın.
Akış
GET /v1/events/stream aynı olayları server-sent events olarak taşır. Her event çerçevesinin data alanı bir zarftır ve her çerçevenin id: değeri bir imleçtir, next_cursor ile aynı değerdir; böylece besleme ile akış arasında boşluk olmadan geçiş yapabilirsiniz. Kopan bir bağlantıdan, bir done çerçevesinden (akış zaman zaman kendini sonlandırır) ya da bir error çerçevesinden sonra, Last-Event-ID değerini aldığınız son id: olarak ayarlayıp yeniden bağlanın. Çoğu SSE istemcisi bunu sizin yerinize yapar; Lingara'nın kütüphanelerindeki tailEvents de yapar (oradaki streamEvents tek bir bağlantıdır). Bu bir imleçtir, olayın id değeri değildir. Akış düzenli bir canlılık sinyali gönderir; bu yüzden sessiz bir bağlantı ölü bir bağlantıdır.
curl -N "https://api.getlingara.com/v1/events/stream" \
-H "Authorization: Bearer $LINGARA_TOKEN"Lingara'ya bir olay gönderin
events:write ile POST /v1/events, Lingara'ya {type, data} biçiminde bir olay gönderir: world.context_changed (bir scene, source_lang, target_lang, level ve isteğe bağlı olarak name ve persona içeren bir npc ile tags) ya da world.practice_requested (bir topic ile aynı diller ve seviye). Idempotency-Key zorunludur: UUID gibi en fazla 255 görünür ASCII karakteri. Bu anahtar olmadan yanıt 400 ve idempotency_key_required olur. Onu olay başına bir kez belirleyin ve yeniden denemede aynı anahtarı gönderin. Bir anahtar bir olaydır: bir gün içinde aynı anahtarla gelen ikinci bir istek, gövdesi farklı olsa bile ilk yanıtı alır (JSON olarak eşit, bayt bayt değil), ondan sonra ise aşağıda anlatıldığı gibi aynı olayı alır. Gelen olaylar imzalanmaz: kanıt erişim token'ınızdır. Dünyayı tanımlayın, asla oyuncuyu değil: scene, npc, topic veya tags içinde ad ya da sohbet olması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: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}}'Her metin alanı, baştaki ve sondaki boşluklar kırpıldıktan sonra sayılan, görünür karakterlerden oluşan tek bir satırdır: scene ve topic en fazla 160, npc.name en fazla 32 ve npc.persona en fazla 120. Satır sonları, sekmeler ve diğer denetim karakterleri reddedilir; görünmez ve biçimlendirme karakterleri de reddedilir: yön geçersiz kılmaları, bazı yazı sistemlerinin ve emojilerin ihtiyaç duyduğu birleştiriciler dışındaki sıfır genişlikli karakterler, etiket bloğu ve özel kullanım karakterleri. tags her biri en fazla 24 karakterlik en fazla 8 küçük harfli makine belirteci içerir ve asla ders planına ulaşmaz. level 1 ile 9 arasındadır ve iki dil farklı olmalıdır. Bu sınırların dışındaki bir istek 400 ile reddedilir ve hiçbir olay kaydetmez.
"generate": true ile (world.practice_requested için varsayılan), token'ın ayrıca lesson_plans:write kapsamına ihtiyacı vardır. Bu kapsam olmadan istek 403 ile reddedilir ve hiçbir olay kaydedilmez. Bu kapsamla Lingara, doğrudan oluşturmayla aynı denetimler ve aynı ücretle bir ders planı başlatır ve 202 yanıtının reaction alanı ne olduğunu söyler. started ve plan_status generating ile, kullandığınız her yolda data.plan_id değeri yanıttaki plan_id olan bir lesson_plan.ready veya lesson_plan.failed gelir. partial veya complete ile plan kütüphaneden gelmiştir ve hemen okunabilir; hiçbir olay vaat edilmez: yine de bir olay gelebilir, bu yüzden yalnızca generating beklemeye değer. refused veya failed ile olay yine de geçerlidir. Aynı anahtarla yeniden denenmez; yeniden denemek için yeni bir olay gönderin.
Bir gün içindeki yeniden deneme ilk yanıtı geri alır. Ondan sonraki yeniden deneme, saklanan olaydan yeniden oluşturulur; bu kayıt başlattığı planı tutar ama bir tepkinin neden reddedildiğini tutmaz. Bu yüzden geç bir yeniden deneme reaction failed ve internal ile yanıt verebilir: bu, ilk sonucun kaydedilmediği anlamına gelir, plan olmadığı anlamına gelmez. Sakladıysanız planı plan_id değeriyle okuyun ya da yeni bir olay gönderin.
Tidewater Games: sunucusuz bir oyun
Kurgusal bir stüdyo olan Tidewater Games, oyuncunun bir gece pazarını keşfettiği bir Godot oyunu geliştirir. Geliştiricisi oyunu kendi makinesinde, kendi istemcisiyle çalıştırır.
Oyuncu bir erişte tezgâhına girer. Oyun, yukarıdaki gelen olaylar bölümündeki komutla "generate": true içeren bir world.context_changed gönderir ve yanıttaki plan_id değerini saklar.
plan_status generating ise oyun, o plan_id ile bir lesson_plan.ready gelene kadar akışı okur ya da beslemeyi sorgular. Ardından planı aşağıdaki gibi lesson_plans:read ile okur. Plan zaten complete ise onu hemen okur.
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"Daha sonra stüdyo, HTTPS uç noktası olan küçük bir sunucu ekler ve onu lesson_plan.ready için kaydeder. Aynı olay oraya aynı id ile gelir ve oyunun onu okuyan kodu değişmez.
Bir istemci gizli anahtarı asla bir oyun derlemesinin içinde dağıtılmamalıdır, çünkü bir oyuncunun cihazındaki her şey okunabilir. Lingara bir oyuncu adına oturum açmayı destekleyene kadar, oyuncuların makinelerindeki bir oyun kendi sunucusuyla konuşur ve Lingara ile doğrudan yalnızca geliştiricinin kendi kopyası konuşur.
Olayların maliyeti
İstemcinizin kullanımı, her /v1/ çağrısını saydığı gibi, kabul edilen her gelen olayı, her besleme çağrısını ve açılan her akışı sayar. "generate": true ile gönderilen bir olay ayrıca bir ders planı olarak sayılır. Her webhook teslimi, olay ve uç nokta başına bir kez, hangi deneme olursa olsun ilk 2xx yanıtında sayılır. Asla yeniden sayılmaz ve bir test asla sayılmaz. GET /v1/usage ayın şu ana kadarki kısmını gösterir.
Bir çeviri ile İngilizce referans arasında fark varsa, İngilizce referans geçerlidir.