USSD API
USSD endpoints exposed by the Devotel CPaaS API Base path:/api/v1/ussd
Endpoint count: 7
title: “Worked USSD session samples” description: “Worked samples for the USSD session lifecycle: define a menu tree, handle the aggregator session callback with CON and END replies, dry-run a step in the simulator, and the errors to expect. Each sample shows the request and the response together.”
Worked USSD session samples
USSD is not a message channel — it is a session channel. The subscriber dials a short code (for example*384*1#), the aggregator POSTs each step of the session to your callback URL, and your synchronous HTTP response is the next screen: a text/plain body that starts with CON (keep the session open, render the menu) or END (show a final message and terminate). Samples on this page therefore pair the aggregator’s callback and your reply body — one is meaningless without the other. For the interactive builder surface, see the USSD flow builder guide.
Bodies are UTF-8 — menus routinely carry non-English prompts in the regions where USSD reaches feature phones, and any characters a GSM handset can render are valid in a prompt.
1. Define the session flow
PUT /api/v1/ussd/menuroot the initial dial renders, and option branches whose next points at another node. A node with no options — or with final: true — is a terminal screen (END); anything else renders its choices and continues (CON).
Request
root naming no node, or an option next pointing at a missing node all return 422 (see step 4), so a broken graph never answers live sessions.
2. Handle a session callback
POST /api/v1/ussd/callback/{tenantId}text is empty and the reply renders the root menu; on each later step the aggregator replays the full *-joined input and the reply renders the screen that input resolves to. Read the request and response together — the response is the screen.
Aggregator request (form-encoded or JSON — both are accepted)
CON renders the next menu and keeps the session open
CON / END).
Aggregator request — the subscriber pressed 1
END shows a final message and terminates the session
END Invalid selection. Session ended. — which is deliberate: USSD has no “back”, and re-prompting would loop because the aggregator replays the bad token on every callback. Idle sessions are closed by the operator (commonly 30–90 seconds), and because the engine is stateless a subscriber who re-dials and re-keys lands on the same screens — design every path to terminate within a few keypresses.
3. Read the session back
The engine is pure and stateless, so nothing is stored per session and there is no session-history API — “reading a session back” means fetching the flow definition and replaying the aggregator’s accumulated input through the simulator, which runs the same resolution the live callback runs.GET /api/v1/ussd/menu*-joined string — and inspect the node-visit outcome:
POST /api/v1/ussd/simulatedata.raw is the exact text/plain body the live callback would return for that input, and node_id names the node the walk landed on — the per-step node-visit trail. Walk every path this way before pointing the aggregator at the callback: initial dial, one key per branch, each terminal screen, and one deliberately bad key to confirm the invalid-selection end.
4. Errors
422 — the flow graph is invalid. Any structural violation in the tree returnsVALIDATION_ERROR and names the offending spot (issue paths point into nodes, options, or root — an option key may only contain digits, #, or *, because a handset can only send the 12-key DTMF set):
next resolving to a node in nodes.
Callback timeouts. The callback endpoint answers in milliseconds — there is no slow internal hop, so a hang on the subscriber’s side is almost always the operator’s idle-session timeout rather than a 504-class delay here. Two defensive cases still matter:
- You publish no menu. A callback for a tenant with no configured menu gets
END Service is not available.(HTTP 200) — the session closes gracefully instead of hanging the subscriber at a frozen prompt. GET/PUT /menutimes out. Retry with yourmeta.request_id— reads and writes are idempotent, and a replaced menu takes effect on the next callback.
Get the configured USSD menu
GET /api/v1/ussd/menudata.menu, or null when none has been configured yet.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Get the USSD push-provider config
GET /api/v1/ussd/push/configdata.config, or null when unset. The credential value is never returned — authConfigured reports whether one is stored.
string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.USSD aggregator session callback
POST /api/v1/ussd/callback/{tenantId}sessionId, phoneNumber, serviceCode, and the accumulated text. Responds with a text/plain body beginning CON (continue) or END (terminate). The :tenantId path segment scopes the call to the reseller tenant that owns the menu.
string
required
Platform tenant id that owns the USSD menu.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Open a network-initiated (push) USSD session
POST /api/v1/ussd/pushaction: CON keeps the session open for a reply; END shows a one-shot notice. Requires a push provider configured via PUT /ussd/push/config.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.string
required
Destination handset in E.164 (e.g.
+234801234567).string
required
First screen text (≤182 chars).
string (enum: CON|END)
CON awaits a reply; END is one-shot. Defaults to CON.string
Override the default service code.
string
Idempotency / correlation key echoed to the aggregator.
Simulate a USSD session step
POST /api/v1/ussd/simulatetext input string (the aggregator’s accumulated *-joined keypresses) and returns the resulting screen, exactly as the live callback would. Pass an inline menu to preview an unsaved definition; otherwise the tenant’s configured menu is used. Useful for building and testing menus before pointing the aggregator at the callback.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.string
Accumulated input string,
*-joined (empty for the initial dial).A USSD menu definition: a tree of
nodes (screens) navigated from root.Create or replace the USSD menu
PUT /api/v1/ussd/menuroot plus every option next resolves to an existing node. Replaces any previously configured menu.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.Create or replace the USSD push-provider config
PUT /api/v1/ussd/push/configpushUrl must be an https URL; it is re-validated and DNS-pinned against the SSRF guard on every push.
string
Stripe-style idempotency token. Pass a stable, client-generated value (1-255 chars) to dedupe retries on transient timeouts. The same key+credential+path replays the original response for 24h on 2xx (5min on 4xx, 30s on 5xx). Returns 409 if a concurrent request with the same key is already in flight; replayed responses include the
Idempotency-Replay: true response header.string (enum: true|false)
Sandbox opt-in for Clerk-session-authenticated requests. Set to
true to route the call through the test-mode pipeline: no real provider delivery, no credits deducted, response meta.test_mode: true. Ignored for live API keys (dv_live_sk_*) — server-to-server clients must use a test-prefixed key (dv_test_sk_*) to exercise sandbox. Test-prefixed keys unconditionally enable sandbox regardless of this header.