Skip to main content
Some conversations are naturally sequential: ask a question, wait for the answer, ask the next one. State machines (see State Machines) model this with explicit steps and transitions, which pays off for long-lived or branchy flows. For everything else, inline awaiting lets you write the conversation as ordinary linear code — no steps, no transitions, no stored progress.
The handler runs on the first update, suspends at the await without blocking the polling loop, and resumes on the matching update — local variables (nameMsg) survive the suspension as normal async state. The builder chain (TextMatches, With{FilterName}, Where, timeouts) is covered in Inline Awaits.

Choosing between inline awaiting and state machines

Parsing what the user sent

The awaited update arrives as a raw Message or CallbackQuery. Two bundled helpers parse its payload with exactly the same rules the generated router uses for [Arg] parameters and [Pattern] templates — so inline flows and routed handlers never disagree about what “parseable” means.

Command arguments: CommandHelper.ParseArgs

CommandHelper.ParseArgs tokenizes the message text and converts each token with the same engine behind [Arg]: numbers parse invariant-culture, enums parse case-insensitively (names or numeric values), everything else goes through TypeDescriptor converters. Combine it with CommandHelper.MatchCommand when the reply itself carries a command:
Each ArgSpec mirrors one [Arg] parameter: Type, a name used in error messages, and IsOptional (missing optional tokens yield the type’s default). A failing parse throws CommandArgsParseException, so a try/catch is the natural “re-ask the user” loop. For plain free-text replies without a command prefix, pass argsStartIndex: 0 to treat the whole text as arguments.

Callback data: CallbackPatternMatcher.Match<T…>

For inline keyboards, CallbackPatternMatcher exposes template overloads that are the imperative twin of [Pattern]: literal segments plus {name} placeholders, with captures bound positionally to generic type arguments (up to five, plus a literal-only overload for exact routes):
The conversion rules match the router exactly: string binds verbatim, enums parse case-insensitively, numerics parse invariant-culture, other types need a static TryParse. A {*name} wildcard tail binds to a string and captures everything after the separator. Template syntax details, the 64-byte callback_data budget, and the full overload list live in Callback-Data Patterns.

A complete flow combining both

Pitfalls

  • The awaited payload is raw. Values you extract with ParseArgs or CallbackPatternMatcher come as object?[] / typed out parameters, not handler parameters — cast explicitly.
  • Always handle null from the await. A null means the timeout expired or the wait was cancelled; reply with guidance (or silently drop) instead of continuing.
  • Non-matching updates fall through. While a handler is suspended, other updates for the same user go through normal routing; design your prompts so stray messages don’t derail the flow.
  • A suspended handler is single-shot memory. If the bot restarts, suspended flows are gone. Anything that must survive restarts belongs in a persisted state machine instead.