Skip to main content

USSD API

USSD (Unstructured Supplementary Service Data) is the menu-over-dial-code channel for feature phones. A subscriber dials a short code such as *384*1#, the mobile network opens a synchronous session and posts each keypress to your application, and the application replies with the next screen. It needs no data plan, which makes it the reach channel for markets where a large share of devices are 2G feature phones. Orbit hosts the menu and the session engine for you. You configure a menu tree once, point your aggregator (for example Africa’s Talking) at the tenant-scoped callback, and Orbit resolves each step. Base path: /api/v1/ussd Authentication: API key (X-API-Key) or session JWT — except the aggregator callback, which is a public, tenant-scoped webhook.

Using the SDKs

The samples below exercise the endpoints this page documents — create or replace a menu, preview a step with the simulator, and fetch the saved menu — in every language the page claims. Node and Python use the SDK escape-hatch request(), which keeps auth/retries and the { data, meta } envelope identical while it reaches endpoints the typed helpers don’t wrap yet. Go, Ruby, and PHP use plain HTTP clients (no SDK dependency) against the same endpoints. This same menu tree appears throughout the page: a root screen, one branch, and one terminal node.
The Python SDK is core-scope — it wraps the 8 core resources (messaging, voice, contacts, campaigns, verify, numbers) and reaches everything else through the generic client.request() escape hatch above. See the Python SDK. The push flow (PUT /api/v1/ussd/push/config then POST /api/v1/ussd/push) follows the same pattern — swap the path and body from the curl sample in Configure the push provider below. The aggregator callback is deliberately excluded: it is a public webhook you point your aggregator dashboard at, so it stays curl-only. Raw curl in the body of this page works identically. Full SDK index at SDK quickstart.

How a session works

The engine speaks the de-facto African-aggregator protocol. On every step the aggregator sends the full accumulated input string, with each prior keypress joined by * (the initial dial is an empty string). Orbit replies with a plain-text body whose first token is:
  • CON — show the message and keep the session open for more input.
  • END — show the message and terminate the session.
Because the aggregator replays the entire input chain on each callback, the engine is stateless: the next screen is a deterministic function of (menu, accumulated input). There is no per-session storage to manage.

Endpoints

The menu definition

A menu is a tree of nodes (screens) navigated from a root node. Each branch node lists numbered options; a node with no options (or final: true) terminates the session. On PUT /menu the tree is validated for unique node ids and that root plus every option next resolves to an existing node; the request is rejected otherwise. A successful write replaces any previously configured menu.

Create or replace a menu

The configured menu is returned under data.menu. GET /api/v1/ussd/menu returns the same shape, or data.menu: null when none has been configured yet.

Simulate a session step

Run the live engine against an input string without touching your aggregator — useful while building and testing menus. Pass an inline menu to preview an unsaved definition; omit it to use the tenant’s configured menu.
raw is the exact plain-text body the callback would return for the same input. When no menu is configured the simulator returns an END screen rather than an error.

Aggregator callback

Configure this URL in your aggregator dashboard (for example Africa’s Talking). It is a public webhook: the {tenantId} path segment scopes the call to the tenant that owns the menu, so no API key is sent. The aggregator posts it on every step of a session. It accepts both JSON and application/x-www-form-urlencoded bodies with the fields below, and responds with a text/plain body beginning CON (continue) or END (terminate).

Callback round trip

The callback fires once when the subscriber dials the service code and again after each keypress. The aggregator sends the full text chain each time. For the menu above, the initial request has an empty text value and Orbit returns CON Welcome to Acme; after the subscriber presses 1, the aggregator sends text=1 and Orbit returns the terminal balance screen. Use the following handler shape when you receive the aggregator request. Each example reads the form-encoded fields and returns the plain-text response the aggregator expects.
The response body for the continuing balance step is:
When the subscriber reaches the leaf node, return END instead of CON :
That terminal response closes the session after the subscriber presses 1; the preceding CON Welcome to Acme response keeps the menu open for that keypress. USSD has no “back” affordance, so an input that doesn’t match any option — or that arrives after a terminal screen — ends the session cleanly. A callback for a tenant with no configured menu ends the session with END Service is not available. rather than leaving the subscriber hanging.

Network-initiated (push) sessions

The endpoints above all answer a session the subscriber opened by dialling a short code. Push works in the opposite direction: your application asks your aggregator to open a USSD session on a subscriber’s handset — for balance prompts, mobile-money confirmations, and feature-phone OTP flows. Push has no company-wide provider. You register your own aggregator push endpoint per tenant, exactly like the inbound callback URL you set in your aggregator dashboard, and Orbit POSTs each push to it. The push URL must be an https:// URL and is re-validated and DNS-pinned against the SSRF guard on every send.

Configure the push provider

Set the aggregator endpoint your push sessions go out through with PUT /api/v1/ussd/push/config.
GET /api/v1/ussd/push/config returns the stored config under data.config, or data.config: null when none is set. The credential is never echoed back: instead of authHeaderValue, the read response carries authConfigured — a boolean reporting whether a credential is stored.

Open a push session

POST /api/v1/ussd/push opens the session on the subscriber’s handset via the configured aggregator.

Push session initiation

Configure the aggregator endpoint once, then call POST /api/v1/ussd/push to open a session on the subscriber’s handset. The first screen below matches the simulator menu above: it greets the subscriber with Welcome to Acme, and the callback continues with the balance screen when the subscriber replies 1.
providerStatus is the HTTP status your aggregator returned (a 2xx). accepted means the aggregator took the session; the subscriber’s reply, if any, then arrives on your aggregator callback like any other step.

Error codes

USSD sessions never originate outbound voice or SMS. The reply is returned synchronously to the aggregator inside the open session, so no termination path is involved.

See also