External Navigation
External suggestions are buttons the AI offers under its messages that take the user outside your app — to WhatsApp, a phone dialer, the app store, a map, a website, or any other deeplink. They sit alongside in-app Navigation suggestions in the same chat surface; the user doesn’t have to think about which kind of action they’re tapping.
This page covers the SDK side — what suggestions look like at runtime, the SDK’s default tap behavior, and when to override it. For configuring which destinations the AI is allowed to suggest, see Dashboard › External Destinations.
Why External Navigation Exists
Most chatbots have to fudge “outside the app” actions: pretend WhatsApp is an in-app screen, jam phone numbers into a tool description, or write a custom rendering for every off-app destination. Qafka treats them as a first-class concept so the AI can suggest “talk to support on WhatsApp” or “open in Maps” with a real button — typed, validated, with predictable platform behavior.
The mental model parallels in-app navigation:
| In-app (Navigation) | Off-app (External Navigation) | |
|---|---|---|
| Where the user ends up | A screen in your React Native app | A different app on the device, or a web URL |
| What you configure | Navigation rules per screen | Destinations per project |
| What the SDK receives | NavigationSuggestion via onNavigationSuggest | ExternalSuggestion via onExternalSuggestion |
| What happens by default | Routes via Expo Router | Opens via Linking.openURL |
Destination Types
Eleven typed destinations are supported. Each has its own validation, icon, and platform handling — the AI knows what kind of phrase to use for each (“message us on WhatsApp” vs “download the app” vs “open in Maps”).
| Type | Used for |
|---|---|
whatsapp | Direct chat with a customer support number |
phone | Call a phone number (dialer) |
sms | Open the SMS composer pre-filled with a number |
email | Open the mail composer pre-filled with an address (and optional subject) |
website | Open an external web URL (help center, blog, landing page) |
map | Open the device’s map app at a specific address (platform-aware: Apple Maps on iOS, Google Maps elsewhere) |
app_store | Open the right store URL for the device (iOS App Store / Play Store) |
social | Open a social media app or profile (Instagram, X, TikTok, LinkedIn, YouTube) |
deeplink | Generic catch-all — any custom URL scheme the typed list doesn’t cover (Uber, Spotify, niche apps) |
embed | Share a video or social post (e.g. YouTube, Instagram) inline |
document | Share a link to a document (e.g. a PDF) |
The first eight are “first-class”: admin sets one config field (phone number, URL), the system generates the right URL with the right platform handling. deeplink requires the admin to write the URL scheme and a fallback URL by hand. embed and document are media types — instead of redirecting, they carry a media payload (title, description, image) meant to be rendered as an in-chat card. See Media Destinations below for how each SDK handles them today.
The Suggestion Shape
When the AI suggests an external destination, the SDK receives it as:
interface ExternalSuggestion {
id: string // unique ID for the suggestion
type: ExternalType // 'whatsapp' | 'phone' | 'sms' | 'email' | 'website' | 'map' | 'app_store' | 'social' | 'deeplink'
label: string // button label (e.g. "Message on WhatsApp")
icon: string // icon key (e.g. "logo-whatsapp")
url: string // the resolved URL to open (already platform-aware)
fallbackUrl?: string // alternate URL when `url` isn't handleable
}The React Native SDK’s ExternalSuggestion type does not carry a media field — see Media Destinations.
The url is already resolved by the backend using the destination type’s platform-aware logic. You don’t need to translate whatsapp://send?phone=... to https://wa.me/... yourself — the backend gives you the right value for the device’s platform.
Default Behavior
If you don’t pass an onExternalSuggestion prop, the SDK opens the URL via React Native’s Linking API, with a fallback for cases where the target app isn’t installed:
Linking.canOpenURL(url)
↓ true ↓ false
Linking.openURL(url) fallbackUrl ? Linking.openURL(fallbackUrl) : (no-op)Errors are silently swallowed by default. If you want to catch them — to log analytics, show a toast, or fall back to your own UI — provide the callback.
Customizing the Action
Pass onExternalSuggestion to take over what happens when the user taps a button. The callback receives the full ExternalSuggestion and you decide what to do with it.
Tracking Analytics
The most common reason to override — log every external tap so you can see which destinations are actually used:
import { Linking } from 'react-native'
<Qafka
// projectId, endUserId, …
onExternalSuggestion={(s) => {
analytics.track('external_nav_press', {
type: s.type,
destinationId: s.id,
label: s.label,
})
Linking.openURL(s.url)
}}
/>Note that providing the callback means you are now responsible for opening the URL — the SDK’s default handler doesn’t fire when you set this prop.
In-App WebView for website
Open external websites in your own in-app browser instead of bouncing the user to Safari/Chrome:
<Qafka
// projectId, endUserId, …
onExternalSuggestion={(s) => {
if (s.type === 'website') {
navigation.navigate('WebViewScreen', { url: s.url })
return
}
Linking.openURL(s.url) // everything else uses default behavior
}}
/>Confirmation Modal Before Leaving
For high-friction destinations (paid call lines, app store deeplinks during a checkout flow), confirm before leaving:
<Qafka
// projectId, endUserId, …
onExternalSuggestion={(s) => {
if (s.type === 'phone') {
Alert.alert(
'Call customer support?',
s.label,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Call', onPress: () => Linking.openURL(s.url) },
],
)
return
}
Linking.openURL(s.url)
}}
/>Handling Missing Target Apps
The default handler checks canOpenURL and falls back automatically. If you take over, you’ll likely want to do the same:
<Qafka
// projectId, endUserId, …
onExternalSuggestion={async (s) => {
const canOpen = await Linking.canOpenURL(s.url)
if (canOpen) {
await Linking.openURL(s.url)
return
}
if (s.fallbackUrl) {
await Linking.openURL(s.fallbackUrl)
return
}
Toast.show('The required app is not installed')
}}
/>This is especially relevant for whatsapp (WhatsApp not installed → fall back to wa.me web link) and app_store (right store opens directly on the right OS, no fallback needed).
Media Destinations (embed / document)
embed and document destinations carry a media payload — the backend resolves a video/social-post embed or a document link into a title, description and optional image, meant to be rendered as an in-chat card instead of a plain redirect button.
- Web widget — renders
embed/documentsuggestions as in-chat media cards. It announces this at request time via aninline_mediaclient capability. On the web SDK,ExternalSuggestioncarries an optionalmediafield for these two types:kind: 'embed'(withprovider,providerLabel,embedUrl, and optionalaspectRatio/height) orkind: 'document', both withtitleand optionaldescription,imageUrl,siteName. Anembedcard plays in the chat when clicked; adocumentcard opens its link in a new tab.- Not forwarded to
onExternalSuggestion. The widget draws media suggestions itself, so they never reach youronExternalSuggestioncallback — only regular destinations (WhatsApp, phone, links, …) do. If you render your own external buttons from that callback, you won’t get a duplicate button for a video the widget is already showing, but you also can’t intercept or restyle media cards there. - At most one video/post card per reply. A single answer shows at most one
embedcard; when several match, the best-scoring one is kept.documentcards aren’t subject to this limit.
- Not forwarded to
- React Native SDK — never receives
embed/documentsuggestions at all. The backend filters both types out server-side for any client that hasn’t declared inline-media support, before the AI even sees them as candidates; the React Native SDK doesn’t declare it, so these destinations are effectively web-only. (TheExternalSuggestiontype also doesn’t include amediafield, and there’s no special-cased handling for the two types, so even a suggestion that somehow arrived would just fall through to the defaultLinking.openURLpath — but in practice it never arrives.)
Voice Mode
External suggestions do not fire in voice chat today. Voice can speak about external destinations (“you can reach support on WhatsApp at…”), but the buttons aren’t rendered in the voice page.
If your project relies heavily on external suggestions, surface those flows from text chat.
Configuring Destinations
The AI can only suggest destinations you’ve defined in the dashboard’s External Destinations page. Matching is driven by each destination’s Purpose (an AI instruction), Keywords, and Example User Questions — not its Description, which is an internal note never shown to the AI. A destination that doesn’t match any of the three for a given message is never suggested for it, so fill in more than just a one-line Purpose. Without configured destinations, no external event ever fires regardless of what the user types.