Events and subscriptions
send is request-response: you call a method, the host answers with a Result or Failed event. subscribe is the incoming stream: everything the host sends, including answers to your send calls.
import bridge from "@vkma.dev/bridge";
await bridge.send("VKWebAppInit");const user = await bridge.send("VKWebAppGetUserInfo");// user: UserInfo — id, first_name, photo_100, …Types come from the @vkma.dev/bridge-schema catalogue: props from RequestPropsMap, the response from ReceiveDataMap. A method with no receive side in the catalogue (e.g. VKWebAppLibverifyCheck) resolves as Promise<void>.
How the response is paired
Section titled “How the response is paired”Every send gets a request_id. By default the instance injects one itself — __vkma_1, __vkma_2, … When the host sends an event whose request_id has a pending entry:
request_idis stripped from the payload — the promise resolves with the body without it;- an event carrying
error_typerejects the promise with that same body (also withoutrequest_id).
Pass your own request_id in props when the host side needs it for its own logic — the instance policy never touches an explicit identifier:
await bridge.send("VKWebAppGetUserInfo", { request_id: "sync-1" });A response without request_id (or with a foreign one) is an event, not an answer: promises are left alone.
Errors
Section titled “Errors”The rejection is not an Error — it is the Failed event body. Discriminate by error_type:
import type { ErrorData } from "@vkma.dev/bridge";
try { await bridge.send("VKWebAppGetUserInfo");} catch (failed) { const data = failed as ErrorData; switch (data.error_type) { case "client_error": // data.error_data.error_reason break; case "api_error": // data.error_data.error_msg, error_code break; case "auth_error": // data.error_data.error_reason break; }}If no answer ever arrives, the promise hangs forever — add a timeout or signal.
subscribe
Section titled “subscribe”Every incoming event, responses to send included. The shape is event.detail.type / event.detail.data, delivered as-is, request_id included:
const off = bridge.subscribe(event => { switch (event.detail.type) { case "VKWebAppUpdateConfig": // event.detail.data.appearance: "light" | "dark" break; case "VKWebAppLocationChanged": // event.detail.data.location: string break; }});
off(); // unsubscribesubscribe returns its own unsubscribe function; the classic bridge.unsubscribe(listener) exists too. A handler may unsubscribe mid-dispatch.
Which one to use
Section titled “Which one to use”- You need an answer —
send:VKWebAppGetUserInfo,VKWebAppStorageGet,VKWebAppShowWallPostBox(the promise resolves when the user closes the box). - The host reports on its own —
subscribe:VKWebAppUpdateConfig,VKWebAppLocationChanged,VKWebAppAudioPaused,VKWebAppUpdateInsets.
Responses to your own send calls also land in subscribe — if you write a global handler, filter out payloads carrying request_id.
What happens to every event along the way is visible in the tap — it records sends, responses and events alike.