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:
| Prefix | Use for | Usual page type |
|---|---|---|
start | The first page | dynamic or menu |
welcome_* | A greeting that asks a first question | prompt |
enter_*, collect_* | Asking for input | prompt |
select_*, choose_* | Picking from a list | menu |
confirm_* | A yes or no check before something happens | prompt with data_type: "boolean" |
pay_*, charge_* | Taking a payment | template (collect_payment) |
lookup_*, validate_* | Checking something on your server | dynamic, or a prompt with an exchange |
prep_*, save_* | Saving values without showing anything | side_effect |
*_done, thank_you | A successful end | display |
*_not_found, *_failed, system_error | An error end | display |
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.
Menus
- 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)orName - 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
hiderather 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:
{
"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:
{
"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_errorpage 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, notrc.result_of_signup_check. - Use
scby default. Userconly for what must last until the next visit. - Give
rcandscvalues 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 barerc.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
rcandscvalue 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.