NewDash Dots are hereMini apps in Dash, with payments built inLearn more

Products · Developer guides

Authoring Conventions

Naming, flow shapes, labels, menus, error pages and a checklist for USSD flows that are easy to use and easy to maintain.

None of these conventions are enforced, but they make a flow easier for subscribers to use and easier for you to read, review and debug. The rules that are enforced are in Flow Definition.

Page IDs

Page IDs show up in $(page.ID.value), in next_page and in your exchange requests, so make them say what the page does.

Use snake_case and start with a verb or a role:

PrefixUse forUsual page type
startThe first pagedynamic or menu
welcome_*A greeting that asks a first questionprompt
enter_*, collect_*Asking for inputprompt
select_*, choose_*Picking from a listmenu
confirm_*A yes or no check before something happensprompt with data_type: "boolean"
pay_*, charge_*Taking a paymenttemplate (collect_payment)
lookup_*, validate_*Checking something on your serverdynamic, or a prompt with an exchange
prep_*, save_*Saving values without showing anythingside_effect
*_done, thank_youA successful enddisplay
*_not_found, *_failed, system_errorAn error enddisplay

Make IDs specific: select_service_type beats select, and account_not_found beats error. Avoid numbered IDs such as page1 or step_3.

Flow Shapes

Most flows follow one of three shapes.

Collect, pay, thank

No server calls. Good for event tickets, donations and simple top-ups.

welcome_get_name   (prompt)
select_ticket      (menu)
pay_ticket         (template: collect_payment)
thank_you          (display)

Look up, choose, pay

Your server checks the subscriber or their account first. Good for bills, school fees and meter top-ups.

start              (dynamic: your server decides what to show)
select_service     (menu, items from your server)
enter_reference    (prompt + exchange)
confirm_details    (boolean prompt)
pay_bill           (template: collect_payment)
payment_done       (display)
account_not_found  (display, chosen by your server)
system_error       (display, chosen by your server)

Remember returning subscribers

Pages skip themselves when a saved value is already there. Good for memberships, weekly giving and loyalty schemes. See Context Stores.

collect_display_name  (prompt, skipped if rc.display_name is set)
choose_option         (menu, items shown or hidden by rc values)
pay_option            (template: collect_payment)
option_done           (display)

Labels

  • Put a short greeting or context on the first line and the question on the next, separated by \n: "Welcome to Kenema Water.\nEnter your meter number".
  • Keep every label under 130 characters with real values filled in, and menu items under 64. Longer text is cut off. See Limits.
  • Name the amount and currency: SLE $(page.enter_amount.value).
  • Use the subscriber's first name when you have it: $(strings.split_index(rc.display_name, ' ', 0)).
  • For payment pages, follow the PIN label pattern in collect_payment.
  • Use 2 to 6 items for a decision. Use more, up to 16, only for long lists such as packages.
  • Put the most common choice first and Exit last.
  • Write items as Name (SLE amount) or Name - detail: "Standard (SLE 200)".
  • Use a fixed list when the items never change, and "items": "$(data.KEY)" when your server supplies them.
  • Hide items with hide rather than writing two flows: "hide": "$(rc.tier != 'gold')".

Confirm Before Paying

Before taking money, ask a yes or no question. Use exit_message to end the flow cleanly when the subscriber says no:

JSON
{
  "id": "confirm_payment",
  "component": {
    "type": "prompt",
    "data_type": "boolean",
    "label": "Pay SLE $(page.enter_amount.value) to Kenema Water?",
    "exit_message": "Payment cancelled. Nothing was charged."
  }
}

The subscriber answers 1 or 2. On yes, the page's value is true and the flow carries on. See prompt.

Exit Options

Give every long menu an Exit item, and send it to a short goodbye page with next_page:

JSON
{
  "id": "start_menu",
  "component": {
    "type": "menu",
    "label": "Welcome to Kenema Water.",
    "items": [
      { "label": "Pay bill", "value": "pay" },
      { "label": "Buy tokens", "value": "tokens" },
      { "label": "Exit", "value": "exit" }
    ]
  },
  "on_done": {
    "action": "next",
    "next_page": "$(page.start_menu.value == 'exit' ? 'say_goodbye' : 'enter_account')"
  }
}

say_goodbye is a display page, so the flow ends there.

Errors

  • Give each failure its own display page with a specific ID and a message that says what went wrong and what to do: "Meter number not found. Check the number on your meter and try again."
  • Keep one system_error page for anything unexpected: "Sorry, we can't complete this now. Please try again later."
  • Let your server choose the error page: when a lookup fails, the exchange response navigates to account_not_found. The flow only declares the pages. See the Exchange API response.
  • Make exchange handlers safe to run twice: handling the same request again should not record or charge anything twice.

More on failures is in Error Handling.

Context Keys

  • Use snake_case, like page IDs.
  • Name the value, not how you got it: rc.has_signed_up, not rc.result_of_signup_check.
  • Use sc by default. Use rc only for what must last until the next visit.
  • Give rc and sc values a default when you use them in an expression: $(rc.usual_amount ?? 50).
  • Test for a saved name or text with strings.is_blank(rc.KEY), not with a bare rc.KEY.

Flow Length

Keep flows short, ideally under 10 pages. Every extra screen is a chance for the session to time out or the subscriber to give up. If a journey is long, save progress in rc so a returning subscriber can skip what they already did.

Exchange Endpoints

  • Serve them over HTTPS.
  • Reply quickly, and show a clear error page when something fails.
  • One endpoint can serve every page. The request says which page it came from, so it's fine to repeat the same url.

Checklist Before Publishing

  • Page IDs are snake_case and start with a verb or role.
  • No page is called error, and each failure has its own message.
  • Every label fits in 130 characters, and every menu item in 64, with real values filled in.
  • Every payment page follows the collect_payment label and amount rules.
  • Every rc and sc value used in an expression has a ?? default.
  • Long menus have an Exit item.
  • Prompts have sensible length or value limits and a clear pattern_mismatch_message.
  • You walked through every path in the Simulator. See Editing and Publishing.

Keep reading