Skip to main content

Managing USSD Session State

USSD gateways are stateless: each user keypress arrives as a fresh HTTP request. Your service must reconstruct where the user is in the flow. This guide covers tracking state and keeping sessions continuous.

๐Ÿ”‘ The session_idโ€‹

Every request carries a session_id that stays constant for the whole interaction. Key all your state on it.

{ "session_id": "ussd_abc123", "phone": "+254712345678", "text": "1*2" }

๐Ÿงต Two Ways to Track Stateโ€‹

ApproachHowTrade-off
Concatenated textGateway sends full input history (1*2*50)No storage, but fragile for long flows
Server-side storeSave state keyed by session_idRobust, needs a store with TTL

For anything beyond a couple of steps, use a server-side store.

๐Ÿงฉ Parsing Concatenated Inputโ€‹

Many gateways append each entry with *. The last segment is the newest input.

def handle(req):
steps = req['text'].split('*') if req['text'] else []
if not steps:
return main_menu()
if steps[0] == '1':
return "END Your balance is KES 240."
if steps[0] == '2':
return buy_airtime(steps[1:])
return "END Invalid option."

๐Ÿ’พ Server-Side State with TTLโ€‹

Store progress under the session ID and expire it after the session window.

async function handle({ session_id, text }) {
let state = (await store.get(session_id)) || { step: 'menu', data: {} };

if (state.step === 'menu') {
if (text === '2') { state = { step: 'amount', data: {} }; await store.set(session_id, state, 180);
return 'CON Enter amount:'; }
}
if (state.step === 'amount') {
state.data.amount = text;
await store.del(session_id); // session complete
return `END Sending ${text}. Confirmation sent by SMS.`;
}
return 'CON 1. Balance\n2. Send money';
}

๐Ÿ” Continuity & Cleanupโ€‹

  • Set a TTL matching the carrier session window (~180s) so stale state self-clears.
  • Delete state on END to free storage and avoid stale resumes.
  • Don't rely on memory โ€” use a shared store so any instance can serve the next keystroke.
  • Treat re-dials as new sessions โ€” a new session_id means start over.

๐Ÿ’ก Best Practicesโ€‹

  • Always key on session_id, never on phone number alone (concurrent sessions).
  • Persist minimal data โ€” only what the next step needs.
  • Expire aggressively to match gateway timeouts.
  • Validate each step's input before advancing state.
  • Make handlers idempotent in case the gateway retries.

Last Updated: May 2026