Skip to content

Events

A Mini App talks to the client over an event bridge. The app emits events named web_app_*; the client answers with events of its own. Everything the native chrome does — buttons, popups, haptics, theme, biometrics, storage — is one of these.

Not implemented

Mini Apps do not run yet — there is no client runtime and no SDK to include. See implementation status.

How the bridge works

The client delivers events into the page by calling, per the TZ:

js
window.Telegram.WebView.receiveEvent(name, params)

That namespace is written as Telegram in the specification — presumably for compatibility with existing Mini Apps, though the TZ does not say so, and does not say whether a GROM-branded alias will exist alongside it. Treat the namespace as unsettled.

The client must support every event listed here. An event a platform cannot service is answered with an UNSUPPORTED error rather than silence, which means your page can branch on capability instead of waiting on a timeout. Several events carry an explicit *_failed counterpart for exactly this.

App → client

Names only

The TZ lists these events by name and does not document their parameters. The descriptions below are the plain reading of each name; the payloads are not published, and this page will not invent them.

Where an event also exists in the Telegram Web Apps model its parameters are likely identical, but "likely" is not a schema. Anything you build against an assumed payload today is a guess you will have to re-check.

Interface

EventPurpose
web_app_readyThe page has finished initializing and can be shown
web_app_expandExpand the viewport to full height
web_app_closeClose the Mini App
web_app_setup_main_buttonConfigure the primary action button
web_app_setup_secondary_buttonConfigure the secondary action button
web_app_setup_back_buttonShow or hide the native back button
web_app_setup_settings_buttonShow or hide the settings entry
web_app_setup_closing_behaviorAsk for a confirmation prompt before closing
web_app_set_header_colorSet the header colour
web_app_set_background_colorSet the background colour
web_app_set_bottom_bar_colorSet the bottom bar colour
web_app_request_fullscreenEnter fullscreen
web_app_exit_fullscreenLeave fullscreen
web_app_toggle_orientation_lockLock or unlock screen orientation

Dialogs and system UI

EventPurpose
web_app_open_popupNative popup with buttons
web_app_open_linkOpen an external URL
web_app_open_tg_linkOpen an in-platform link
web_app_open_invoiceOpen a payment invoice
web_app_open_scan_qr_popupOpen the QR scanner
web_app_close_scan_qr_popupClose the QR scanner
web_app_read_text_from_clipboardRequest clipboard contents
web_app_trigger_haptic_feedbackFire a haptic

Information and permissions

EventPurpose
web_app_request_themeAsk for the current theme
web_app_request_viewportAsk for current viewport metrics
web_app_request_safe_areaAsk for device safe-area insets
web_app_request_content_safe_areaAsk for insets imposed by client chrome
web_app_request_write_accessAsk to message the user
web_app_request_phoneAsk for the user's phone number
web_app_request_emoji_status_accessAsk for permission to set emoji status
web_app_set_emoji_statusSet the user's emoji status

Sensors

EventPurpose
web_app_start_accelerometer / web_app_stop_accelerometerStart or stop accelerometer updates
web_app_start_gyroscope / web_app_stop_gyroscopeStart or stop gyroscope updates
web_app_start_device_orientation / web_app_stop_device_orientationStart or stop orientation updates
web_app_request_locationOne-shot location request
web_app_check_locationCheck location availability and permission
web_app_open_location_settingsOpen the OS location settings

Biometrics

EventPurpose
web_app_biometry_get_infoQuery availability, type and permission state
web_app_biometry_request_accessAsk the user to allow biometrics
web_app_biometry_request_authPrompt for a biometric check
web_app_biometry_update_tokenStore or clear the biometry-protected token
web_app_biometry_open_settingsOpen the OS biometry settings

Storage

EventPurpose
web_app_device_storage_save_key / get_key / clearPlain local storage
web_app_secure_storage_save_key / get_key / restore_key / clearHardware-backed storage

device_storage is ordinary local storage — SharedPreferences on Android, localStorage on web — isolated per bot and per user. secure_storage is Keychain on iOS and Keystore with EncryptedSharedPreferences on Android, and is UNSUPPORTED on web. Any flow that requires it needs a path that works without it.

There is no cloud storage event in the TZ. If you are porting a Telegram Mini App that uses CloudStorage, that is a gap to plan around rather than an omission from this page.

Everything else

EventPurpose
web_app_add_to_home_screenPrompt to install a home-screen shortcut
web_app_check_home_screenCheck whether the shortcut exists
web_app_share_to_storyShare media to a story
web_app_send_prepared_messageSend a message prepared earlier via savePreparedInlineMessage
web_app_switch_inline_queryHand a query back to inline mode
web_app_data_sendReturn data to the bot — keyboard-button launches only
web_app_invoke_custom_methodProxy a call to your own server
web_app_request_file_downloadDownload a file to the device
oauth_requestBegin an OAuth flow

oauth_request is the one entry without the web_app_ prefix. The TZ writes it that way; whether that is intentional or a slip is not stated.

web_app_data_send carries the hardest constraints on this page: 4096 bytes, accepted once — later events are ignored and the WebView is force-closed — and available only from a reply-keyboard launch. See opening modes and Limits.

Client → app

These the TZ documents properly, with parameters. Reproduced as specified.

Interface

EventParams
main_button_pressednull
secondary_button_pressednull
back_button_pressednull
settings_button_pressednull
popup_closedbutton_id?
visibility_changedis_visible
viewport_changedheight, is_state_stable, is_expanded
theme_changedtheme_params
fullscreen_changedis_fullscreen, blur_enabled?
fullscreen_failederror: UNSUPPORTED / ALREADY_FULLSCREEN
safe_area_changedtop, bottom, left, right
content_safe_area_changedtop, bottom, left, right

viewport_changed fires repeatedly during an animation. is_state_stable marks the final frame — that is the one to lay out against. See Design.

Payments

EventParams
invoice_closedslug, status

status is one of:

ValueMeaning
cancelledClosed before the payment form was submitted
failedThe attempt ended in an RPC error
pendingAdditional verification (3-D Secure) finished, but the confirming service message has not arrived
paidPaid, and the service message was received

WARNING

Errors inside 3-D Secure land in pending, not failed. A UI that treats "not paid yet" as "failed" will tell users their payment did not go through while it is still in flight. Only paid is success, and only your backend's record of the service message should unlock anything.

Permissions

EventParams
write_access_requestedstatus: allowed / cancelled
phone_requestedstatus: sent / cancelled
emoji_status_access_requestedstatus: allowed / cancelled
emoji_status_setnull
emoji_status_failederror: UNSUPPORTED, SUGGESTED_EMOJI_INVALID, DURATION_INVALID, USER_DECLINED, SERVER_ERROR, UNKNOWN_ERROR
file_download_requestedstatus: downloading / cancelled

Biometrics

EventParams
biometry_info_receivedavailable, type: finger / face / unknown, access_requested, access_granted, token_saved, device_id
biometry_token_updatedstatus: updated / removed / failed
biometry_auth_requestedstatus: authorized / failed, token?

The TZ calls out one behaviour explicitly: access_requested = true with access_granted = false means the user refused. Asking again does nothing — the OS will not re-prompt. Offer to open settings instead, and send web_app_biometry_open_settings if they accept.

Sensors

EventParams
accelerometer_started / accelerometer_stoppednull
accelerometer_changedx, y, z — float, m/s²
accelerometer_failederror: UNSUPPORTED
gyroscope_started / gyroscope_stoppednull
gyroscope_changedx, y, z — float, rad/s
gyroscope_failederror: UNSUPPORTED
device_orientation_started / device_orientation_stoppednull
device_orientation_changedalpha, beta, gamma, absolute — radians
device_orientation_failederror: UNSUPPORTED

Updates are never emitted faster than the refresh_rate the app asked for, in milliseconds.

Location

EventParams
location_requestedavailable, latitude, longitude, altitude, course, speed, horizontal_accuracy, vertical_accuracy, course_accuracy, speed_accuracy
location_checkedavailable, access_requested?, access_granted?

Fields the platform cannot supply arrive as null rather than being omitted. Check for null per field; a device that reports a position but no speed_accuracy is normal.

Storage

EventParams
device_storage_key_savedreq_id
device_storage_key_receivedreq_id, value or null
device_storage_clearedreq_id
device_storage_failedreq_id, error
secure_storage_key_savedreq_id
secure_storage_key_receivedreq_id, value or null, can_restore
secure_storage_key_restoredreq_id, value
secure_storage_clearedreq_id
secure_storage_failedreq_id, errorUNSUPPORTED where the platform lacks secure storage

Every storage event carries req_id. Storage is the one part of the bridge with concurrent in-flight requests, so match responses on req_id rather than assuming the next event answers the last call.

Everything else

EventParams
qr_text_receiveddata
scan_qr_popup_closednull
clipboard_text_receivedreq_id, data?
custom_method_invokedreq_id, result?, error?
home_screen_addednull
home_screen_failederror: UNSUPPORTED
home_screen_checkedstatus: unsupported / unknown / added / missed
prepared_message_sentnull
prepared_message_failederror
oauth_supportedversion: 1
oauth_result_confirmedresult_url?

web_app_check_home_screen is always answered with home_screen_checked — no timeout handling needed for that one specifically.

Matching requests to responses

Most pairings are unambiguous by name: web_app_start_gyroscope is answered by gyroscope_started, gyroscope_failed follows a request that could not be serviced. Three are stated outright in the TZ rather than inferred:

RequestResponseSource
web_app_request_themetheme_changedStated
web_app_check_home_screenhome_screen_checkedStated, always answered
web_app_invoke_custom_methodcustom_method_invokedStated

Everything else is matched by name, which is a reasonable inference and not a documented contract. Write your bridge so an unrecognized or unexpected event is logged and ignored rather than throwing.

Custom methods

A Mini App can call your own server through the client, without a direct fetch:

  1. App emits web_app_invoke_custom_method with req_id, method, params
  2. Client calls invokeWebViewCustomMethod(bot, custom_method, params)
  3. The platform forwards it to your bot's webhook or server
  4. Client answers the app with custom_method_invoked carrying req_id and result or error

This is marked P3 in the TZ — the lowest priority in this section, and the most likely to arrive last or differently.

The routing is worth thinking about before adopting it. Your server sees a request relayed by the platform, so you inherit the platform's view of who is calling rather than establishing it yourself. Whether that carries a verifiable identity — and how it relates to initData — is not specified. A plain HTTPS call from your page to your backend, authorized by a session you issued after verifying initData, has a trust model you can actually reason about.