Courstore API
Maktab API
Maktabingiz kurslariga talabalarni CRM yoki boshqa tizimdan qabul qiling. To‘lov sizning tomoningizda qoladi, Courstore esa kirishni ochib, talabaga xabar beradi.
https://api.courstore.com/v1/school-apiSwagger spetsifikatsiyasi →Umumiy ko‘rinish
School API tizimingizga (CRM, to‘lov xizmati, o‘z saytingiz) talabalarni Courstore’dagi maktab kurslariga qabul qilish va ularning kirishini tekshirish imkonini beradi. To‘lovni o‘zingiz qabul qilasiz — API faqat kursga kirishni ochadi.
- Asosiy URL:
https://api.courstore.com/v1/school-api - Format: JSON. So‘rov tanasi —
Content-Type: application/json, hajmi 4 KB gacha. - Avtorizatsiya:
X-School-Keysarlavhasidagi maktab kaliti. - Kirish: Pro (yillik), Lifetime va Enterprise tariflari. Boshqa tariflarda API
403 tariff_not_allowedqaytaradi — tarif har bir so‘rovda tekshiriladi. - Xatolar doim JSON ko‘rinishida keladi:
{"error": "kod", "message": "matn"}.
To‘liq mashina o‘qiydigan spetsifikatsiya: Swagger.
| Metod | URL | Vazifasi |
|---|---|---|
| GET | /courses | Maktab kurslari ro‘yxati va odatiy kirish muddati |
| POST | /students | Talabani telefon bo‘yicha topish yoki yaratish |
| POST | /enrollments | Talabani kursga qabul qilish (talaba bo‘lmasa, yaratadi) |
| GET | /enrollments/{requestId} | requestId bo‘yicha qabul holati |
| GET | /enrollments?phone=&courseId= | Talabaning kursga joriy kirishi |
Tez boshlash
- Tarifni tekshiring. API Pro (yillik), Lifetime va Enterprise tariflarida mavjud.
- Kalit oling. Maktab kabinetida «Integratsiyalar» → «Maktab API» → «Kalit yaratish» bo‘limini oching.
cs_live_…ko‘rinishidagi kalit faqat bir marta ko‘rsatiladi — darhol ishonchli joyga saqlab qo‘ying. 3 tagacha faol kalit saqlash mumkin, istalganini bekor qilish mumkin. - Birinchi so‘rovni yuboring. Maktab kurslari ro‘yxatini oling — javobda qabul qilish uchun kerak bo‘ladigan kurslarning
idqiymatlari bo‘ladi.
export KEY="cs_live_..."
export BASE="https://api.courstore.com/v1/school-api"curl -s "$BASE/courses" \
-H "X-School-Key: $KEY"[
{
"id": 1234,
"title": "Основы маркетинга",
"slug": "osnovy-marketinga",
"status": "publish",
"access": {
"mode": "months",
"amount": 3
}
},
{
"id": 1301,
"title": "Мастер-класс",
"slug": "master-klass",
"status": "publish",
"access": {
"mode": "unlimited",
"amount": null
}
}
]Keyin talabani kursga qabul qiling — batafsil «Kursga qabul qilish» metodida:
curl -s -X POST "$BASE/enrollments" \
-H "X-School-Key: $KEY" \
-H "Idempotency-Key: order-0042" \
-H "Content-Type: application/json" \
-d '{"phone":"+77001234567","courseId":1234,"access":{"mode":"days","amount":30},"notify":true,"orderRef":"CRM-0042"}'{
"requestId": "req_882",
"status": "queued",
"studentId": 118350,
"enrollmentId": null,
"expireDate": null,
"lastDay": null
}queued holatidagi 202 javobi qabul navbatga olinganini bildiradi. Natijani «Qabul holati» metodidan oling (odatda bir necha soniyada tayyor bo‘ladi).
Autentifikatsiya
Har bir so‘rovda maktab kaliti bilan X-School-Key sarlavhasi bo‘lishi kerak:
curl -s "https://api.courstore.com/v1/school-api/courses" \
-H "X-School-Key: cs_live_..."- Kalit kabinetda yaratiladi: «Integratsiyalar» → «Maktab API». Formati —
cs_live_…, bir marta ko‘rsatiladi. - Maktabga 3 tagacha faol kalit beriladi. Istalgan kalitni shu kartochkada bekor qilish mumkin; uni ishlatadigan integratsiyalar ishlashdan to‘xtaydi.
- Kalitni o‘z serveringizda saqlang. Uni mobil ilovalarga, brauzer kodiga va ochiq repozitoriylarga joylashtirmang.
- Muddat majburiy emas: kalit yaratishda «gacha amal qiladi» sanasini ko‘rsatish mumkin (shu kun ham kiradi). Undan keyin kalit
401 key_expiredqaytaradi — yangisini yarating. - Maktab tarifi tugasa, uning barcha kalitlari avtomatik va butunlay bekor qilinadi; tarifni uzaytirgach yangi kalit yarating. Bekor qilingan kalit
401 bad_keyoladi. - Kalit berilmasa, noto‘g‘ri bo‘lsa yoki bekor qilingan bo‘lsa —
401javobi keladi:
{
"error": "bad_key",
"message": "Неверный ключ API"
}Kalit to‘g‘ri, lekin maktab tarifi API’ga kirishni bermasa — 403 tariff_not_allowed. «Maktab API» kartochkasida bugungi kalit ishlatilishi va oxirgi xatolar ko‘rinadi.
Metodlar
Quyidagi misollarda «Tez boshlash» bo‘limidagi $BASE va $KEY o‘zgaruvchilari ishlatiladi. Yo‘llar asosiy URL — https://api.courstore.com/v1/school-api — ga nisbatan ko‘rsatilgan.
Maktab kurslari
/coursesKalit egasi maktabning kurslarini qaytaradi. access maydoni — kurs sozlamalaridagi odatiy kirish muddati (qabul qilishda access berilmasa ishlatiladi).
Parametrlar yo‘q.
So‘rov misoli
curl -s "$BASE/courses" \
-H "X-School-Key: $KEY"Javob maydonlari
| Maydon | Turi | Tavsif |
|---|---|---|
id | integer | Kurs identifikatori — uni courseId sifatida beriladi |
title | string | Kurs nomi |
slug | string | Manzildagi kursning qisqa nomi |
status | string | publish | private | draft | pending. Faqat e’lon qilingan kursga qabul qilish mumkin |
access.mode | string | days | months | unlimited |
access.amount | integer | null | Kunlar yoki oylar soni; unlimited bo‘lsa null |
Javob misoli
[
{
"id": 1234,
"title": "Основы маркетинга",
"slug": "osnovy-marketinga",
"status": "publish",
"access": {
"mode": "months",
"amount": 3
}
},
{
"id": 1301,
"title": "Мастер-класс",
"slug": "master-klass",
"status": "publish",
"access": {
"mode": "unlimited",
"amount": null
}
}
]Metod xatolari
| HTTP | Kod | Ma’nosi |
|---|---|---|
| 401 | bad_key | Kalit berilmagan, noto‘g‘ri yoki bekor qilingan |
| 401 | key_expired | Kalitning amal qilish muddati tugagan |
| 403 | tariff_not_allowed | Maktab tarifi API’ga kirishni bermaydi |
| 429 | rate_limited | Daqiqasiga 60 so‘rov limiti oshib ketdi |
Talabani topish yoki yaratish
/studentsTalabani telefon bo‘yicha qidirib, qaytaradi; sizning maktabingizda bunday talaba bo‘lmasa — yaratadi. Telefon me’yorlashtiriladi (+7 / 8 / bo‘shliqlar / qavslar). Mavjud talabaning ismi va pochtasi qayta yozilmaydi. Yangi talaba parolsiz yaratiladi — kirish kod orqali.
So‘rov tanasi (JSON)
| Parametr | Turi | Majburiy | Tavsif |
|---|---|---|---|
phone | string | ha | Istalgan formatdagi telefon |
name | string | yo‘q | Ism, 200 belgigacha |
email | string | yo‘q | Pochta, 200 belgigacha |
lang | string | yo‘q | Talaba tili: ru | kk | en | uz. Odatiy ru |
So‘rov misoli
curl -s -X POST "$BASE/students" \
-H "X-School-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"+7 (700) 123-45-67","name":"Анна","email":"anna@example.com","lang":"ru"}'Javob maydonlari
| Maydon | Turi | Tavsif |
|---|---|---|
studentId | integer | Talaba identifikatori |
created | boolean | true — talaba sizning maktabingizda hali bo‘lmagan (javob 201); false — allaqachon o‘qiydi (javob 200) |
Javob misoli
{
"studentId": 118350,
"created": true
}{
"studentId": 118347,
"created": false
}Metod xatolari
| HTTP | Kod | Ma’nosi |
|---|---|---|
| 401 | bad_key | Kalit berilmagan, noto‘g‘ri yoki bekor qilingan |
| 401 | key_expired | Kalitning amal qilish muddati tugagan |
| 403 | tariff_not_allowed | Maktab tarifi API’ga kirishni bermaydi |
| 409 | student_limit_reached | Tarif bo‘yicha talabalar limitiga yetildi |
| 422 | validation_error | Ma’lumotlar noto‘g‘ri, masalan telefon raqami; shuningdek invalid_json va payload_too_large |
| 429 | rate_limited | Daqiqasiga 60 so‘rov limiti oshib ketdi |
Kursga qabul qilish
/enrollmentsTalabani kursga qabul qiladi. Bunday telefonli talaba bo‘lmasa, u yaratiladi. Kirish muddati access berilsa undan, aks holda kurs sozlamalaridan olinadi. Idempotency-Key sarlavhasi majburiy.
Sarlavha va so‘rov tanasi
| Parametr | Turi | Majburiy | Tavsif |
|---|---|---|---|
Idempotency-Key | header, string | ha | Operatsiyaning noyob kaliti (masalan, CRM’dagi buyurtma raqami), 128 belgigacha: harflar, raqamlar, . _ : - |
phone | string | ha | Talabaning istalgan formatdagi telefoni |
courseId | integer | ha | GET /courses javobidagi kurs identifikatori |
name | string | yo‘q | Ism, 200 belgigacha (yangi talaba uchun ishlatiladi) |
email | string | yo‘q | Pochta, 200 belgigacha |
access | object | yo‘q | Kirish muddati. Berilmasa — kurs sozlamalaridan |
access.mode | string | yo‘q | days | months |
access.amount | integer | yo‘q | days: 1…3650; months: 1…120 |
notify | boolean | yo‘q | Talabaga qabul haqida xabarnomalar yuborish. Odatiy true |
orderRef | string | yo‘q | Sizning buyurtma raqamingiz, 128 belgigacha (o‘z hisobingiz uchun) |
So‘rov misoli
curl -s -X POST "$BASE/enrollments" \
-H "X-School-Key: $KEY" \
-H "Idempotency-Key: order-0042" \
-H "Content-Type: application/json" \
-d '{"phone":"+77001234567","name":"Анна","courseId":1234,"orderRef":"CRM-2026-0042"}'Javob maydonlari
| Maydon | Turi | Tavsif |
|---|---|---|
requestId | string | So‘rov identifikatori — GET /enrollments/{requestId} uchun |
status | string | queued — navbatga olindi; done — bajarildi |
studentId | integer | null | Talaba identifikatori |
enrollmentId | integer | null | Qabul identifikatori; so‘rov navbatda turganda null |
expireDate | date | null | Kirish yopiladigan chegara; null — muddatsiz yoki hali noma’lum |
lastDay | date | null | Kirishning oxirgi kuni |
Javob misoli
{
"requestId": "req_882",
"status": "queued",
"studentId": 118350,
"enrollmentId": null,
"expireDate": null,
"lastDay": null
}{
"requestId": "req_881",
"status": "done",
"studentId": 118347,
"enrollmentId": 902113,
"expireDate": "2026-12-01",
"lastDay": "2026-11-30"
}Yangi qabul — javob 202, status: queued. Qabul va xabarnomalar taxminan bir daqiqa ichida bajariladi (amalda — soniyalar); natija — GET /enrollments/{requestId} metodida.
Qayta xarid (talaba kursga allaqachon qabul qilingan) — kirish darhol uzaytiriladi, javob 200, status: done.
Metod xatolari
| HTTP | Kod | Ma’nosi |
|---|---|---|
| 401 | bad_key | Kalit berilmagan, noto‘g‘ri yoki bekor qilingan |
| 401 | key_expired | Kalitning amal qilish muddati tugagan |
| 403 | tariff_not_allowed | Maktab tarifi API’ga kirishni bermaydi |
| 404 | course_not_found | Kurs sizning maktabingizda topilmadi (begona kursni ham qo‘shgan holda) |
| 409 | idempotency_conflict | Bu Idempotency-Key boshqa tana bilan ishlatilgan |
| 409 | idempotency_in_progress | Shu kalitli so‘rov hali bajarilmoqda |
| 409 | course_locked | Kurs muzlatilgan |
| 409 | course_closed | Kurs e’lon qilinmagan (masalan, qoralama) |
| 409 | student_limit_reached | Tarif bo‘yicha talabalar limitiga yetildi |
| 422 | idempotency_key_required | Idempotency-Key sarlavhasi berilmagan; shuningdek validation_error, invalid_json, payload_too_large |
| 429 | rate_limited | Daqiqasiga 60 so‘rov limiti oshib ketdi |
| 429 | daily_limit | Maktab uchun sutkasiga 300 ta qabul limiti oshib ketdi |
Qabul holati
/enrollments/{requestId}POST /enrollments metodi qabul qilgan qabulning holatini qaytaradi.
Yo‘l parametrlari
| Parametr | Turi | Majburiy | Tavsif |
|---|---|---|---|
requestId | string | ha | Qabul javobidagi identifikator, masalan req_882 |
So‘rov misoli
curl -s "$BASE/enrollments/req_882" \
-H "X-School-Key: $KEY"Javob maydonlari
| Maydon | Turi | Tavsif |
|---|---|---|
requestId | string | So‘rov identifikatori |
status | string | queued — qayta ishlashni kutmoqda; done — talaba qabul qilindi; failed — 15 daqiqa ichida qabul tasdiqlanmadi |
studentId | integer | null | Talaba identifikatori |
courseId | integer | null | Kurs identifikatori |
expireDate | date | null | Kirish yopiladigan chegara |
lastDay | date | null | Kirishning oxirgi kuni |
notified | boolean | true — talabaga xabarnoma yuborishga qo‘yildi (yetkazilishini xizmat tasdiqlamaydi) |
error | string | null | failed bo‘lganda sabab: not_confirmed |
Javob misoli
{
"requestId": "req_882",
"status": "done",
"studentId": 118350,
"courseId": 1234,
"expireDate": "2027-01-20",
"lastDay": "2027-01-19",
"notified": true,
"error": null
}Metod xatolari
| HTTP | Kod | Ma’nosi |
|---|---|---|
| 401 | bad_key | Kalit berilmagan, noto‘g‘ri yoki bekor qilingan |
| 401 | key_expired | Kalitning amal qilish muddati tugagan |
| 403 | tariff_not_allowed | Maktab tarifi API’ga kirishni bermaydi |
| 404 | — | Bunday requestId li so‘rov topilmadi |
| 429 | rate_limited | Daqiqasiga 60 so‘rov limiti oshib ketdi |
Kirishni tekshirish
/enrollments?phone=&courseId=Talabaning maktabingiz kursiga joriy kirishini ko‘rsatadi. Maktabingiz bilan bog‘lanmagan talaba mavjud bo‘lmagan talaba bilan bir xil javob beradi: active: false.
So‘rov parametrlari (query)
| Parametr | Turi | Majburiy | Tavsif |
|---|---|---|---|
phone | string | ha | Talaba telefoni. URL’dagi + belgisini %2B ko‘rinishida bering |
courseId | integer | ha | Kurs identifikatori |
So‘rov misoli
curl -s "$BASE/enrollments?phone=%2B77001234567&courseId=1234" \
-H "X-School-Key: $KEY"Javob maydonlari
| Maydon | Turi | Tavsif |
|---|---|---|
active | boolean | Talabada amaldagi kirish bormi |
expireDate | date | null | Kirish yopiladigan chegara |
lastDay | date | null | Kirishning oxirgi kuni |
progress | integer | Kurs bo‘yicha progress, % |
Javob misoli
{
"active": true,
"expireDate": "2026-12-01",
"lastDay": "2026-11-30",
"progress": 35
}Metod xatolari
| HTTP | Kod | Ma’nosi |
|---|---|---|
| 401 | bad_key | Kalit berilmagan, noto‘g‘ri yoki bekor qilingan |
| 401 | key_expired | Kalitning amal qilish muddati tugagan |
| 403 | tariff_not_allowed | Maktab tarifi API’ga kirishni bermaydi |
| 404 | course_not_found | Kurs sizning maktabingizda topilmadi |
| 422 | validation_error | phone / courseId berilmagan yoki noto‘g‘ri |
| 429 | rate_limited | Daqiqasiga 60 so‘rov limiti oshib ketdi |
O‘quvchi uchun kirish havolasi
Servisingiz o‘quvchiga bir martalik havola bera oladi: u parolsiz Courstore’dagi maktabingiz kabinetini ochadi. Havoladagi token — 32 tasodifiy bayt, bizda faqat xesh saqlanadi, birinchi ishlatilgandan keyin havola o‘chadi.
Courstore’ga kirish havolasi
O‘quvchi servisingizga kirgan — unga Courstore’dagi maktabingiz kabinetini parolsiz ochadigan havola bering. Havola bir martalik.
/students/login-linkSo‘rov tanasi
| Parametr | Turi | Majburiy | Tavsif |
|---|---|---|---|
phone | string | ha | O‘quvchi telefoni istalgan formatda |
redirect | integer | yo‘q | Maktabingiz kursi id’si: kirgandan keyin kurs sahifasi ochiladi. Usiz — «Mening kurslarim» |
ttlSeconds | integer | yo‘q | Havola amal qilish muddati, 30 dan 86400 soniyagacha, standart 120 |
So‘rov misoli
curl -s -X POST "$BASE/students/login-link" \
-H "X-School-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"+77001234567","redirect":1234,"ttlSeconds":120}'Javob misoli
{
"url": "https://myschool.courstore.com/ru/login-via-token?token=csl_...&returnUrl=courses%2Fosnovy-marketinga",
"expiresAt": "2026-10-16T10:02:00Z"
}expiresAt — havola qaysi vaqtgacha (UTC) amal qiladi.
Qaysi muddatni tanlash kerak
| Ssenariy | ttlSeconds | Nega |
|---|---|---|
| Saytingizdagi «Courstore’ga o‘tish» tugmasi: havolani olib, darhol yo‘naltirish | 120 (standart) | Havola bir necha soniya kerak; qisqa muddat — manzil tarqalib ketsa, xavf kam |
| Xat, WhatsApp yoki Telegram’dagi havola | 86400 gacha (24 soat) | O‘quvchi xabarni keyinroq ochishi mumkin. Havola baribir faqat bir marta ishlaydi |
curl -s -X POST "$BASE/students/login-link" \
-H "X-School-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"+77001234567","ttlSeconds":86400}'Havola eskirgan yoki allaqachon ochilgan bo‘lsa, o‘quvchi xato ko‘rmaydi: u maktabingizning oddiy kirish sahifasiga «Havola eskirgan — telefon raqamingiz orqali kiring» degan maslahat bilan tushadi.
Serverdagi misol
app.get("/go-to-courstore", requireLogin, async (req, res) => {
const r = await fetch(`${API}/students/login-link`, {
method: "POST",
headers: { "X-School-Key": KEY, "Content-Type": "application/json" },
body: JSON.stringify({ phone: req.user.phone, redirect: 1234 }),
});
if (r.status !== 200) return res.redirect("https://myschool.courstore.com/ru/login");
const { url } = await r.json();
res.redirect(url);
});Metod xatolari
| HTTP | Kod | Ma’nosi |
|---|---|---|
| 404 | student_not_found | Raqam maktabingiz o‘quvchisiga tegishli emas. Muallif, xodimlar va administratorlarga kirish havolasi hech qachon berilmaydi — ularga ham shu javob |
| 404 | course_not_found | redirect dagi kurs maktabingizga tegishli emas |
| 422 | validation_error | phone, redirect noto‘g‘ri yoki ttlSeconds 30…86400 oralig‘idan tashqarida |
| 429 | daily_limit | Maktabga sutkasiga 2000 tadan ortiq havola; Retry-After sarlavhasiga qarang |
| 429 | rate_limited | Kalitga daqiqasiga 60 tadan ortiq so‘rov (API umumiy limiti) |
Xavfsizlik. Havola o‘quvchi akkauntiga kirish bilan teng: uni loglarga yozmang, uchinchi shaxslarga yubormang va faqat servisingizga kirgan o‘quvchiga bering.
Vebxuklar
Vebxuklar tizimingizga qabul qilishlar va kirish o‘zgarishlari haqida API’ni so‘ramasdan darhol xabar beradi: Courstore manzilingizga o‘zi POST yuboradi.
Sozlash
- Kabinetda: «Integratsiyalar» → «Maktab API» → «Vebxuklar» → URL. Faqat
https://va faqat ommaviy manzil: ichki va mahalliy manzillar hamda IP-manzillar taqiqlangan. - Birinchi saqlashda
whsec_…imzo siri beriladi. U bir marta ko‘rsatiladi — darhol saqlab qo‘ying. Sirni qayta chiqarish mumkin: eskisi darhol ishlamay qoladi. - «Test yuborish» tugmasi
webhook.testhodisasini yuboradi. Jurnalda oxirgi 20 ta yetkazish ko‘rinadi: hodisa, javob kodi, urinish, vaqt.
Hodisalar
Hodisa turi type maydonida va X-Courstore-Event sarlavhasida keladi.
type | Qachon keladi | accessUntil |
|---|---|---|
enrollment.created | O‘quvchi kursga qabul qilindi: to‘lov, kabinetda qo‘lda qabul qilish, School API | Kirishning oxirgi kuni yoki null |
access.extended | Kirish uzaytirildi yoki kurs qayta sotib olindi | Yangi oxirgi kun |
access.expired | Kirish muddati tugadi. Faqat vebxuk yoqilgandan keyingi tugashlar uchun keladi | Kirishning oxirgi kuni |
access.revoked | Muallif o‘quvchining kursga kirishini yopdi | Maydon yo‘q |
webhook.test | Kabinetdagi «Test yuborish» tugmasi | — |
accessUntil — kirishning oxirgi kuni, shu kun ham (YYYY-MM-DD), null — muddatsiz.
So‘rov tanasi
{
"id": "evt_8f3a1c0e2b7d4a91",
"type": "enrollment.created",
"createdAt": "2026-10-16T10:00:00Z",
"schoolId": 7,
"data": {
"enrollmentId": 501,
"student": {
"id": 118347,
"name": "Анна",
"phone": "+77001234567",
"email": null
},
"course": {
"id": 1234,
"title": "Основы маркетинга"
},
"accessUntil": "2026-12-31"
}
}{
"id": "evt_1b9e44d07c2f8e30",
"type": "access.revoked",
"createdAt": "2026-10-20T08:15:00Z",
"schoolId": 7,
"data": {
"enrollmentId": 501,
"student": {
"id": 118347,
"name": "Анна",
"phone": "+77001234567",
"email": null
},
"course": {
"id": 1234,
"title": "Основы маркетинга"
}
}
}Javob maydonlari
| Maydon | Turi | Tavsif |
|---|---|---|
id | string | Hodisaning noyob id’si evt_…; X-Courstore-Delivery bilan bir xil. Idempotentlik kaliti |
type | string | Hodisa turi |
createdAt | date-time | Hodisa sodir bo‘lgan vaqt (UTC) |
schoolId | integer | Courstore’dagi maktabingiz id’si |
data.enrollmentId | integer | Qabul qilish id’si |
data.student | object | O‘quvchining id, name, phone, email |
data.course | object | Kursning id va title |
data.accessUntil | date | null | Kirishning oxirgi kuni; null — muddatsiz. access.revoked da maydon yo‘q |
Sarlavhalar va imzo
| Sarlavha | Tavsif |
|---|---|
X-Courstore-Event | Hodisa turi |
X-Courstore-Delivery | Hodisa id’si (tanadagi id bilan bir xil) — takrorlardan himoya uchun |
X-Courstore-Signature | t=<unix-vaqt>,v1=<hex>, bunda v1 = HMAC-SHA256(sir, t + "." + xom tana) |
POST /courstore/webhook HTTP/1.1
Content-Type: application/json
X-Courstore-Event: enrollment.created
X-Courstore-Delivery: evt_8f3a1c0e2b7d4a91
X-Courstore-Signature: t=1792144800,v1=5f0c2b…- So‘rovning xom tanasini oling — JSON tahlilidan oldin: har qanday qayta formatlash imzoni buzadi.
whsec_…siri bilant + "." + tanasatridan HMAC-SHA256 ni hex ko‘rinishida hisoblang.- Uni
v1bilan doimiy vaqtli taqqoslash orqali solishtiring (timingSafeEqual,hash_equals,compare_digest). t5 daqiqadan eski bo‘lgan so‘rovlarni rad eting — bu ushlab qolingan so‘rovni qayta yuborishdan himoya qiladi.
Imzoni tekshirish misoli
const crypto = require("crypto");
const express = require("express");
const app = express();
app.post("/courstore/webhook", express.raw({ type: "application/json" }), (req, res) => {
const sig = Object.fromEntries(
String(req.get("X-Courstore-Signature") || "").split(",").map((p) => p.split("="))
);
const expected = crypto
.createHmac("sha256", process.env.COURSTORE_WEBHOOK_SECRET)
.update(sig.t + "." + req.body.toString("utf8"))
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(sig.t)) < 300;
const ok = fresh && typeof sig.v1 === "string" && sig.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
// event.id — idempotency key; event.type — enrollment.created | access.extended | ...
res.sendStatus(200);
});<?php
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_COURSTORE_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, getenv('COURSTORE_WEBHOOK_SECRET'));
if (abs(time() - (int)($sig['t'] ?? 0)) > 300 || !hash_equals($expected, (string)($sig['v1'] ?? ''))) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// $event['id'] — idempotency key; $event['type'] — event type
http_response_code(200);import hashlib, hmac, os, time
from flask import Flask, request
app = Flask(__name__)
@app.post("/courstore/webhook")
def courstore_webhook():
body = request.get_data()
header = request.headers.get("X-Courstore-Signature", "")
sig = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
expected = hmac.new(os.environ["COURSTORE_WEBHOOK_SECRET"].encode(),
sig.get("t", "").encode() + b"." + body, hashlib.sha256).hexdigest()
t = sig.get("t", "")
fresh = t.isdigit() and abs(time.time() - int(t)) <= 300
if not fresh or not hmac.compare_digest(expected, sig.get("v1", "")):
return "", 401
event = request.get_json()
# event["id"] — idempotency key; event["type"] — event type
return "", 200Yetkazish va takrorlash
- 10 soniya ichida
2xxbilan javob bering. Og‘ir ishlovni javobdan keyin o‘z navbatingizda bajaring. - Boshqa har qanday javob, vaqt tugashi yoki ulanish xatosi — yetkazish quyidagi jadval bo‘yicha takrorlanadi, jami 6 urinish.
- Qayta yo‘naltirishlar (
3xx) bajarilmaydi va muvaffaqiyatsizlik hisoblanadi — yakuniy manzilni kiriting. - Hodisalar tartibi kafolatlanmaydi, bitta hodisa ikki marta kelishi mumkin —
idbo‘yicha idempotent ishlang. - Hodisalar 1–2 daqiqagacha kechikib keladi, kirish tugashi (
access.expired) — 30 daqiqagacha.
| Urinish | Qachon |
|---|---|
| 1 | Hodisadan keyin darhol |
| 2 | 1 daqiqadan keyin |
| 3 | 5 daqiqadan keyin |
| 4 | 30 daqiqadan keyin |
| 5 | 2 soatdan keyin |
| 6 | 6 soatdan keyin |
Har bir urinish natijasi «Maktab API» kartasidagi yetkazish jurnalida ko‘rinadi.
Idempotentlik
POST /enrollments metodi Idempotency-Key sarlavhasini talab qiladi — operatsiyaning noyob kaliti, masalan CRM’dagi buyurtma raqami (128 belgigacha: harflar, raqamlar, . _ : -). U so‘rovni takrorlashni xavfsiz qiladi: taym-aut sababli javob yo‘qolsa, so‘rovni shu kalit bilan takrorlang — talaba ikki marta qabul qilinmaydi.
| Vaziyat | Natija |
|---|---|
Bir xil Idempotency-Key va bir xil tana | Bir xil javob, takroriy harakat yo‘q |
Bir xil Idempotency-Key, lekin boshqa tana | 409 idempotency_conflict |
| Shu kalitli so‘rov hali bajarilmoqda | 409 idempotency_in_progress — kutib, takrorlang |
| Sarlavha berilmagan | 422 idempotency_key_required |
{
"error": "idempotency_conflict",
"message": "Этот Idempotency-Key уже использован с другим телом запроса"
}Buni qayta xarid bilan adashtirmang: talaba kursga allaqachon qabul qilingan bo‘lsa va siz yangi operatsiya yuborsangiz (boshqa Idempotency-Key), kirish muddati uzaytiriladi — «Kirish muddati» bo‘limiga qarang.
Kirish muddati
Kirish muddati quyidagicha aniqlanadi:
- So‘rovda
accessobyekti berilsa, muddat undan olinadi:mode=days(amount1 dan 3650 gacha) yokimonths(amount1 dan 120 gacha). accessberilmasa, muddat kurs sozlamalaridan olinadi. UlarniGET /coursesjavobidagiaccessmaydonida ko‘rish mumkin:mode=days/monthsvaamount, yokiunlimited(muddatsiz kirish,amount=null).
{
"phone": "+77001234567",
"courseId": 1234,
"access": {
"mode": "months",
"amount": 3
}
}Javoblarda va qabul holatida: lastDay — kirishning oxirgi kuni, expireDate — kirish yopiladigan chegara; null — muddatsiz kirish yoki sana hali noma’lum.
Qayta xarid. Talaba bu kursga avval qabul qilingan bo‘lsa, kirish darhol uzaytiriladi: hisob ikki sananing kattasidan — bugundan yoki kirishning joriy tugashidan — boshlanadi. Javob — 200, status: done (navbatsiz).
Talabaga xabarnomalar
API orqali qabul kabinetdan qo‘lda qabul qilish bilan bir xil navbat orqali o‘tadi. Talaba bir xil xabarlarni oladi: WhatsApp / WABA / Telegram / pochta.
notifyparametri (odatiytrue);falseqiymati shu qabul uchun xabarnomalarni o‘chiradi.- Qayta xaridda (uzaytirish) talaba Telegram’da xabarnoma oladi — kabinetdagi «Kirishni uzaytirish» kabi.
- Qabul holatida
notified: truexabarnoma yuborishga qo‘yilganini bildiradi. WhatsApp/Telegram’ga yetkazilishini xizmat tasdiqlamaydi.
{
"phone": "+77001234567",
"courseId": 1234,
"notify": false
}Limitlar
Cheklovlar platformani va talabalaringizni integratsiyadagi tasodifiy sikllardan himoya qiladi.
| Cheklov | Qiymat | Oshib ketsa |
|---|---|---|
| So‘rovlar | kalitga daqiqasiga 60 | 429 rate_limited va Retry-After sarlavhasi (takrorlashgacha soniyalar) |
| Qabullar | maktabga sutkasiga 300 | 429 daily_limit |
| So‘rov tanasi hajmi | 4 KB gacha | 422 payload_too_large |
| Faol kalitlar | maktabga 3 tagacha | Keraksiz kalitni bekor qilib, yangisini yarating |
So‘rovlar limiti oshib ketsa, javobda Retry-After sarlavhasi bo‘ladi — takrorlashgacha soniyalar soni. Bugungi foydalanish kabinetdagi «Maktab API» kartochkasida ko‘rinadi.
Xatolar
Barcha xatolar kod va tavsifli JSON ko‘rinishida keladi:
{
"error": "validation_error",
"message": "phone: некорректный номер телефона"
}HTTP holatiga va error maydoniga (mashina kodi) tayaning; message matni jurnallar uchun mo‘ljallangan.
| Kod | HTTP | Ma’nosi | Nima qilish kerak |
|---|---|---|---|
bad_key | 401 | Kalit berilmagan, noto‘g‘ri yoki bekor qilingan | X-School-Key sarlavhasini tekshiring; kerak bo‘lsa, kabinetda yangi kalit yarating |
key_expired | 401 | Kalitning amal qilish muddati tugagan («gacha amal qiladi») | Kabinetda yangi kalit yarating |
tariff_not_allowed | 403 | Maktab tarifi API’ga kirishni bermaydi (Pro yillik, Lifetime yoki Enterprise kerak). Har bir so‘rovda tekshiriladi | Mos tarifga o‘ting |
course_not_found | 404 | Kurs sizning maktabingizda topilmadi, jumladan boshqa maktabga tegishli kurs | courseId ni GET /courses javobi bilan solishtiring |
validation_error | 422 | So‘rov ma’lumotlari noto‘g‘ri (maydon message da ko‘rsatiladi) | So‘rovni message tavsifiga ko‘ra tuzating |
invalid_json | 422 | So‘rov tanasi yaroqli JSON emas | JSON va Content-Type sarlavhasini tekshiring |
payload_too_large | 422 | So‘rov tanasi 4 KB dan katta | So‘rov tanasini qisqartiring |
idempotency_key_required | 422 | POST /enrollments uchun Idempotency-Key berilmagan | Sarlavhani qo‘shing, masalan buyurtma raqami bilan |
idempotency_conflict | 409 | Bu Idempotency-Key so‘rovning boshqa tanasi bilan ishlatilgan | Yangi operatsiya uchun yangi kalit ishlating yoki asl tanani yuboring |
idempotency_in_progress | 409 | Shu kalitli so‘rov hali bajarilmoqda | Kuting va shu kalit bilan takrorlang |
course_locked | 409 | Kurs muzlatilgan | Kursni kabinetda muzlatishdan chiqaring yoki boshqasini tanlang |
course_closed | 409 | Kurs e’lon qilinmagan (masalan, qoralama) | Kursni e’lon qiling |
student_limit_reached | 409 | Tarif bo‘yicha talabalar limitiga yetildi | Joy bo‘shating yoki limiti yuqori tarifga o‘ting |
rate_limited | 429 | Kalitga daqiqasiga 60 tadan ortiq so‘rov | Retry-After soniya kutib, takrorlang |
daily_limit | 429 | Maktabga sutkasiga 300 tadan ortiq qabul | Keyinroq takrorlang, Retry-After ga tayaning |
failed holatidagi qabul uchun error maydonida not_confirmed bo‘ladi: 15 daqiqa ichida qabul tasdiqlanmadi.
Maxfiylik
API faqat sizning maktabingiz ma’lumotlarini ko‘rsatadi.
POST /studentsjavobidagicreated: truetalaba sizning maktabingizda hali bo‘lmaganini bildiradi — uning akkaunti platformada boshqa maktabda mavjud bo‘lsa ham. Maktabingiz uchun bog‘lanish yaratiladi, begona ma’lumotlar ochilmaydi.created: false— talaba sizning maktabingizda allaqachon o‘qiydi. Mavjud talabaning ismi va pochtasi qayta yozilmaydi.- Maktabingiz bilan bog‘lanmagan talaba uchun kirishni tekshirish mavjud bo‘lmagan talaba bilan bir xil javob beradi:
active: false. GET /coursesfaqat kalit egasi maktabining kurslarini qaytaradi; begona kurs404 course_not_foundjavobini beradi.