Tool handlers & deep links
The bot has two kinds of tools.
- Server-side catalog tools (
search_catalog,get_product,find_similar_products,compare_products,list_subcategories,list_top_categories, site-nav resolution,end_session) run entirely on the backend. The bot speaks the result; your app does nothing. - Client-executed actions (open a product, add to cart, apply a filter, …) are pushed to the SDK as an
actionmessage. The SDK runs your handler or fires a deep link, then sends theaction_ackback to the backend for you.
The mobile profile has no DOM to manipulate, so UI actions map to either a deep link (navigation) or a native callback (mutate your cart, set a filter). You register these; the SDK wires the acknowledgement.
Web host bridge (browser SDKs)
On the web — the @monoverse/voicebot-{react,vue,next} embeds and the vanilla loader — client
actions are routed through window.VoiceBotSyncBridge. You provide typed handlers (directly, or via the
SDK onAction prop); the widget resolves them in precedence order and sends the action_ack for you.
This is host-agnostic — a non-WooCommerce storefront wires its own cart / variant / filter handlers and
the bot acts through them exactly as it would on WooCommerce.
The bot only offers actions your host has the capability for, and writes are confirmation-gated and
server-authoritative. The full model — the capability vocabulary, declaring a host_profile, pushing
variant data, and the bridge / onAction shapes with a worked example — lives in
Capabilities & host actions. Wire bridge.selectVariant /
onAction.selectVariant for variant selection — not toolHandlers.select_variant (that never fires).
The rest of this page covers the mobile profile (React Native / Flutter), which is DOM-less and maps the same actions to deep links or native callbacks.
Register handlers and deep links
- React Native
- Flutter
// Native callback — mutate the cart, apply a filter, etc.
client.registerToolHandler('apply_filter', (args) => {
productList.setFilter(args);
return { success: true };
});
// Deep link — navigate via your router. Returns a route URI.
client.registerDeepLinkMap('open_product', ({ id }) => `myapp://product/${String(id)}`);
client.registerDeepLinkMap('view_cart', () => 'myapp://cart');
Deep links open through React Native Linking by default — override with a deepLinkOpener if you route
differently.
// Native callback — returns a result map.
client.registerToolHandler('apply_filter', (args) async {
productList.setFilter(args);
return {'success': true};
});
// Deep link — return a route URI; emitted on session.deepLinks.
client.registerDeepLinkMap('open_product', (args) => 'myapp://product/${args['id']}');
client.registerDeepLinkMap('view_cart', (args) => 'myapp://cart');
Listen to the resolved navigations on session.deepLinks:
session.deepLinks.listen((d) => router.go(d.uri));
The action table
Each action the backend can push, and how to map it on mobile. This is the authoritative mapping from the
wire protocol §9.
action | Mobile mapping |
|---|---|
open_product / navigate / open_category / view_cart / open_checkout | deep link (registerDeepLinkMap) → your route; fallback native callback |
add_to_cart / remove_from_cart / update_cart_qty | native callback (your cart API); executes after conversational consent (no extra button gate); no handler → { ok: false, error: "no_handler" } |
select_variant | native callback (drive your variant selector); reversible, not confirmation-gated |
apply_filter / filter_by_attribute / clear_filter | native callback (your product list) |
highlight / scroll_to | native callback, or a no-op ok: true if the list is not addressable |
show_toast | native callback (snackbar) |
submit_form | server-side plugin callback (unchanged), or a native override; confirmation-gated (requires explicit user interaction) |
submit_checkout | highlights the submit control and waits for a click — never auto-submits (confirmation-gated, non-overridable) |
open_popup / switch_language | native callback, or disabled |
no_handler — safe degradation
An action with no registered handler never crashes your app. The SDK auto-replies:
{ "success": false, "error": "no_handler" }
and the bot adapts — for example, it stops offering to do that action and steers the conversation
elsewhere. You only need to register the actions your app actually supports. (The handler-result key the SDK
sends back is success on the client side; on the wire it is reflected into the action_ack ok field — see
§7.)
You can also declare your capabilities up front so the bot only offers what you can execute — the SDK sends
the set of registered handler names in the hello.capabilities frame (see
§10).
How an action flows
bot decides to open a product
│
▼
server executes the tool, pushes: { "type":"action", "action":"open_product", "action_id":"…", "params":{ "id": 42 } }
│
▼
SDK looks up "open_product"
├─ deep-link map → resolve URI → open via Linking / emit on deepLinks
└─ tool handler → run your callback → get a result
│
▼
SDK sends: { "type":"action_ack", "action_id":"…", "ok": true, "result": {…} }
│
▼
(no handler registered) → SDK sends ok:false, error:"no_handler"; bot adapts
See also
- Context enrichment — tell the bot what's on screen so it picks better actions.
- Wire protocol §6 — the
actionandtool_callframes.