Lingara Lingara Dokumentasi Panduan API Pustaka Aplikasi Buat Aplikasi web
Bahasa: Bahasa Indonesia

Webhook dan peristiwa

Versi API 2026-10-affable-towhee

Lingara mencatat apa yang terjadi pada rencana pelajaran dan penggunaan akun Anda sebagai peristiwa, dan menerima peristiwa dari game atau aplikasi Anda. Setiap peristiwa, lewat jalur mana pun, memiliki amplop yang sama, dan katalog peristiwa mencantumkan semuanya.

Amplop

Setiap peristiwa membawa enam bidang. id diawali lgr_evt_, unik, dan merupakan kunci untuk deduplikasi. type menyebutkan jenis peristiwa. created_at adalah waktu peristiwa terjadi. api_version adalah versi yang menjadi acuan bentuk data: versi yang disematkan pada klien Anda, atau, pada umpan dan aliran, versi yang disebut permintaan Anda di Lingara-Version. subject diawali lgr_sub_ dan menyatakan siapa yang dibicarakan peristiwa itu: nilainya tetap untuk klien Anda tetapi berbeda untuk setiap klien, dan tidak pernah berupa email, nama, atau ID akun. data berukuran kecil dan menyebut sumber daya alih-alih menyalinnya: ambil sumber daya dengan cakupan yang dibutuhkannya.

Dua jenis perlu dijelaskan masing-masing satu kalimat. lesson_plan.ready dapat tiba dua kali untuk satu rencana, pertama dengan data.status partial lalu complete: tanggapi yang pertama untuk rencana yang sudah bisa dipakai, atau tunggu complete untuk mendapatkan semua set. usage.threshold_reached hanya dikirim untuk akun dan klien berbasis meteran, dan lonjakan yang melewati beberapa ambang hanya melaporkan ambang tertinggi yang terlewati, jadi jangan mengharapkan satu peristiwa per ambang.

Satu log, tiga cara mendengarnya

Webhook cocok untuk server dengan titik akhir HTTPS publik. Umpan dan aliran cocok untuk program yang tidak memilikinya, seperti game di mesin pemain. Amplopnya sama di setiap jalur, sehingga program dapat mulai dengan umpan lalu pindah ke webhook nanti tanpa mengubah cara membaca peristiwa. Rute peristiwa melayani program native. Game yang berjalan di browser belum dapat memanggilnya, karena /v1/ tidak menjawab preflight lintas asal.

Siapa yang menerima peristiwa

Klien menerima peristiwa jika memegang events:read dan cakupan milik jenis peristiwa itu sendiri, yang tercantum di katalog, dan jika peristiwa itu menyangkut pemilik klien. Pada umpan dan aliran, cakupan token akses mempersempitnya lebih lanjut, dan types mempersempitnya ke jenis yang Anda sebutkan. webhook.test hanya dikirim ke titik akhir tujuannya, tidak pernah ke umpan, dan tidak dapat dilanggani. app.installed dan app.uninstalled hanya dikirim ke klien milik aplikasi itu sendiri, tidak pernah ke klien lain dari akun yang sama.

Daftarkan titik akhir

Daftarkan titik akhir di halaman Webhook pada aplikasi web Lingara, di app.getlingara.com/admin/webhooks, dengan memilih klien terlebih dahulu. URL-nya harus memakai https di port 443, dan host-nya hanya boleh di-resolve ke alamat publik. Pilih Peristiwa yang dikirim: hanya jenis yang diizinkan cakupan klien yang ditawarkan. URL dan peristiwa tidak dapat diubah nanti: tambahkan titik akhir baru dan hapus yang lama. Rahasia penandatanganan diawali lgr_whsec_ dan hanya ditampilkan sekali.

Verifikasi pengiriman

Setiap pengiriman adalah POST dengan tiga header, mengikuti spesifikasi Standard Webhooks: webhook-id (id peristiwa), webhook-timestamp, dan webhook-signature. Kunci HMAC adalah bagian rahasia setelah lgr_whsec_ yang didekode dari base64, tidak pernah rahasia itu sebagai string. Verifikasi sebelum mengurai body, atas byte mentahnya, seperti di bawah. Tolak pengiriman yang stempel waktunya berselisih lebih dari lima menit dari sekarang, nilai default pustaka Standard Webhooks: ini mencegah pengiriman yang tertangkap diputar ulang.

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 menerbitkan pemverifikasi untuk sebagian besar bahasa. Pemverifikasi itu mengharapkan rahasia yang ditulis sebagai whsec_ diikuti base64, atau base64 saja, jadi berikan bagian rahasia Lingara setelah lgr_whsec_. Pustaka milik Lingara sendiri menerima rahasia secara utuh.

Jawab dengan cepat, bersiaplah untuk percobaan ulang

Jawab dengan 2xx apa pun dalam 10 detik, lalu kerjakan tugasnya setelah itu. Jawaban lain, termasuk waktu habis atau 3xx (pengalihan tidak diikuti), dicoba ulang dengan jeda yang makin panjang selama sekitar sehari. 410 sebagai jawaban atas pengiriman otomatis langsung menonaktifkan titik akhir; 410 sebagai jawaban atas uji atau pengiriman ulang tidak. Setelah lima hari pengiriman gagal, titik akhir juga dinonaktifkan. Dalam kedua kasus, pemiliknya dikirimi email. Gangguan di pihak Lingara tidak pernah dihitung untuk menonaktifkan titik akhir. Dari halaman Webhook Anda dapat mengirim uji, atau mengirim ulang pengiriman mana pun dalam 30 hari terakhir. Masing-masing adalah satu percobaan, tidak pernah dicoba ulang, dan tetap dikirim bahkan ke titik akhir yang dinonaktifkan.

Pengiriman dilakukan setidaknya sekali dan tanpa urutan. Peristiwa yang sama dapat tiba dua kali, dan percobaan ulang dapat tiba setelah peristiwa yang lebih baru. webhook-id sama di setiap percobaan ulang, dan pada pengiriman ulang hingga 30 hari kemudian. Catat setiap id yang sudah Anda tangani selama 30 hari, dan abaikan pengulangan. Urutkan berdasarkan created_at jika urutan penting.

Rotasi rahasia penandatanganan

Satu titik akhir dapat memegang dua rahasia penandatanganan sekaligus. Selama keduanya aktif, webhook-signature membawa dua entri v1,, dan penerima yang menerima salah satunya tetap berfungsi. Tambahkan rahasia baru ke server Anda, terapkan, lalu cabut yang lama.

Umpan

GET /v1/events dengan token akses klien mengembalikan items (amplop), next_cursor, dan has_more. Token memerlukan events:read dan cakupan setiap jenis yang ingin Anda terima: dengan events:read saja, umpan kosong. Umpan dimulai dari sekarang. Berikan start=oldest untuk peristiwa sekitar 30 hari terakhir. Umpan tidak memerlukan titik akhir publik maupun rahasia penandatanganan: token akses membuktikan siapa yang bertanya. Penukaran di bawah meminta kedua cakupan yang dibutuhkan peristiwa rencana pelajaran.

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 selalu ada: simpan dan berikan kembali sebagai cursor. Nilainya buram. has_more true berarti panggil lagi sekarang, dan false berarti Anda sudah mengejar ketertinggalan: lakukan polling nanti, atau buka aliran. Kursor yang lebih lama dari 30 hari ditolak dengan 410 dan cursor_expired. Tanpa kursor, umpan dimulai dari sekarang, dan peristiwa di antaranya terlewati. Untuk memulihkannya, panggil dengan start=oldest, yang menjangkau sejauh peristiwa disimpan, dan lewati nilai id yang sudah Anda tangani.

Aliran

GET /v1/events/stream membawa peristiwa yang sama sebagai server-sent events. data setiap frame event adalah satu amplop, dan id: setiap frame adalah kursor, token yang sama dengan next_cursor, sehingga Anda dapat berpindah antara umpan dan aliran tanpa celah. Setelah koneksi terputus, setelah frame done (aliran sesekali berakhir sendiri), atau setelah frame error, sambungkan kembali dengan Last-Event-ID disetel ke id: terakhir yang Anda terima. Sebagian besar klien SSE melakukannya untuk Anda, begitu pula tailEvents di pustaka Lingara (streamEvents di sana adalah satu koneksi tunggal). Itu adalah kursor, bukan id peristiwa. Aliran mengirim heartbeat, jadi koneksi yang senyap adalah koneksi yang mati.

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

Kirim peristiwa ke Lingara

POST /v1/events dengan events:write mengirim peristiwa ke Lingara sebagai {type, data}: world.context_changed (sebuah scene, source_lang, target_lang, level, dan secara opsional npc dengan name dan persona, serta tags) atau world.practice_requested (sebuah topic serta bahasa dan level yang sama). Idempotency-Key wajib: hingga 255 karakter ASCII yang terlihat, seperti UUID. Tanpanya jawabannya adalah 400 dan idempotency_key_required. Setel sekali per peristiwa, dan kirim kunci yang sama saat mencoba ulang. Satu kunci adalah satu peristiwa: dalam sehari, permintaan kedua dengan kunci yang sama mendapatkan jawaban pertama (sama sebagai JSON, bukan byte demi byte), meskipun body-nya berbeda, dan setelah itu mendapatkan peristiwa yang sama, seperti di bawah. Peristiwa masuk tidak ditandatangani: token akses Anda adalah buktinya. Gambarkan dunianya, jangan pemainnya: tanpa nama atau obrolan di scene, npc, topic, atau 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}}'

Setiap bidang teks adalah satu baris karakter yang terlihat, dihitung setelah dipangkas: scene dan topic hingga 160, npc.name hingga 32, dan npc.persona hingga 120. Jeda baris, tab, dan karakter kontrol lainnya ditolak, begitu pula karakter tak terlihat dan karakter pemformatan: penimpa arah, karakter lebar nol selain penyambung yang dibutuhkan sebagian aksara dan emoji, blok tag, dan karakter penggunaan pribadi. tags memuat hingga 8 token mesin huruf kecil masing-masing hingga 24 karakter, dan tidak pernah sampai ke rencana pelajaran. level bernilai 1 hingga 9, dan kedua bahasa harus berbeda. Permintaan di luar batas ini ditolak dengan 400 dan tidak mencatat peristiwa.

Dengan "generate": true (default untuk world.practice_requested), token juga memerlukan lesson_plans:write. Tanpanya permintaan ditolak dengan 403 dan tidak ada peristiwa yang dicatat. Dengannya, Lingara memulai rencana pelajaran, dengan pemeriksaan dan tagihan yang sama seperti membuatnya secara langsung, dan reaction pada jawaban 202 menyatakan apa yang terjadi. Dengan started dan plan_status generating, sebuah lesson_plan.ready atau lesson_plan.failed yang data.plan_id-nya adalah plan_id jawaban itu akan menyusul, di setiap jalur yang Anda pakai. Dengan partial atau complete, rencana itu berasal dari pustaka dan dapat dibaca sekarang, dan tidak ada peristiwa yang dijanjikan: satu peristiwa mungkin tetap tiba, jadi hanya generating yang layak ditunggu. Dengan refused atau failed, peristiwa itu tetap berlaku. Peristiwa itu tidak dicoba ulang dengan kunci yang sama, jadi kirim peristiwa baru untuk mencoba lagi.

Percobaan ulang dalam sehari mendapatkan jawaban pertama kembali. Percobaan ulang setelah itu dibangun ulang dari peristiwa yang disimpan, yang mempertahankan rencana yang dimulainya tetapi tidak alasan sebuah reaksi ditolak. Jadi percobaan ulang yang terlambat dapat menjawab dengan reaction failed dan internal: artinya hasil pertama tidak tercatat, bukan berarti tidak ada rencana. Baca rencana itu dengan plan_id-nya jika Anda menyimpannya, atau kirim peristiwa baru.

Tidewater Games: game tanpa server

Tidewater Games, sebuah studio fiktif, membuat game Godot tempat pemain menjelajahi pasar malam. Pengembangnya menjalankan game itu di mesinnya sendiri, dengan kliennya sendiri.

Pemain masuk ke sebuah kedai mi. Game mengirim world.context_changed dengan "generate": true, yaitu perintah di bagian “Kirim peristiwa ke Lingara” di atas, dan menyimpan plan_id dari jawaban.

Jika plan_status bernilai generating, game membaca aliran, atau melakukan polling pada umpan, sampai lesson_plan.ready dengan plan_id itu tiba. Lalu game membaca rencana itu dengan lesson_plans:read, seperti di bawah. Jika rencana itu sudah complete, game langsung membacanya.

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"

Kemudian studio menambahkan server kecil dengan titik akhir HTTPS dan mendaftarkannya untuk lesson_plan.ready. Peristiwa yang sama tiba di sana, dengan id yang sama, dan kode game untuk membacanya tidak berubah.

Rahasia klien tidak boleh ikut dalam build game, karena apa pun di perangkat pemain dapat dibaca. Sampai Lingara mendukung masuk atas nama pemain, game di mesin pemain berkomunikasi dengan server studionya sendiri, dan hanya salinan milik pengembang sendiri yang berkomunikasi langsung dengan Lingara.

Biaya peristiwa

Penggunaan klien Anda menghitung setiap peristiwa masuk yang diterima, setiap panggilan umpan, dan setiap aliran yang dibuka, sebagaimana ia menghitung setiap panggilan /v1/. Peristiwa yang dikirim dengan "generate": true juga dihitung sebagai rencana pelajaran. Setiap pengiriman webhook dihitung sekali per peristiwa per titik akhir, pada 2xx pertamanya, percobaan ke berapa pun itu. Pengiriman itu tidak pernah dihitung lagi, dan uji tidak pernah dihitung. GET /v1/usage menampilkan penggunaan bulan ini sejauh ini.

Jika terjemahan berbeda dengan referensi berbahasa Inggris, referensi berbahasa Inggris yang berlaku.