Courstore API
Мектеп API
Мектебіңіздің курстарына студенттерді CRM-нен немесе басқа жүйеден қабылдаңыз. Төлем сіздің жағыңызда қалады, ал Courstore қолжетімділікті ашып, студентке хабарлайды.
https://api.courstore.com/v1/school-apiSwagger спецификациясы →Шолу
School API сіздің жүйеңізге (CRM, төлем сервисі, өз сайтыңыз) студенттерді Courstore-дағы мектеп курстарына қабылдауға және олардың қолжетімділігін тексеруге мүмкіндік береді. Төлемді өзіңіз қабылдайсыз — API тек курсқа қолжетімділікті ашады.
- Негізгі URL:
https://api.courstore.com/v1/school-api - Формат: JSON. Сұраныс денесі —
Content-Type: application/json, көлемі 4 КБ-қа дейін. - Авторизация:
X-School-Keyтақырыбындағы мектеп кілті. - Қолжетімділік: Pro (жылдық), Lifetime және Enterprise тарифтері. Қалған тарифтерде API
403 tariff_not_allowedқайтарады — тариф әр сұраныста тексеріледі. - Қателер әрқашан JSON түрінде келеді:
{"error": "код", "message": "мәтін"}.
Толық машина оқитын спецификация: Swagger.
| Әдіс | URL | Мақсаты |
|---|---|---|
| GET | /courses | Мектеп курстарының тізімі және әдепкі қолжетімділік мерзімі |
| POST | /students | Студентті телефон бойынша табу немесе құру |
| POST | /enrollments | Студентті курсқа қабылдау (студент жоқ болса, құрады) |
| GET | /enrollments/{requestId} | requestId бойынша қабылдау мәртебесі |
| GET | /enrollments?phone=&courseId= | Студенттің курсқа ағымдағы қолжетімділігі |
Жылдам бастау
- Тарифті тексеріңіз. API Pro (жылдық), Lifetime және Enterprise тарифтерінде қолжетімді.
- Кілт алыңыз. Мектеп кабинетінде «Интеграциялар» → «Мектеп API» → «Кілт жасау» бөлімін ашыңыз.
cs_live_…түріндегі кілт бір рет қана көрсетіледі — бірден сенімді жерге сақтап қойыңыз. 3 белсенді кілтке дейін ұстауға болады, кез келгенін қайтарып алуға болады. - Алғашқы сұранысты жіберіңіз. Мектеп курстарының тізімін алыңыз — жауапта қабылдауға қажетті курстардың
idмәндері болады.
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
}
}
]Содан кейін студентті курсқа қабылдаңыз — толығырақ «Курсқа қабылдау» әдісінде:
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 мәртебесі бар 202 жауабы қабылдау кезекке қабылданғанын білдіреді. Нәтижені «Қабылдау мәртебесі» әдісінен алыңыз (әдетте бірнеше секундтан кейін дайын болады).
Аутентификация
Әр сұраныста мектеп кілті бар X-School-Key тақырыбы болуы тиіс:
curl -s "https://api.courstore.com/v1/school-api/courses" \
-H "X-School-Key: cs_live_..."- Кілт кабинетте жасалады: «Интеграциялар» → «Мектеп API». Пішімі —
cs_live_…, бір рет көрсетіледі. - Мектепке 3 белсенді кілтке дейін беріледі. Кез келген кілтті сол карточкада қайтарып алуға болады; оны қолданатын интеграциялар жұмысын тоқтатады.
- Кілтті серверіңізде сақтаңыз. Оны мобильді қосымшаларға, браузер кодына және ашық репозиторийлерге орналастырмаңыз.
- Мерзім міндетті емес: кілтті шығарғанда «дейін жарамды» күнін көрсетуге болады (осы күнді қоса). Одан кейін кілт
401 key_expiredқайтарады — жаңасын шығарыңыз. - Мектептің тарифі аяқталғанда оның барлық кілттері автоматты түрде және біржола қайтарып алынады; тарифті ұзартқаннан кейін жаңа кілт шығарыңыз. Қайтарып алынған кілт
401 bad_keyалады. - Кілт берілмесе, қате болса немесе қайтарып алынса —
401жауабы келеді:
{
"error": "bad_key",
"message": "Неверный ключ API"
}Кілт дұрыс, бірақ мектеп тарифі API-ге қолжетімділік бермесе — 403 tariff_not_allowed. «Мектеп API» карточкасында бүгінгі кілт қолданысы мен соңғы қателер көрінеді.
Әдістер
Төмендегі мысалдарда «Жылдам бастау» бөліміндегі $BASE және $KEY айнымалылары қолданылады. Жолдар негізгі URL — https://api.courstore.com/v1/school-api — қатысты көрсетілген.
Мектеп курстары
/coursesКілт иесі мектептің курстарын қайтарады. access өрісі — курс баптауларындағы әдепкі қолжетімділік мерзімі (қабылдау кезінде access берілмесе қолданылады).
Параметрлер жоқ.
Сұраныс мысалы
curl -s "$BASE/courses" \
-H "X-School-Key: $KEY"Жауап өрістері
| Өріс | Түрі | Сипаттама |
|---|---|---|
id | integer | Курс идентификаторы — оны courseId ретінде береді |
title | string | Курс атауы |
slug | string | Мекенжайдағы курстың қысқа атауы |
status | string | publish | private | draft | pending. Тек жарияланған курсқа қабылдауға болады |
access.mode | string | days | months | unlimited |
access.amount | integer | null | Күн немесе ай саны; unlimited кезінде null |
Жауап мысалы
[
{
"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
}
}
]Әдіс қателері
| HTTP | Код | Мәні |
|---|---|---|
| 401 | bad_key | Кілт берілмеген, қате немесе қайтарып алынған |
| 401 | key_expired | Кілттің мерзімі өткен |
| 403 | tariff_not_allowed | Мектеп тарифі API-ге қолжетімділік бермейді |
| 429 | rate_limited | Минутына 60 сұраныс лимиті асып кетті |
Студентті табу немесе құру
/studentsСтудентті телефон бойынша іздеп, қайтарады; сіздің мектебіңізде мұндай студент болмаса — құрады. Телефон қалыпқа келтіріледі (+7 / 8 / бос орындар / жақшалар). Бар студенттің аты мен поштасы қайта жазылмайды. Жаңа студент құпиясөзсіз құрылады — кіру код арқылы.
Сұраныс денесі (JSON)
| Параметр | Түрі | Міндетті | Сипаттама |
|---|---|---|---|
phone | string | иә | Кез келген пішімдегі телефон |
name | string | жоқ | Аты, 200 таңбаға дейін |
email | string | жоқ | Пошта, 200 таңбаға дейін |
lang | string | жоқ | Студент тілі: ru | kk | en | uz. Әдепкі ru |
Сұраныс мысалы
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"}'Жауап өрістері
| Өріс | Түрі | Сипаттама |
|---|---|---|
studentId | integer | Студент идентификаторы |
created | boolean | true — студент сіздің мектебіңізде әлі болмаған (жауап 201); false — бұрыннан оқиды (жауап 200) |
Жауап мысалы
{
"studentId": 118350,
"created": true
}{
"studentId": 118347,
"created": false
}Әдіс қателері
| HTTP | Код | Мәні |
|---|---|---|
| 401 | bad_key | Кілт берілмеген, қате немесе қайтарып алынған |
| 401 | key_expired | Кілттің мерзімі өткен |
| 403 | tariff_not_allowed | Мектеп тарифі API-ге қолжетімділік бермейді |
| 409 | student_limit_reached | Тариф бойынша студенттер лимитіне жеттіңіз |
| 422 | validation_error | Деректер қате, мысалы телефон нөмірі; сондай-ақ invalid_json және payload_too_large |
| 429 | rate_limited | Минутына 60 сұраныс лимиті асып кетті |
Курсқа қабылдау
/enrollmentsСтудентті курсқа қабылдайды. Мұндай телефоны бар студент болмаса, ол құрылады. Қолжетімділік мерзімі access берілсе, содан, әйтпесе курс баптауларынан алынады. Idempotency-Key тақырыбы міндетті.
Тақырып және сұраныс денесі
| Параметр | Түрі | Міндетті | Сипаттама |
|---|---|---|---|
Idempotency-Key | header, string | иә | Операцияның бірегей кілті (мысалы, CRM-дегі тапсырыс нөмірі), 128 таңбаға дейін: әріптер, цифрлар, . _ : - |
phone | string | иә | Студенттің кез келген пішімдегі телефоны |
courseId | integer | иә | GET /courses жауабындағы курс идентификаторы |
name | string | жоқ | Аты, 200 таңбаға дейін (жаңа студент үшін қолданылады) |
email | string | жоқ | Пошта, 200 таңбаға дейін |
access | object | жоқ | Қолжетімділік мерзімі. Берілмесе — курс баптауларынан |
access.mode | string | жоқ | days | months |
access.amount | integer | жоқ | days: 1…3650; months: 1…120 |
notify | boolean | жоқ | Студентке қабылдау туралы хабарландыру жіберу. Әдепкі true |
orderRef | string | жоқ | Сіздің тапсырыс нөміріңіз, 128 таңбаға дейін (өз есебіңіз үшін) |
Сұраныс мысалы
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"}'Жауап өрістері
| Өріс | Түрі | Сипаттама |
|---|---|---|
requestId | string | Сұраныс идентификаторы — GET /enrollments/{requestId} үшін |
status | string | queued — кезекке қабылданды; done — орындалды |
studentId | integer | null | Студент идентификаторы |
enrollmentId | integer | null | Қабылдау идентификаторы; сұраныс кезекте тұрғанда null |
expireDate | date | null | Қолжетімділікті жабу шекарасы; null — мерзімсіз немесе әлі белгісіз |
lastDay | date | null | Қолжетімділіктің соңғы күні |
Жауап мысалы
{
"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"
}Жаңа қабылдау — жауап 202, status: queued. Қабылдау мен хабарландырулар шамамен бір минут ішінде орындалады (іс жүзінде — секундтар); нәтиже — GET /enrollments/{requestId} әдісінде.
Қайта сатып алу (студент курсқа бұрын қабылданған) — қолжетімділік бірден ұзартылады, жауап 200, status: done.
Әдіс қателері
| HTTP | Код | Мәні |
|---|---|---|
| 401 | bad_key | Кілт берілмеген, қате немесе қайтарып алынған |
| 401 | key_expired | Кілттің мерзімі өткен |
| 403 | tariff_not_allowed | Мектеп тарифі API-ге қолжетімділік бермейді |
| 404 | course_not_found | Курс сіздің мектебіңізде табылмады (бөтен курсты қоса) |
| 409 | idempotency_conflict | Бұл Idempotency-Key басқа денемен қолданылып қойған |
| 409 | idempotency_in_progress | Осы кілтпен сұраныс әлі орындалып жатыр |
| 409 | course_locked | Курс тоңазытылған |
| 409 | course_closed | Курс жарияланбаған (мысалы, жоба) |
| 409 | student_limit_reached | Тариф бойынша студенттер лимитіне жеттіңіз |
| 422 | idempotency_key_required | Idempotency-Key тақырыбы берілмеген; сондай-ақ validation_error, invalid_json, payload_too_large |
| 429 | rate_limited | Минутына 60 сұраныс лимиті асып кетті |
| 429 | daily_limit | Мектепке тәулігіне 300 қабылдау лимиті асып кетті |
Қабылдау мәртебесі
/enrollments/{requestId}POST /enrollments әдісі қабылдаған қабылдаудың күйін қайтарады.
Жол параметрлері
| Параметр | Түрі | Міндетті | Сипаттама |
|---|---|---|---|
requestId | string | иә | Қабылдау жауабындағы идентификатор, мысалы req_882 |
Сұраныс мысалы
curl -s "$BASE/enrollments/req_882" \
-H "X-School-Key: $KEY"Жауап өрістері
| Өріс | Түрі | Сипаттама |
|---|---|---|
requestId | string | Сұраныс идентификаторы |
status | string | queued — өңдеуді күтуде; done — студент қабылданды; failed — 15 минут ішінде қабылдау расталмады |
studentId | integer | null | Студент идентификаторы |
courseId | integer | null | Курс идентификаторы |
expireDate | date | null | Қолжетімділікті жабу шекарасы |
lastDay | date | null | Қолжетімділіктің соңғы күні |
notified | boolean | true — студентке хабарландыру жіберуге қойылды (жеткізуді сервис растамайды) |
error | string | null | failed кезіндегі себеп: not_confirmed |
Жауап мысалы
{
"requestId": "req_882",
"status": "done",
"studentId": 118350,
"courseId": 1234,
"expireDate": "2027-01-20",
"lastDay": "2027-01-19",
"notified": true,
"error": null
}Әдіс қателері
| HTTP | Код | Мәні |
|---|---|---|
| 401 | bad_key | Кілт берілмеген, қате немесе қайтарып алынған |
| 401 | key_expired | Кілттің мерзімі өткен |
| 403 | tariff_not_allowed | Мектеп тарифі API-ге қолжетімділік бермейді |
| 404 | — | Мұндай requestId бар сұраныс табылмады |
| 429 | rate_limited | Минутына 60 сұраныс лимиті асып кетті |
Қолжетімділікті тексеру
/enrollments?phone=&courseId=Студенттің мектебіңіздің курсына ағымдағы қолжетімділігін көрсетеді. Мектебіңізбен байланысы жоқ студент жоқ студентпен бірдей жауап береді: active: false.
Сұраныс параметрлері (query)
| Параметр | Түрі | Міндетті | Сипаттама |
|---|---|---|---|
phone | string | иә | Студент телефоны. URL-дегі + белгісін %2B түрінде беріңіз |
courseId | integer | иә | Курс идентификаторы |
Сұраныс мысалы
curl -s "$BASE/enrollments?phone=%2B77001234567&courseId=1234" \
-H "X-School-Key: $KEY"Жауап өрістері
| Өріс | Түрі | Сипаттама |
|---|---|---|
active | boolean | Студентте әрекет етуші қолжетімділік бар ма |
expireDate | date | null | Қолжетімділікті жабу шекарасы |
lastDay | date | null | Қолжетімділіктің соңғы күні |
progress | integer | Курс бойынша прогресс, % |
Жауап мысалы
{
"active": true,
"expireDate": "2026-12-01",
"lastDay": "2026-11-30",
"progress": 35
}Әдіс қателері
| HTTP | Код | Мәні |
|---|---|---|
| 401 | bad_key | Кілт берілмеген, қате немесе қайтарып алынған |
| 401 | key_expired | Кілттің мерзімі өткен |
| 403 | tariff_not_allowed | Мектеп тарифі API-ге қолжетімділік бермейді |
| 404 | course_not_found | Курс сіздің мектебіңізде табылмады |
| 422 | validation_error | phone / courseId берілмеген немесе қате |
| 429 | rate_limited | Минутына 60 сұраныс лимиті асып кетті |
Оқушыға арналған кіру сілтемесі
Сіздің сервис оқушыға бір реттік сілтеме бере алады: ол құпиясөзсіз Courstore-дағы мектебіңіздің кабинетін ашады. Сілтемедегі токен — 32 кездейсоқ байт, бізде тек хеш сақталады, бірінші қолданудан кейін сілтеме жарамсыз болады.
Courstore-ға кіру сілтемесі
Оқушы сіздің сервиске кірген — оған Courstore-дағы мектебіңіздің кабинетін құпиясөзсіз ашатын сілтеме беріңіз. Сілтеме бір реттік.
/students/login-linkСұраныс денесі
| Параметр | Түрі | Міндетті | Сипаттама |
|---|---|---|---|
phone | string | иә | Оқушының кез келген форматтағы телефоны |
redirect | integer | жоқ | Мектебіңіз курсының id-і: кіргеннен кейін курс беті ашылады. Онсыз — «Менің курстарым» |
ttlSeconds | integer | жоқ | Сілтеменің жарамдылық мерзімі, 30-дан 86400 секундқа дейін, әдепкі 120 |
Сұраныс мысалы
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}'Жауап мысалы
{
"url": "https://myschool.courstore.com/ru/login-via-token?token=csl_...&returnUrl=courses%2Fosnovy-marketinga",
"expiresAt": "2026-10-16T10:02:00Z"
}expiresAt — сілтеме қай уақытқа дейін (UTC) жарамды.
Қандай мерзімді таңдау керек
| Сценарий | ttlSeconds | Неліктен |
|---|---|---|
| Сайттағы «Courstore-ға өту» батырмасы: сілтемені алып, бірден бағыттау | 120 (әдепкі) | Сілтеме бірнеше секундқа ғана керек; қысқа мерзім — мекенжай бір жерге тарап кетсе, қауіп аз |
| Хаттағы, WhatsApp немесе Telegram-дағы сілтеме | 86400 дейін (24 сағ) | Оқушы хабарламаны кейінірек ашуы мүмкін. Сілтеме бәрібір бір рет қана жұмыс істейді |
curl -s -X POST "$BASE/students/login-link" \
-H "X-School-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"phone":"+77001234567","ttlSeconds":86400}'Сілтеменің мерзімі өтсе немесе ол ашылып қойса, оқушы қате көрмейді: ол мектебіңіздің кәдімгі кіру бетіне «Сілтеменің мерзімі өтті — телефон нөмірі арқылы кіріңіз» деген кеңеспен түседі.
Сервердегі мысал
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);
});Әдіс қателері
| HTTP | Код | Мәні |
|---|---|---|
| 404 | student_not_found | Нөмір мектебіңіздің оқушысына тиесілі емес. Авторға, қызметкерлерге және әкімшілерге кіру сілтемесі ешқашан берілмейді — оларға да осы жауап |
| 404 | course_not_found | redirect ішіндегі курс мектебіңізге тиесілі емес |
| 422 | validation_error | phone, redirect қате немесе ttlSeconds 30…86400 аралығынан тыс |
| 429 | daily_limit | Мектепке тәулігіне 2000-нан астам сілтеме; Retry-After тақырыбын қараңыз |
| 429 | rate_limited | Кілтке минутына 60-тан астам сұраныс (API-дің жалпы лимиті) |
Қауіпсіздік. Сілтеме оқушы аккаунтына кірумен бірдей: оны логқа жазбаңыз, үшінші тұлғаларға жібермеңіз және тек сіздің сервиске кірген оқушыға ғана беріңіз.
Вебхуктар
Вебхуктар жазылулар мен қолжетімділік өзгерістері туралы жүйеңізге API-ді сұрамай-ақ бірден хабарлайды: Courstore сіздің мекенжайыңызға өзі POST жібереді.
Баптау
- Кабинетте: «Интеграциялар» → «Мектеп API» → «Вебхуктар» → URL. Тек
https://және тек жария мекенжай: ішкі және жергілікті мекенжайларға, IP-мекенжайларға тыйым салынған. - Алғаш сақтағанда
whsec_…қолтаңба құпиясы беріледі. Ол бір рет көрсетіледі — бірден сақтап алыңыз. Құпияны қайта шығаруға болады: ескісі бірден жарамсыз болады. - «Тест жіберу» батырмасы
webhook.testоқиғасын жібереді. Журналда соңғы 20 жеткізу көрінеді: оқиға, жауап коды, әрекет, уақыт.
Оқиғалар
Оқиға түрі type өрісінде және X-Courstore-Event тақырыбында келеді.
type | Қашан келеді | accessUntil |
|---|---|---|
enrollment.created | Оқушы курсқа жазылды: төлем, кабинеттегі қолмен жазу, School API | Қолжетімділіктің соңғы күні немесе null |
access.extended | Қолжетімділік ұзартылды немесе курс қайта сатып алынды | Жаңа соңғы күн |
access.expired | Қолжетімділік мерзімі аяқталды. Тек вебхук қосылғаннан кейінгі аяқталуларға келеді | Қолжетімділіктің соңғы күні |
access.revoked | Автор оқушының курсқа қолжетімділігін жапты | Өріс жоқ |
webhook.test | Кабинеттегі «Тест жіберу» батырмасы | — |
accessUntil — қолжетімділіктің соңғы күні, қоса алғанда (YYYY-MM-DD), null — мерзімсіз.
Сұраныс денесі
{
"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": "Основы маркетинга"
}
}
}Жауап өрістері
| Өріс | Түрі | Сипаттама |
|---|---|---|
id | string | Оқиғаның бірегей id-і evt_…; X-Courstore-Delivery-мен сәйкес. Идемпотенттілік кілті |
type | string | Оқиға түрі |
createdAt | date-time | Оқиға болған уақыт (UTC) |
schoolId | integer | Courstore-дағы мектебіңіздің id-і |
data.enrollmentId | integer | Жазылу id-і |
data.student | object | Оқушының id, name, phone, email |
data.course | object | Курстың id және title |
data.accessUntil | date | null | Қолжетімділіктің соңғы күні; null — мерзімсіз. access.revoked ішінде өріс жоқ |
Тақырыптар мен қолтаңба
| Тақырып | Сипаттама |
|---|---|
X-Courstore-Event | Оқиға түрі |
X-Courstore-Delivery | Оқиға id-і (денедегі id-мен бірдей) — қайталаудан қорғану үшін |
X-Courstore-Signature | t=<unix-уақыт>,v1=<hex>, мұнда v1 = HMAC-SHA256(құпия, t + "." + шикі дене) |
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…- Сұраныстың шикі денесін алыңыз — JSON талдауға дейін: кез келген қайта пішімдеу қолтаңбаны бұзады.
whsec_…құпиясыменt + "." + денежолынан HMAC-SHA256 есептеңіз, hex түрінде.- Оны
v1-мен тұрақты уақыттағы салыстырумен салыстырыңыз (timingSafeEqual,hash_equals,compare_digest). tмәні 5 минуттан ескі сұраныстарды қабылдамаңыз — бұл ұсталған сұранысты қайта жіберуден қорғайды.
Қолтаңбаны тексеру мысалы
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 "", 200Жеткізу және қайталау
- 10 секунд ішінде
2xxдеп жауап беріңіз. Ауыр өңдеуді жауаптан кейін өз кезегіңізде жасаңыз. - Кез келген басқа жауап, күту уақытының өтуі немесе қосылу қатесі — жеткізу төмендегі кесте бойынша қайталанады, барлығы 6 әрекет.
- Қайта бағыттаулар (
3xx) орындалмайды және сәтсіздік болып саналады — соңғы мекенжайды көрсетіңіз. - Оқиғалар реті кепілдендірілмейді, бір оқиға екі рет келуі мүмкін —
idбойынша идемпотентті өңдеңіз. - Оқиғалар 1–2 минутқа дейін кешігіп келеді, қолжетімділіктің аяқталуы (
access.expired) — 30 минутқа дейін.
| Әрекет | Қашан |
|---|---|
| 1 | Оқиғадан кейін бірден |
| 2 | 1 минуттан кейін |
| 3 | 5 минуттан кейін |
| 4 | 30 минуттан кейін |
| 5 | 2 сағаттан кейін |
| 6 | 6 сағаттан кейін |
Әр әрекеттің нәтижесі «Мектеп API» карточкасындағы жеткізу журналында көрінеді.
Идемпотенттілік
POST /enrollments әдісі Idempotency-Key тақырыбын талап етеді — операцияның бірегей кілті, мысалы CRM-дегі тапсырыс нөмірі (128 таңбаға дейін: әріптер, цифрлар, . _ : -). Ол сұранысты қайталауды қауіпсіз етеді: тайм-аутқа байланысты жауап жоғалса, сұранысты сол кілтпен қайталаңыз — студент екі рет қабылданбайды.
| Жағдай | Нәтиже |
|---|---|
Сол Idempotency-Key және сол дене | Сол жауап, қайталанған әрекет жоқ |
Сол Idempotency-Key, бірақ басқа дене | 409 idempotency_conflict |
| Осы кілтпен сұраныс әлі орындалып жатыр | 409 idempotency_in_progress — күтіп, қайталаңыз |
| Тақырып берілмеген | 422 idempotency_key_required |
{
"error": "idempotency_conflict",
"message": "Этот Idempotency-Key уже использован с другим телом запроса"
}Мұны қайта сатып алумен шатастырмаңыз: студент курсқа қабылданып қойған болса және сіз жаңа операция жіберсеңіз (басқа Idempotency-Key), қолжетімділік ұзартылады — «Қолжетімділік мерзімі» бөлімін қараңыз.
Қолжетімділік мерзімі
Қолжетімділік мерзімі былай анықталады:
- Сұраныста
accessнысаны берілсе, мерзім содан алынады:mode=days(amount1-ден 3650-ге дейін) немесеmonths(amount1-ден 120-ға дейін). accessберілмесе, мерзім курс баптауларынан алынады. ОлардыGET /coursesжауабындағыaccessөрісінен көруге болады:mode=days/monthsжәнеamount, не болмасаunlimited(мерзімсіз қолжетімділік,amount=null).
{
"phone": "+77001234567",
"courseId": 1234,
"access": {
"mode": "months",
"amount": 3
}
}Жауаптар мен қабылдау мәртебесінде: lastDay — қолжетімділіктің соңғы күні, expireDate — қолжетімділікті жабу шекарасы; null — мерзімсіз қолжетімділік немесе күні әлі белгісіз.
Қайта сатып алу. Студент осы курсқа бұрын қабылданған болса, қолжетімділік бірден ұзартылады: есеп екі күннің үлкенінен басталады — бүгіннен немесе қолжетімділіктің ағымдағы аяқталуынан. Жауап — 200, status: done (кезексіз).
Студентке хабарландыру
API арқылы қабылдау кабинеттен қолмен қабылдаумен бірдей кезек арқылы өтеді. Студент сол хабарламаларды алады: WhatsApp / WABA / Telegram / пошта.
notifyпараметрі (әдепкі бойыншаtrue);falseмәні осы қабылдау үшін хабарландыруларды өшіреді.- Қайта сатып алғанда (ұзарту) студент Telegram-да хабарландыру алады — кабинеттегі «Қолжетімділікті ұзарту» сияқты.
- Қабылдау мәртебесінде
notified: trueхабарландыру жіберуге қойылғанын білдіреді. WhatsApp/Telegram-ға жеткізуді сервис растамайды.
{
"phone": "+77001234567",
"courseId": 1234,
"notify": false
}Лимиттер
Шектеулер платформаны және студенттеріңізді интеграциядағы кездейсоқ циклдерден қорғайды.
| Шектеу | Мәні | Асып кетсе |
|---|---|---|
| Сұраныстар | кілтке минутына 60 | 429 rate_limited және Retry-After тақырыбы (қайталауға дейінгі секунд) |
| Қабылдаулар | мектепке тәулігіне 300 | 429 daily_limit |
| Сұраныс денесінің көлемі | 4 КБ-қа дейін | 422 payload_too_large |
| Белсенді кілттер | мектепке 3-ке дейін | Қажет емес кілтті қайтарып алып, жаңасын жасаңыз |
Сұраныс лимиті асып кетсе, жауапта Retry-After тақырыбы болады — қайталауға дейінгі секунд саны. Бүгінгі қолданыс кабинеттегі «Мектеп API» карточкасында көрінеді.
Қателер
Барлық қателер код пен сипаттамасы бар JSON түрінде келеді:
{
"error": "validation_error",
"message": "phone: некорректный номер телефона"
}HTTP-мәртебеге және error өрісіне (машиналық код) сүйеніңіз; message мәтіні журналдарға арналған.
| Код | HTTP | Мәні | Не істеу керек |
|---|---|---|---|
bad_key | 401 | Кілт берілмеген, қате немесе қайтарып алынған | X-School-Key тақырыбын тексеріңіз; қажет болса, кабинетте жаңа кілт жасаңыз |
key_expired | 401 | Кілттің әрекет ету мерзімі өткен («дейін жарамды») | Кабинетте жаңа кілт шығарыңыз |
tariff_not_allowed | 403 | Мектеп тарифі API-ге қолжетімділік бермейді (Pro жылдық, Lifetime немесе Enterprise қажет). Әр сұраныста тексеріледі | Қолайлы тарифке өтіңіз |
course_not_found | 404 | Курс сіздің мектебіңізде табылмады, оның ішінде басқа мектептің курсы | courseId мәнін GET /courses жауабымен салыстырыңыз |
validation_error | 422 | Сұраныс деректері қате (өріс message ішінде көрсетіледі) | Сұранысты message сипаттамасы бойынша түзетіңіз |
invalid_json | 422 | Сұраныс денесі жарамды JSON емес | JSON-ды және Content-Type тақырыбын тексеріңіз |
payload_too_large | 422 | Сұраныс денесі 4 КБ-тан үлкен | Сұраныс денесін қысқартыңыз |
idempotency_key_required | 422 | POST /enrollments үшін Idempotency-Key берілмеген | Тақырыпты қосыңыз, мысалы тапсырыс нөмірімен |
idempotency_conflict | 409 | Бұл Idempotency-Key сұраныстың басқа денесімен қолданылып қойған | Жаңа операцияға жаңа кілт қолданыңыз немесе бастапқы денені жіберіңіз |
idempotency_in_progress | 409 | Осы кілтпен сұраныс әлі орындалып жатыр | Күтіп, сол кілтпен қайталаңыз |
course_locked | 409 | Курс тоңазытылған | Курсты кабинетте тоңазытудан шығарыңыз немесе басқасын таңдаңыз |
course_closed | 409 | Курс жарияланбаған (мысалы, жоба) | Курсты жариялаңыз |
student_limit_reached | 409 | Тариф бойынша студенттер лимитіне жеттіңіз | Орын босатыңыз немесе лимиті жоғары тарифке өтіңіз |
rate_limited | 429 | Кілтке минутына 60-тан астам сұраныс | Retry-After секунд күтіп, қайталаңыз |
daily_limit | 429 | Мектепке тәулігіне 300-ден астам қабылдау | Кейінірек қайталаңыз, Retry-After мәніне сүйеніңіз |
failed мәртебесіндегі қабылдау үшін error өрісінде not_confirmed болады: 15 минут ішінде қабылдау расталмады.
Құпиялылық
API тек сіздің мектебіңіздің деректерін көрсетеді.
POST /studentsжауабындағыcreated: trueстудент сіздің мектебіңізде әлі болмағанын білдіреді — оның аккаунты платформада басқа мектепте бар болса да. Сіздің мектебіңіз үшін байланыс жасалады, бөтен деректер ашылмайды.created: false— студент сіздің мектебіңізде бұрыннан оқиды. Бар студенттің аты мен поштасы қайта жазылмайды.- Мектебіңізбен байланысы жоқ студент үшін қолжетімділікті тексеру жоқ студентпен бірдей жауап береді:
active: false. GET /coursesтек кілт иесі мектебінің курстарын қайтарады; бөтен курс404 course_not_foundжауабын береді.