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-hatchrequest(), 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.
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.
(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 aroot 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
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 inlinemenu 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 fulltext 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.
END instead of CON :
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 anhttps:// 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 withPUT /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 callPOST /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
- Messaging API — send SMS, WhatsApp, and RCS
- RCS API — rich messaging on Android