वेबहुक और इवेंट
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 केवल उपयोग-आधारित बिलिंग वाले खातों और क्लाइंट के लिए भेजा जाता है, और एक साथ कई सीमाएँ पार होने पर केवल पार की गई सबसे ऊँची सीमा बताई जाती है, इसलिए हर सीमा के लिए एक इवेंट की अपेक्षा न करें।
एक लॉग, सुनने के तीन तरीके
वेबहुक उस सर्वर के लिए उपयुक्त हैं जिसके पास सार्वजनिक HTTPS एंडपॉइंट है। फ़ीड और स्ट्रीम उस प्रोग्राम के लिए उपयुक्त हैं जिसके पास ऐसा एंडपॉइंट नहीं है, जैसे किसी खिलाड़ी की मशीन पर चल रहा गेम। हर रास्ते पर एनवेलप एक जैसा होता है, इसलिए कोई प्रोग्राम फ़ीड से शुरू करके बाद में वेबहुक पर जा सकता है, इवेंट पढ़ने का तरीका बदले बिना। इवेंट रूट नेटिव प्रोग्राम को जवाब देते हैं। ब्राउज़र में चल रहा गेम अभी उन्हें कॉल नहीं कर सकता, क्योंकि /v1/ किसी क्रॉस-ऑरिजिन प्रीफ़्लाइट का जवाब नहीं देता।
इवेंट कौन सुनता है
कोई क्लाइंट इवेंट तब सुनता है जब उसके पास events:read और इवेंट प्रकार का अपना स्कोप हो, जिसे इवेंट सूची बताती है, और जब इवेंट क्लाइंट के स्वामी के बारे में हो। फ़ीड और स्ट्रीम पर, एक्सेस टोकन के स्कोप इसे और सीमित करते हैं, और types इसे आपके बताए प्रकारों तक सीमित करता है। webhook.test केवल उसी एंडपॉइंट पर जाता है जिस पर उसे भेजा गया था, फ़ीड पर कभी नहीं, और उसकी सदस्यता नहीं ली जा सकती। app.installed और app.uninstalled केवल ऐप के अपने क्लाइंट पर जाते हैं, उसी खाते के किसी अन्य क्लाइंट पर कभी नहीं।
एंडपॉइंट पंजीकृत करें
Lingara वेब ऐप के वेबहुक पेज पर, 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 की ओर की गड़बड़ी एंडपॉइंट को अक्षम करने में कभी नहीं गिनी जाती। वेबहुक पेज से आप परीक्षण भेज सकते हैं, या पिछले 30 दिनों की किसी भी डिलीवरी को फिर से डिलीवर कर सकते हैं। हर एक एक ही प्रयास है, कभी दोहराया नहीं जाता, और अक्षम एंडपॉइंट पर भी भेजा जाता है।
डिलीवरी कम से कम एक बार होती है और क्रम की गारंटी नहीं होती। एक ही इवेंट दो बार आ सकता है, और कोई पुनः प्रयास किसी बाद के इवेंट के बाद आ सकता है। webhook-id हर पुनः प्रयास पर एक जैसा रहता है, और 30 दिन बाद तक की पुनः डिलीवरी पर भी। संभाले गए हर id को 30 दिनों तक दर्ज रखें, और दोहराव को अनदेखा करें। यदि क्रम मायने रखता है तो created_at के अनुसार क्रमबद्ध करें।
साइनिंग सीक्रेट बदलें
एक एंडपॉइंट के पास एक साथ दो साइनिंग सीक्रेट हो सकते हैं। जब दोनों सक्रिय हों, webhook-signature में दो v1, प्रविष्टियाँ होती हैं, और जो रिसीवर किसी एक को भी स्वीकार करता है वह काम करता रहता है। नया सीक्रेट अपने सर्वर में जोड़ें, उसे डिप्लॉय करें, फिर पुराने को रद्द करें।
फ़ीड
किसी क्लाइंट के एक्सेस टोकन के साथ GET /v1/events items (एनवेलप), next_cursor और has_more लौटाता है। टोकन को events:read और हर उस प्रकार का स्कोप चाहिए जिसे आप सुनना चाहते हैं: केवल events:read के साथ फ़ीड खाली रहती है। यह अभी से शुरू होती है। लगभग पिछले 30 दिनों के इवेंट के लिए start=oldest भेजें। इसे न सार्वजनिक एंडपॉइंट चाहिए न साइनिंग सीक्रेट: एक्सेस टोकन साबित करता है कि कौन पूछ रहा है। नीचे दिया गया एक्सचेंज वे दोनों स्कोप माँगता है जो पाठ योजना इवेंट को चाहिए।
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 वही इवेंट server-sent events के रूप में ले जाता है। हर 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 Lingara को {type, data} के रूप में एक इवेंट भेजता है: 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 पोस्ट करता है, यानी ऊपर के इनबाउंड खंड वाली कमांड, और जवाब से 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 के साथ भेजा गया इवेंट पाठ योजना के रूप में भी गिना जाता है। हर वेबहुक डिलीवरी प्रति इवेंट प्रति एंडपॉइंट एक बार गिनी जाती है, उसके पहले 2xx पर, चाहे वह कोई भी प्रयास हो। यह फिर कभी नहीं गिनी जाती, और परीक्षण कभी नहीं गिना जाता। GET /v1/usage इस महीने का अब तक का उपयोग दिखाता है।
यदि किसी अनुवाद और अंग्रेज़ी संदर्भ में अंतर हो, तो अंग्रेज़ी संदर्भ ही मान्य है।