Telegram Mini App

Kontrak backend dan panduan implementasi frontend Telegram Mini App menggunakan Sejoli Telegram dan Sejoli API.

Halaman ini adalah kontrak implementasi frontend Telegram Mini App (TMA). Sejoli Telegram memverifikasi identitas Telegram dan menghubungkannya ke user WordPress; Sejoli API menerbitkan user-session JWT dan menyediakan data identity-bound melalui /me/*.

Prasyarat

  • Sejoli Telegram aktif, berlisensi, dan Bot Token telah dikonfigurasi.
  • Sejoli API aktif dan berlisensi. TMA tidak menerbitkan session tanpa plugin ini.
  • Mini App dibuka dari bot Telegram sehingga Telegram.WebApp.initData tersedia.
  • Frontend hanya menggunakan HTTPS dan base URL WordPress yang telah ditentukan admin.
  • Jika binding via OTP diinginkan, aktifkan pengaturan Link Account via OTP pada Sejoli Telegram.

Pembagian Tanggung Jawab

KomponenTanggung jawab
Telegram clientMenyediakan Telegram.WebApp.initData.
Frontend TMAMengirim request, merender state, menyimpan session token, dan memanggil /me/*.
Sejoli TelegramMemverifikasi HMAC initData, binding Telegram, OTP, rate limit, dan unlink.
Sejoli APIMenerbitkan, memverifikasi, dan mencabut user-session JWT serta menyediakan /me/*.
Module tambahanMendaftarkan endpoint /me/* opsional melalui extensibility Sejoli API.

Base URL yang digunakan:

text
https://domain-anda.com/wp-json/sejoli/v1

State Machine Frontend

stateDiagram-v2
    [*] --> Booting
    Booting --> InvalidContext: initData kosong
    Booting --> NeedsLink: session needs_link
    Booting --> Authenticated: session authenticated
    Booting --> Failed: error backend

    NeedsLink --> ProfileOnly: link_method profile
    NeedsLink --> Credentials: link_method otp
    Credentials --> WaitingOTP: OTP terkirim
    WaitingOTP --> Authenticated: OTP valid
    WaitingOTP --> Credentials: OTP kedaluwarsa atau limit percobaan

    Authenticated --> Authenticated: panggil /me/*
    Authenticated --> NeedsLink: unlink berhasil
    Authenticated --> Booting: session invalid atau kedaluwarsa

Saat aplikasi dibuka, frontend selalu memulai dari POST /tma/session. Jangan menganggap token lama masih valid tanpa menangani response 401 dari endpoint /me/*.

1. Membuat atau Memulihkan Session

http
POST /wp-json/sejoli/v1/tma/session
Content-Type: application/json
json
{
  "init_data": "<Telegram.WebApp.initData>"
}

Jika akun Telegram sudah terhubung:

json
{
  "success": true,
  "status": "authenticated",
  "token": "<USER_SESSION_JWT>",
  "token_type": "Bearer",
  "expires_in": 604800,
  "user": {
    "id": 35,
    "display_name": "User Name",
    "email": "user@example.com"
  },
  "data": {
    "token": "<USER_SESSION_JWT>",
    "user": {
      "id": 35,
      "display_name": "User Name",
      "email": "user@example.com"
    }
  },
  "tg": {
    "id": "123456789",
    "username": "example_user"
  }
}

data dipertahankan sebagai envelope kompatibilitas. Implementasi baru dianjurkan membaca field top-level token, user, status, dan expires_in.

Jika akun belum terhubung dan OTP aktif:

json
{
  "success": true,
  "status": "needs_link",
  "needs_link": true,
  "telegram_user_id": "123456789",
  "telegram_username": "example_user",
  "link_method": "otp",
  "otp_enabled": true,
  "tg": {
    "id": "123456789",
    "username": "example_user",
    "first_name": "Example",
    "last_name": "User"
  }
}

Jika OTP dimatikan, response memakai:

json
{
  "status": "needs_link",
  "link_method": "profile",
  "otp_enabled": false
}

Pada state profile, frontend harus mengarahkan user agar login ke Member Area, membuka Profile, lalu memilih Connect Telegram. Jangan tetap menampilkan form password/OTP.

2. Meminta OTP

Endpoint ini hanya tersedia secara fungsional ketika OTP diaktifkan.

http
POST /wp-json/sejoli/v1/tma/link/request-otp
Content-Type: application/json
json
{
  "init_data": "<Telegram.WebApp.initData>",
  "email": "user@example.com",
  "password": "<PASSWORD_USER>"
}

Response berhasil:

json
{
  "success": true,
  "otp_sent": true,
  "delivery_channel": "telegram",
  "delivery_status": "reachable",
  "email_masked": "us***@example.com",
  "expires_in": 600,
  "message": "Kode verifikasi telah dikirim ke chat Telegram Anda."
}

OTP dikirim ke chat Telegram yang membuka Mini App, bukan ditampilkan frontend. Request dibatasi per Telegram ID dan per IP; frontend harus menghormati retry_after pada error 429.

3. Verifikasi OTP dan Binding

http
POST /wp-json/sejoli/v1/tma/link/verify
Content-Type: application/json
json
{
  "init_data": "<Telegram.WebApp.initData>",
  "email": "user@example.com",
  "otp": "123456"
}

Response berhasil sama dengan response authenticated dari /tma/session. Simpan token, lalu gunakan sebagai Bearer token.

Satu challenge menerima maksimal lima kode yang salah. Backend menghapus challenge pada kesalahan kelima; user harus meminta OTP baru.

4. Mengambil Data User dari Sejoli API

http
GET /wp-json/sejoli/v1/me
Authorization: Bearer <USER_SESSION_JWT>
Accept: application/json

Identitas user selalu berasal dari JWT. Jangan mengirim atau mempercayai user_id dari frontend untuk menentukan pemilik data.

Endpoint bawaan dan kontrak lengkap tersedia pada dokumentasi Sejoli API → Endpoint /me. Endpoint tambahan dapat muncul ketika snippet atau plugin lain mendaftarkannya melalui hook sejoli_api_after_register_routes.

Frontend harus memperlakukan module opsional sebagai capability:

  • 404 rest_no_route: fitur/module tidak tersedia; sembunyikan menu terkait.
  • 401 sejoli_session_invalid_token: hapus token lokal dan ulangi bootstrap session.
  • 403: user terautentikasi tetapi tidak memiliki izin.
http
POST /wp-json/sejoli/v1/tma/unlink
Authorization: Bearer <USER_SESSION_JWT>

Response:

json
{
  "success": true,
  "message": "Telegram binding removed."
}

Setelah sukses, frontend wajib menghapus token lokal dan kembali ke state needs_link. Unlink mencabut seluruh user-session JWT Sejoli API milik user tersebut, termasuk session yang diterbitkan melalui integrasi lain.

TypeScript Contract

ts
export interface TelegramIdentity {
  id: string;
  username?: string;
  first_name?: string;
  last_name?: string;
}

export interface SessionUser {
  id: number;
  display_name: string;
  email: string;
}

export interface AuthenticatedSession {
  success: true;
  status: "authenticated";
  token: string;
  token_type: "Bearer";
  expires_in: number;
  user: SessionUser;
  data: { token: string; user: SessionUser };
  tg: TelegramIdentity;
}

export interface NeedsLinkSession {
  success: true;
  status: "needs_link";
  needs_link: true;
  telegram_user_id: string;
  telegram_username?: string;
  link_method: "otp" | "profile";
  otp_enabled: boolean;
  tg: TelegramIdentity;
}

export interface RestError {
  code: string;
  message: string;
  data?: {
    status?: number;
    retry_after?: number;
    attempts_remaining?: number;
  };
}

export type TmaSession = AuthenticatedSession | NeedsLinkSession;

Contoh bootstrap minimal:

ts
const API_BASE = "https://domain-anda.com/wp-json/sejoli/v1";
const initData = window.Telegram?.WebApp?.initData ?? "";

async function createSession(): Promise<TmaSession> {
  if (!initData) throw new Error("Telegram Mini App context is unavailable");

  const response = await fetch(`${API_BASE}/tma/session`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ init_data: initData }),
  });

  const payload = await response.json();
  if (!response.ok) throw payload as RestError;
  return payload as TmaSession;
}

Error yang Harus Ditangani

HTTPCodeTindakan frontend
400/401tma_invalid_initdataTolak login; minta user membuka ulang Mini App dari bot.
401tma_initdata_expiredTutup atau reload Mini App untuk memperoleh initData baru.
500tma_no_bot_tokenTampilkan kesalahan konfigurasi dan arahkan ke admin.
503tma_api_unavailableTMA belum siap; Sejoli API tidak aktif/tersedia.
403tma_otp_disabledGanti UI ke flow Profile, jangan retry OTP.
401tma_invalid_credsTampilkan kredensial tidak valid tanpa mengungkap akun mana yang salah.
409tma_tg_already_boundTelegram sudah terhubung ke akun lain.
409tma_user_already_boundUser WordPress sudah terhubung ke Telegram lain.
429tma_otp_cooldownNonaktifkan tombol selama retry_after.
429tma_otp_rate_limitedNonaktifkan retry selama retry_after.
401tma_invalid_otpTampilkan sisa percobaan dari attempts_remaining.
410tma_otp_expiredKembali ke form permintaan OTP.
429tma_otp_attempts_exceededHapus input OTP dan minta challenge baru.
502tma_telegram_not_reachableMinta user membuka bot dan menekan Start.
502tma_telegram_delivery_failedTawarkan retry setelah jeda.
401sejoli_session_no_tokenKirim Bearer token.
401sejoli_session_invalid_tokenHapus session lokal dan bootstrap ulang.

Keamanan Implementasi

  • Kirim initData mentah; jangan parse lalu membangun ulang string sebelum request.
  • Backend memverifikasi HMAC, auth_date, umur maksimum, dan Telegram user object.
  • Jangan gunakan initDataUnsafe sebagai bukti autentikasi.
  • Jangan menaruh API key atau secret server dalam environment variable frontend yang ikut masuk bundle.
  • Jangan mencatat password, OTP, initData, atau JWT ke analytics dan error-reporting publik.
  • Simpan JWT sesingkat yang diperlukan dan hapus setelah unlink atau response session invalid.
  • Terapkan Content Security Policy dan batasi origin deployment frontend.
  • Webhook Secret adalah urusan Telegram Bot API → backend dan tidak pernah dikirim ke Mini App.

Cloudflare dan Rate Limit IP

Secara default backend hanya memakai REMOTE_ADDR. Jika origin benar-benar hanya dapat dicapai melalui Cloudflare, admin/developer dapat mengaktifkan penggunaan CF-Connecting-IP:

php
add_filter( 'sejoli_telegram_tma_trust_cloudflare_ip', '__return_true' );

Resolver juga dapat disesuaikan melalui filter sejoli_telegram_tma_client_ip, tetapi hasil akhirnya harus berupa IPv4 atau IPv6 yang valid.

Checklist Sebelum Rilis

  1. Buka Mini App dari bot dan pastikan /tma/session memvalidasi initData asli.
  2. Uji state akun terhubung, akun belum terhubung dengan OTP, dan mode Profile-only.
  3. Uji kredensial salah, OTP salah lima kali, cooldown, dan expiry.
  4. Uji bot yang belum pernah ditekan Start.
  5. Uji /me, endpoint admin dengan member biasa, dan module /me/* opsional yang tidak aktif.
  6. Uji unlink, lalu pastikan token lama menerima 401.
  7. Pastikan bundle frontend tidak mengandung Bot Token, Webhook Secret, API key, atau signing secret.
  8. Pastikan error log/analytics tidak merekam password, OTP, initData, atau JWT.

Last updated Aug 15, 2026