In this section, you will learn more about how to use intents and states to route your users through your voice app.

Introduction to User Sessions

A session is an uninterrupted interaction between a user and your application. It consists of at least one request, but can have a series of inputs and outputs. A session can end for the following reasons:

  • The response includes shouldEndSession, which is true for tell and endSession method calls
  • A user doesn't respond to an ask prompt and the session times out
  • The user asks to end the session by saying "quit" or "exit"

Sessions that contain only a single request with a tell response could look like this:

One Session

For more conversational experiences that require back and forth between your app and user, you need to use the ask method. Here is what a session with two requests could look like:

Two Sessions

To save user data in form of attributes across requests during a session, take a look at the Session Attributes section below. The platforms don't offer the ability to store user data across sessions. For this, Jovo offers a Persistence Layer.


The routing is done with handlers, which can be added with the app.setHandler method in the app.js:

Separate Handlers

You can add multiple handlers by passing more than one object to the setHandler method:

This allows you to have the handlers separated into different files (as modules), which can then be added to setHandler by using require:

The stateless.js file could look like this:

A more general introduction to states can be found below.

For a full example of separating handlers into different files, take a look at this GitHub repository: jankoenig/jovo-separate-handlers.

Platform Handlers

For cases where the experience differs on Alexa and Google Assistant, you can use the methods setAlexaHandler and setGoogleActionHandler to overwrite the default handlers.

Here is an example that offers different output for the two platforms:


If you're new to voice applications, you can learn more general info about principles like intents here: Getting Started > Voice App Basics.

Besides at least one of the the required 'LAUNCH' or 'NEW_SESSION' intents, you can add more intents that you defined at the respective developer platforms (see how to create an intent for Amazon Alexa and Google Assistant in our beginner tutorials) like this:

Whenever your application gets a request from one of the voice platforms, this will either be accompanied with an intent (which you need to add), or the signal to start or end the session.

For this, Jovo offers standard, built-in intents, 'LAUNCH' and 'END', to make cross-platform intent handling easier:

Standard Intents

You can learn more about Jovo standard intents in the following sections:

'LAUNCH' Intent

The 'LAUNCH' intent is the first one your users will be directed to when they open your voice app without a specific question (no deep invocations, just "open skill" or "talk to app" on the respective platforms). If you don't have 'NEW_SESSION' defined, this intent is necessary to run your voice app.

Usually, you would need to map the requests from Alexa and Google (as they have different names) to handle both in one intent block, but Jovo helps you there with a standard intent.


You can use the 'NEW_SESSION' intent instead of the 'LAUNCH' intent if you want to always map new session requests to one intent. This means that any request, even deep invocations, will be mapped to the 'NEW_SESSION' intent. Either 'LAUNCH' or 'NEW_SESSION'are required.

This is helpful if you have some work to do, like collect data (timestamps), before you route the users to the intent they wanted with the toIntent method.

This could look like this:

'NEW_USER' Intent

Additionally to the other intents above, you can use the 'NEW_USER' to direct a new user to this intent and do some initial work before proceeding to the interaction:

For example, this saves you some time calling if (this.user().isNewUser()) { } in every intent where you require the access to user data.


The 'ON_REQUEST' intent can be used to map every incoming request to a single intent first. This is the first entry point for any request and does not need to redirect to any other intent. If you make any async calls in the 'ON_REQUEST' intent, use a callback method, otherwise the intent will simply route the user to the desired intent, while the call is still running.

'END' Intent

A session could end due to various reasons. For example, a user could call "stop," there could be an error, or a timeout could occur after you asked a question and the user didn't respond. Jovo uses the standard intent 'END' to match those reasons for you to "clean up" (for example, to get the reason why the session ended, or save something to the database).

If you want to end the session without saying anything, use the following:


It is helpful to find out why a session ended. Use getEndReason inside the 'END' intent to receive more information. This currently only works for Amazon Alexa.

'Unhandled' Intent

Sometimes, an incoming intent might not be found either inside a state or among the global intents in the handlers variable. For this, 'Unhandled' intents can be used to match those calls:

Global 'Unhandled' Intent

One 'Unhandled' intent may be used outside a state to match all incoming requests that can't be found globally.

In the below example all intents that aren't found, are automatically calling the 'Unhandled' intent, which redirects to 'LAUNCH':

State 'Unhandled' Intents

Usually, when an intent is not found inside a state, the routing jumps outside the state and looks for the intent globally.

Sometimes though, you may want to stay inside that state, and try to capture only a few intents (for example, a yes-no-answer). For this, 'Unhandled' intents can also be added to states.

See this example:

This helps you to make sure that certain steps are really taken in the user flow.

However, for some intents (for example, a 'CancelIntent'), it might make sense to always route to a global intent instead of 'Unhandled'. This can be done with intentsToSkipUnhandled.


With intentsToSkipUnhandled, you can define intents that aren't matched to an 'Unhandled' intent, if not found in a state. This way, you can make sure that they are always captured globally.

In the below example, if a person answers to the first question with "Help," it is not going to 'Unhandled', but to the global 'HelpIntent':


In cases where the names of certain intents differ across platforms, Jovo offers a simple mapping function for intents. You can add this to the configuration section of your voice app:

This is useful especially for platform-specific, built-in intents. One example could be Amazon's standard intent when users ask for help: AMAZON.HelpIntent. You could create a similar intent on Dialogflow called HelpIntent and then do the matching with the Jovo intentMap.

This can also be used if you have different naming conventions on both platforms and want to match both intents to a new name. In the below example, the AMAZON.HelpIntent and an intent called help-intent on Dialogflow are matched to a Jovo intent called HelpIntent.

Platform built-in intents

As mentioned above, the platforms offer different types of built-in intents.


For simple voice apps, the structure to handle the logic is quite simple:

This means, no matter how deep into the conversation with your voice app the user is, they will always end up at a specific 'YesIntent' or 'NoIntent'. As a developer need to figure out yourself which question they just answered with "Yes."

This is where states can be helpful. For more complex voice apps that include multiple user flows, it is necessary to remember and route through some user states to understand at which position the conversation currently is. For example, especially "Yes" and "No" as answers might show up across your voice app for a various number of questions. For each question, a state would be very helpful to distinct between different Yes's and No's.

With Jovo, you can include states like this:

By routing a user to a state (by using followUpState), this means you can react specifically to this certain situation in the process.

When a user is in a certain state and calls an intent, Jovo will first look if that intent is available in the given state. If not, a fallback option needs to be provided outside any state:

Alternatively, you can also use an Unhandled intent as described in the section above:


If you want to route a user to a state after you asked a specific question, you can add a followUpState. It is important that you do this before your ask call. For example, you can prepend it like this:

This way, the voice app will first look if the response-intent is available in the given state. If not, it will go to the default called intent if it's available outside a state.

Nested States

You can also nest states for more complex multi-turn conversations:

You can nest as many states as you want. As they are objects, you reach them with the . separator. You can also use getState() to access the current state:

Remove a State

If you are inside a state and want to move outside to a global (stateless) intent in the next request, you have two options:

Intent Redirects

Jovo offers the ability to redirect incoming intents to others. For example, the sample voice app uses this to go from 'LaunchIntent' to 'HelloWorldIntent':

You can use the following methods to redirect intents:


Use toIntent to jump into a new intent within the same request.

Sometimes, you may want to pass additional information (like user input) to another intent. You can use the arg parameter to do exactly this.

To make use of the passed data, add a parameter to your intent handler:


Similar to toIntent, you can use toStateIntent to redirect to an intent inside a specific state.

The routing will look for an intent within the given state, and go there if available. If not, it will go to the fallback option outside your defined states.


If you're inside a state and want to go to a global intent, you can use toStatelessIntent to do exactly this:

Event Listeners

Event Listeners offer a way for you to react on certain events like onRequest and onResponse. Find out more about event listeners here: App Logic > Routing > Event Listeners.

User Input

To learn more about how to make use of user input (slots on Alexa and entities on Dialoflow), take a look at this section: App Logic > Data.

Session Attributes

It might be helpful to save certain information across requests during a session (find out more about session management in the introduction above). This can be done with Session Attributes.

The setSessionAttribute and setSessionAttributes methods can be used to store certain information that you can use later within the session. It's like a cookie that's alive until the session ends (usually after calling the tell function or when the user requests to stop).

You can either access all session attributes with getSessionAttributes, or call for a certain attribute with getSessionAttribute(key).

Have a look at App Logic > Data to learn more about how to persist data across sessions.

Comments and Questions

Any specific questions? Just drop them below. Alternatively, you can also fill out this feedback form. Thank you!

Join Our Newsletter

Be the first to get our free tutorials, courses, and other resources for voice app developers.