Respond to Hey Meta voice invocations

Updated: Oct 7, 2026

Overview

When the wearer says “Hey Meta, start {YOUR APP NAME}”, Meta AI launches or foregrounds your app and sends it a voice invocation. Your app receives the invocation, does the work, and acknowledges it back to the glasses. If your app is not already running, the command cold-launches it.
Voice invocations are not a device-session capability. They are a Wearables-level stream: you do not add a capability to a session, and you do not need a running device session. This matches how Session lifecycle describes “Hey Meta” — a short, system-owned transaction rather than a sustained device session.
Your app’s identity cannot be spoofed. Your package name (Android) or bundle identifier (iOS) is read from the initialized SDK configuration, not supplied by the caller, so an app cannot present itself as another developer’s integration.

Use this page with AI

Copy this prompt into your AI coding tool to add or debug voice invocation handling:
Use https://wearables.developer.meta.com/docs/develop/dat/voice-invocations/, then use the Wearables MCP endpoint https://mcp.developer.meta.com/wearables to call search_dat_docs for current Device Access Toolkit voice invocation guidance. Inspect my app entry points first, then add the smallest change that detects a voice-triggered launch, starts the voice invocations stream, handles every invocation type exhaustively, and answers every invocation through its response handle exactly once. Do not assume a device session is required, and run the relevant local checks.

Prerequisites

Then configure your app for voice invocations in the Wearables Developer Center⁠:
  • Add mobile app configuration. Provide your app’s package name (Android) or bundle identifier (iOS).
  • Configure your app name. The name you provide acts as the keyword the voice service matches, so choose one that is phonetically clear and minimizes user confusion. See Manage projects.
  • Create a new version. Voice invocations do not require a separate permission toggle. After saving your configuration, create a new version of the integration. The new version includes the Voice Invocation permission. See Set up release channels.
Invocation phrases are not declared in your Android manifest or Info.plist, and voice invocations do not require a CAMERA or MICROPHONE permission request. Whether invocations reach your app is determined entirely by your Developer Center configuration.
If voice invocations still do not work after you create a new version, contact support and provide your Wearables Application ID so an engineer can verify that the Voice Invocation permission is present.

How an invocation reaches your app

  1. The wearer speaks to Meta AI on the glasses.
  2. Meta AI resolves the phrase to your app and launches or foregrounds it.
  3. Your app detects that it was launched by a voice invocation.
  4. Your app starts a voice invocations stream and receives the invocation.
  5. Your app does the work and answers through the invocation’s response handle.
Answer every invocation exactly once. Failing to answer leaves Meta AI waiting on the request and may surface a fallback UI to the wearer. The response handle tracks delivery status and rejects duplicate responses, so answering twice is harmless; never answering is the failure mode to avoid.
A foreground Web App takes priority. A voice invocation cannot launch your app while a WebMCP Web App is open in the foreground on the glasses — Meta AI routes the request to that Web App instead. This is intended sandbox behavior.

Error handling

Android exposes nine VoiceInvocationError values on stream.errors:
ErrorCause
CHANNEL_NOT_CONNECTED
The voice invocation channel is not connected.
CHANNEL_ERROR
An unspecified channel error occurred.
FAIL_TO_SEND_INIT_REQUEST
The init request could not be sent.
INIT_REQUEST_ERROR
The init request returned an error.
FAIL_TO_SEND_MESSAGE
A message could not be sent.
INVALID_ACTION_MESSAGE_PROTO
A malformed action message was received.
UNKNOWN_MESSAGE_TYPE
An unrecognized message type was received.
ACTION_FAILED
An action failed.
INVALID_SESSION_STATE
An operation was attempted in an invalid session state.
iOS has the same nine cases plus two more that come from resolving the device: invalidWearablesInterface and deviceNotFound. Three iOS cases are both thrown and publishable — invalidWearablesInterface from the initializer, and deviceNotFound and channelNotConnected from start(deviceIdentifier:). On Android the equivalent conditions surface only on errors, because neither the stream factory nor its automatic start throws.
A malformed invocation is dropped rather than delivered: the SDK reports INVALID_ACTION_MESSAGE_PROTO on the error stream and does not emit a bogus invocation.

Android

The voice invocation APIs live in com.meta.wearable.dat.core.voiceinvocations:
import com.meta.wearable.dat.core.Wearables
import com.meta.wearable.dat.core.selectors.AutoDeviceSelector
import com.meta.wearable.dat.core.voiceinvocations.VoiceInvocationsStream
import com.meta.wearable.dat.core.voiceinvocations.isVoiceInvocationsIntent
import com.meta.wearable.dat.core.voiceinvocations.startVoiceInvocationsStream
import com.meta.wearable.dat.core.voiceinvocations.types.actions.LaunchApp

Step 1: Detect the voice-triggered launch

Meta AI launches your activity with an intent that carries a source marker. Check it in both onCreate and onNewIntent — an already-running app is foregrounded through onNewIntent, not onCreate.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (isVoiceInvocationsIntent(intent)) {
        startYourExperience()
    }
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    if (isVoiceInvocationsIntent(intent)) {
        startYourExperience()
    }
}
isVoiceInvocationsIntent(intent) returns true only for intents originating from the Meta AI app as a result of a voice command. Do not try to reconstruct the check yourself from intent extras.

Step 2: Start the stream

Hold the stream in a property. A local val inside onCreate goes out of scope before you can close it.
private lateinit var stream: VoiceInvocationsStream

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    stream = Wearables.startVoiceInvocationsStream(AutoDeviceSelector())
}
The stream starts itself when it is created; there is no separate start() call. If the automatic start fails, the failure is logged and surfaces on errors and state rather than throwing. The optional second parameter is the CoroutineDispatcher used for background work, defaulting to the SDK’s IO dispatcher.

Step 3: Handle invocations

lifecycleScope.launch {
    stream.invocations.collect { invocation ->
        when (invocation) {
            is LaunchApp -> {
                navigateToMainScreen()
                invocation.responseHandle.sendSuccess(null)
            }
        }
    }
}
sendSuccess and sendFailure are suspend functions returning Boolean: true when the response was delivered. Both take an optional actionOutput string.

Step 4: Observe state and errors

lifecycleScope.launch {
    stream.state.collect { state -> updateUi(state) }
}

lifecycleScope.launch {
    stream.errors.collect { error -> log(error.description) }
}
SessionState has three values:
StateMeaning
STARTING
The stream is connecting.
STARTED
The stream is listening for invocations.
STOPPED
Not started, or no active devices are connected.

Step 5: Close the stream

override fun onDestroy() {
    stream.close()
    super.onDestroy()
}
VoiceInvocationsStream is Closeable. Close it when you stop caring about invocations; the stream’s lifecycle is owned by you, not by the SDK.

Test without hardware

Mock Device Kit can inject invocations through MockGlasses.services.voiceInvocation.
val glasses = mockKit.pairGlasses(GlassesModel.RAYBAN_META).getOrThrow()
val voiceInvocation = glasses.services.voiceInvocation
if (voiceInvocation.hasConnectedApps()) {
  voiceInvocation.simulateLaunchAppAction()
}
simulateLaunchAppAction() returns the request ID of the dispatched action, or null when no app is connected — so a null return means your app was not listening, not that the invocation failed.
Use simulateIncompleteAction() to dispatch a deliberately malformed action and confirm your app surfaces the error rather than acting on a bogus invocation.
See Android testing for how to create a mock device.

iOS

Step 1: Create and start the stream

iOS separates construction from starting, and requires you to name the device. Keep the stream and both listener tokens as stored properties: if they go out of scope when the enclosing function returns, their subscriptions are released and invocations stop arriving.
import MWDATCore

@MainActor
final class VoiceInvocationsController {
    private var stream: VoiceInvocationsStream?
    private var invocationToken: (any AnyListenerToken)?
    private var errorToken: (any AnyListenerToken)?

    func startListening(on deviceIdentifier: DeviceIdentifier) throws {
        let stream = try VoiceInvocationsStream(wearables: Wearables.shared)
        addListeners(to: stream)  // Steps 2 and 3
        try stream.start(deviceIdentifier: deviceIdentifier)
        self.stream = stream
    }
}
Register your listeners before calling start(deviceIdentifier:) so an invocation that arrives immediately is not missed.
The initializer throws invalidWearablesInterface if the SDK is not configured. start(deviceIdentifier:) throws deviceNotFound when no connected device matches, or channelNotConnected when the device has no active connection.
Calling start(deviceIdentifier:) again tears down the previous stream first, so it is the way to switch to another device. To cover devices that connect after launch, call it for each device in wearables.devices and then for each update from wearables.devicesStream().
VoiceInvocationsStream is @MainActor-isolated.

Step 2: Handle invocations

Invocations arrive as any VoiceInvocation, so downcast to the concrete type.
invocationToken = stream.invocationsPublisher.listen { invocation in
    guard let launchApp = invocation as? LaunchApp else { return }
    navigateToMainScreen()
    Task {
        _ = await launchApp.responseHandle.sendSuccess(actionOutput: nil)
    }
}
On iOS, LaunchApp also carries the deviceIdentifier of the glasses that produced the invocation.

Step 3: Observe errors

errorToken = stream.errorPublisher.listen { error in log(error.description) }
iOS does not expose a session-state publisher. Track readiness from whether start(deviceIdentifier:) threw, plus the error publisher.

Step 4: Stop the stream

stream?.stop()
stop() is safe to call when no stream is active. After stopping, call start(deviceIdentifier:) again to listen once more.

Test without hardware

Mock Device Kit can inject invocations through MockGlasses.services.voiceInvocation.
let voiceInvocation = mockGlasses.services.voiceInvocation
if voiceInvocation.hasConnectedClients {
  _ = voiceInvocation.sendLaunchAppAction()
}
sendLaunchAppAction() returns the request ID of the dispatched action, or nil when no app is connected — so a nil return means your app was not listening, not that the invocation failed.
Use sendIncompleteAction() to dispatch a deliberately malformed action and confirm your app surfaces the error rather than acting on a bogus invocation.
See iOS testing for how to create a mock device.

Platform differences

BehaviorAndroidiOS
Creating the stream
Wearables.startVoiceInvocationsStream(deviceSelector, dispatcher)
VoiceInvocationsStream(wearables:)
Starting
Automatic on creation
Explicit start(deviceIdentifier:)
Device targeting
DeviceSelector
A specific DeviceIdentifier
Session state
state: StateFlow<SessionState>
Not exposed
Failure reporting at start
Logged, then surfaced on errors
Thrown from init and start(deviceIdentifier:)
Invocation type modeling
sealed interface; when is exhaustive
protocol; downcast with as?
deviceIdentifier on the invocation
Not present
Present on LaunchApp
Teardown
close() (Closeable)
stop(), restartable
Detecting a voice launch
isVoiceInvocationsIntent(intent)
Not applicable

Best practices

  • Avoid “AI” in your app name. App names that contain “AI” are more easily confused with Meta AI and reduce voice command recognition. Choosing a name without “AI” improves the voice command success rate.
  • Always acknowledge invocations. Every emitted invocation must be answered through its response handle with success or failure. Unanswered invocations leave the glasses waiting on the request.
  • Open the stream early. Start the stream in an app-scoped or early lifecycle location so a voice command that cold-launches your app is captured before your UI appears.
  • Handle disconnects gracefully. Observe the stream’s error and state channels and surface appropriate UI when a device disconnects.
  • Release resources when done. Close or stop the stream when you no longer need to receive invocations.

Next steps