Pular para o conteúdo principal

Guia do Gateway USSD

← Voltar para a Documentação Principal

Este guia cobre o Gateway USSD do OmniSS7, que conecta diálogos USSD SS7/MAP a callbacks HTTP/JSON, permitindo que desenvolvedores de terceiros construam aplicações USSD com um simples endpoint HTTP.

Índice​

  1. Visão Geral
  2. Arquitetura
  3. Habilitando o Gateway USSD
  4. Configuração
  5. Protocolo de Callback HTTP
  6. USSD Originado pela Rede (Push API)
  7. Ciclo de Vida da Sessão
  8. Tratamento de Erros
  9. Métricas e Monitoramento
  10. Servidor de Callback de Exemplo
  11. Solução de Problemas

Visão Geral​

O Gateway USSD lida com duas direções de tráfego USSD:

  • Originado pelo Móvel (Entrada): Um assinante disca um código curto (por exemplo, *100#). O gateway recebe o processUnstructuredSS-Request MAP (opcode 59). Ele encaminha a solicitação para seu callback HTTP. Em seguida, retransmite sua resposta de volta via SS7.
  • Originado pela Rede (Saída): Sua aplicação envia uma mensagem USSD para um assinante via a API REST. O gateway envia um unstructuredSS-Request MAP (opcode 60) via SS7. Ele então roteia a resposta do assinante para seu callback.

Ambas as direções suportam diálogos de múltiplas interações: menus interativos onde o assinante responde e recebe prompts de acompanhamento.

Características Principais​

PropriedadeValor
TransporteHTTP POST síncrono por interação
CodificaçãoAlfabeto padrão GSM de 7 bits (DCS 0x0F) conforme 3GPP TS 23.038
Máximo de caracteres182 caracteres (configurável)
Rastreamento de sessãoUUID gerado pelo gateway por diálogo
AutenticaçãoNenhuma (confia na rede SS7)
RoteamentoCorrespondência de prefixo de código curto para URLs de callback

Referências 3GPP​

EspecificaçãoRelevância
3GPP TS 23.090USSD Fase 2: arquitetura e procedimentos
3GPP TS 24.090USSD Fase 3: detalhes do protocolo
3GPP TS 29.002Protocolo MAP: USSD-Arg, USSD-Res, opcodes 59/60/61
3GPP TS 23.038Alfabeto padrão GSM de 7 bits e esquema de codificação de dados

Arquitetura​

Fluxo Originado pelo Móvel (Entrada)​

Fluxo Originado pela Rede (Push Saída)​

Visão Geral dos Componentes​


Habilitando o Gateway USSD​

O Gateway USSD requer que o modo Cliente MAP esteja habilitado, além de sua própria flag de recurso.

config :omniss7,
map_client_enabled: true,
ussd_gateway_enabled: true

O gateway também requer uma conexão M3UA funcional (veja o Guia do Cliente MAP para configuração do M3UA).


Configuração​

Parâmetros do Gateway USSD​

config :omniss7,
ussd_gateway_enabled: true,
ussd_gateway: %{
# Roteamento de código curto - correspondência de prefixo mais longa
routes: [
%{pattern: "*100", url: "http://balance-app:9000/ussd"},
%{pattern: "*200", url: "http://topup-app:9000/ussd"},
%{pattern: "*", url: "http://default-app:9000/ussd"}
],

# Timeouts de sessão
session_timeout_ms: 180_000, # Duração total da sessão (3 minutos)
turn_timeout_ms: 30_000, # Máximo de espera pela resposta do assinante por interação (30 segundos)

# Configurações de callback HTTP
http_timeout_ms: 5_000, # Timeout para HTTP POST para seu aplicativo (5 segundos)

# Limites de texto
max_text_length: 182 # Máximo de 7 bits GSM (trunca com aviso se excedido)
}

Referência de Parâmetros​

ParâmetroTipoObrigatórioPadrãoDescrição
ussd_gateway_enabledBooleanoSimfalseInterruptor mestre para o recurso do Gateway USSD
ussd_gateway.routesLista de MapasSim[]Regras de roteamento de prefixo de código curto. Cada entrada possui pattern (string de prefixo) e url (URL de callback). A correspondência de prefixo mais longa vence.
ussd_gateway.session_timeout_msInteiroNão180_000Duração máxima total da sessão em milissegundos. A sessão é encerrada com um erro se excedida.
ussd_gateway.turn_timeout_msInteiroNão30_000Tempo máximo para esperar pela resposta do assinante em um diálogo de múltiplas interações, em milissegundos.
ussd_gateway.http_timeout_msInteiroNão5_000Timeout de requisição HTTP para callbacks para sua aplicação, em milissegundos. Cobre tanto o tempo de conexão quanto o tempo de resposta.
ussd_gateway.max_text_lengthInteiroNão182Máximo de caracteres em uma string de texto USSD. Textos que excedem isso são truncados e um aviso é registrado.

Parâmetros de Roteamento​

Cada entrada na lista routes é um mapa:

ParâmetroTipoObrigatórioDescrição
patternStringSimPrefixo de código curto a ser correspondido. Use "*" como um fallback geral. Prefixos mais longos têm prioridade.
urlStringSimURL do endpoint HTTP para receber POSTs de callback para códigos curtos correspondentes.

Correspondência de Roteamento​

As rotas são correspondidas por prefixo mais longo primeiro. Para a string de discagem *100#:

  1. "*100" corresponde (comprimento 4): selecionado
  2. "*10" corresponde (comprimento 3): ignorado, mais curto
  3. "*" corresponde (comprimento 1): fallback

Se nenhuma rota corresponder, o gateway retorna um erro MAP para o móvel e registra um aviso.


Protocolo de Callback HTTP​

Sua aplicação recebe requisições HTTP POST do gateway e responde com instruções JSON.

Requisição do Gateway para Sua Aplicação​

Content-Type: application/json

Primeira interação (iniciação da sessão):

{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"msisdn": "+254712345678",
"type": "initiation",
"text": "*100#",
"turn": 1
}

Interações subsequentes (assinante respondeu):

{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"msisdn": "+254712345678",
"type": "response",
"text": "1",
"turn": 2
}

Campos da Requisição​

CampoTipoDescrição
session_idStringUUID gerado pelo gateway. Único por diálogo USSD. Use isso para correlacionar interações.
msisdnStringMSISDN do assinante (se disponível na mensagem MAP). Pode estar vazio para algumas redes.
typeString"initiation" para a primeira interação, "response" para respostas subsequentes do assinante.
textStringA string de discagem (por exemplo, *100#) na iniciação, ou a entrada do assinante (por exemplo, 1) na resposta.
turnInteiroContador de interações começando em 1. Incrementa com cada interação do assinante.

Resposta da Sua Aplicação​

Sua aplicação deve responder com JSON contendo uma action e text:

Continuar (mostrar menu, aguardar entrada do assinante):

{
"action": "continue",
"text": "1. Saldo\n2. Recarregar\n3. Transferir"
}

Encerrar (mostrar mensagem final, fechar sessão):

{
"action": "end",
"text": "Seu saldo é $5.00"
}

Campos da Resposta​

CampoTipoObrigatórioDescrição
actionStringSim"continue" para manter a sessão aberta e aguardar a entrada do assinante, ou "end" para exibir uma mensagem final e fechar a sessão.
textStringSimTexto a ser exibido no dispositivo do assinante. O comprimento máximo é regido por max_text_length (padrão 182). Use \n para quebras de linha.

USSD Originado pela Rede (Push API)​

Envie uma mensagem USSD para um assinante a partir de sua aplicação.

Endpoint​

POST /api/ussd/send

Requisição​

{
"msisdn": "+254712345678",
"text": "Você tem uma fatura pendente. Responda 1 para pagar.",
"callback_url": "http://billing-app:9000/ussd"
}

Campos da Requisição​

CampoTipoObrigatórioDescrição
msisdnStringSimMSISDN do assinante de destino no formato internacional.
textStringSimTexto inicial USSD a ser exibido. Codificado como GSM de 7 bits.
callback_urlStringSimURL para receber a resposta do assinante via o protocolo de callback padrão.

Resposta​

Sucesso (200 OK):

{
"session_id": "xyz-789-abc-123",
"status": "sent"
}

Respostas de erro:

Status HTTPCorpoCausa
400{"error": "invalid request", "required": ["msisdn", "text", "callback_url"]}Falta um ou mais campos obrigatórios, ou o corpo não é um JSON válido
500{"error": "{:gsm7_encode_failed, ...}"}O texto contém caracteres não no alfabeto GSM de 7 bits. A falha de codificação é apresentada através do ramo de erro genérico, então o status é 500, não 400.
500{"error": "..."}Qualquer outra falha de envio (por exemplo, conectividade M3UA). O campo error carrega a razão interna inspecionada.
503{"error": "USSD gateway not enabled"}ussd_gateway_enabled é false

Nota sobre erros de validação vs. envio: Problemas de validação de campo (chaves faltando, JSON malformado) retornam 400 invalid request. Uma vez que a validação passe, qualquer falha do caminho de envio (incluindo falhas de codificação GSM de 7 bits) é relatada como 500 com a razão inspecionada no campo error.

Exemplo cURL​

curl -X POST http://localhost:9090/api/ussd/send \
-H "Content-Type: application/json" \
-d '{
"msisdn": "+254712345678",
"text": "Você tem uma fatura pendente. Responda 1 para pagar.",
"callback_url": "http://billing-app:9000/ussd"
}'

Ciclo de Vida da Sessão​

Cada diálogo USSD é rastreado como uma sessão com um session_id único.

Estados da Sessão​

Sessão de Uma Interação​

Se seu callback retornar "end" na primeira interação, nenhuma sessão persistente é criada. O gateway envia um MAP End com o resultado e retorna imediatamente.

Sessão de Múltiplas Interações​

Se seu callback retornar "continue", o gateway:

  1. Cria um Session GenServer registrado em UssdGateway.Registry
  2. Envia um MAP Continue com opcode 60 (unstructuredSS-Request) para o móvel
  3. Aguarda a resposta do assinante (até turn_timeout_ms)
  4. Encaminha a resposta para seu callback
  5. Repete até que seu callback retorne "end" ou ocorra um timeout

Comportamento de Timeout​

TimeoutPadrãoEfeito
Timeout de interação30 segundosSe o assinante não responder dentro desta janela, a sessão é encerrada com um erro MAP.
Timeout de sessão3 minutosDuração total da sessão. Encerra a sessão independentemente da atividade.
Timeout de callback HTTP5 segundosSe sua aplicação não responder a tempo, o gateway envia um erro MAP para o móvel e encerra a sessão.

Tratamento de Erros​

O gateway lida com falhas de forma elegante e sempre tenta enviar uma resposta de erro MAP para o móvel, para que o assinante veja uma mensagem significativa em vez de um timeout de rede.

CenárioAção do GatewayCódigo de Erro MAP
Timeout de callback HTTP ou 5xxEncerrar sessão, enviar MAP End com erro34 (systemFailure)
JSON inválido do callbackEncerrar sessão, enviar MAP End com erro34 (systemFailure)
Texto USSD excede max_text_lengthTruncar texto, registrar aviso, continuar normalmenteN/A (truncado, não é um erro)
Timeout do assinante (sem resposta)Encerrar sessão, enviar MAP End com erro34 (systemFailure)
Nenhuma rota corresponde ao código curtoEnviar MAP End com erro, registrar aviso34 (systemFailure)
Falha do Session GenServerSessão morre, assinante vê timeout de redeN/A (saída do processo)
Gateway USSD não habilitadoRetornar Facility Not Supported21 (facilityNotSupported)

Métricas e Monitoramento​

O Gateway USSD expõe métricas Prometheus no endpoint padrão /metrics (porta 9090).

Métricas USSD​

Métrica: ussd_requests_total Tipo: Contador Descrição: Total de requisições USSD processadas Rótulos:

  • direction: "inbound" (originado pelo móvel) ou "outbound" (push originado pela rede)

Métrica: ussd_active_sessions Tipo: Gauge Descrição: Declarado como um gauge para o número de sessões USSD ativas.

Caveat: Este gauge é declarado mas nunca atualizado na versão atual: ele sempre lê 0. Não confie nele para contagens de sessões ativas. Acompanhe a atividade da sessão via ussd_requests_total em vez disso.

Métrica: map_request_duration_milliseconds Tipo: Histograma Descrição: Duração das operações de envio USSD em milissegundos Rótulos:

  • operation: "ussd_send" para requisições de push de saída

Exemplos de Consultas Prometheus​

# Taxa de requisições USSD por direção (entrada vs saída)
rate(ussd_requests_total[5m])

# Latência USSD de saída (p95)
histogram_quantile(0.95, rate(map_request_duration_milliseconds_bucket{operation="ussd_send"}[5m]))

Servidor de Callback de Exemplo​

Python (Flask)​

from flask import Flask, request, jsonify

app = Flask(__name__)
sessions = {}

@app.route('/ussd', methods=['POST'])
def ussd():
data = request.json
session_id = data['session_id']
text = data['text']
turn = data['turn']

if data['type'] == 'initiation':
sessions[session_id] = {'state': 'main_menu'}
return jsonify({
'action': 'continue',
'text': 'Bem-vindo!\n1. Ver saldo\n2. Comprar crédito\n3. Transferir'
})

state = sessions.get(session_id, {}).get('state')

if state == 'main_menu':
if text == '1':
del sessions[session_id]
return jsonify({
'action': 'end',
'text': 'Seu saldo é $5.00'
})
elif text == '2':
sessions[session_id]['state'] = 'buy_airtime'
return jsonify({
'action': 'continue',
'text': 'Digite o valor:'
})
else:
del sessions[session_id]
return jsonify({
'action': 'end',
'text': 'Opção inválida. Adeus.'
})

elif state == 'buy_airtime':
del sessions[session_id]
return jsonify({
'action': 'end',
'text': f'Você comprou ${text} de crédito. Obrigado!'
})

return jsonify({'action': 'end', 'text': 'Sessão expirada.'})

if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)

Node.js (Express)​

const express = require('express');
const app = express();
app.use(express.json());

const sessions = new Map();

app.post('/ussd', (req, res) => {
const { session_id, text, type } = req.body;

if (type === 'initiation') {
sessions.set(session_id, { state: 'main_menu' });
return res.json({
action: 'continue',
text: 'Bem-vindo!\n1. Ver saldo\n2. Comprar crédito'
});
}

const session = sessions.get(session_id);
if (!session) {
return res.json({ action: 'end', text: 'Sessão expirada.' });
}

if (session.state === 'main_menu' && text === '1') {
sessions.delete(session_id);
return res.json({ action: 'end', text: 'Seu saldo é $5.00' });
}

sessions.delete(session_id);
return res.json({ action: 'end', text: 'Adeus.' });
});

app.listen(9000, () => console.log('Callback USSD na porta 9000'));

Solução de Problemas​

Discar USSD Retorna "Serviço N��o Disponível"​

Sintomas: O assinante disca um código curto e imediatamente recebe um erro de rede.

Possíveis causas:

  • ussd_gateway_enabled é false
  • Nenhuma rota corresponde ao código curto discado
  • Conexão M3UA está inativa

Resolução:

  1. Verifique ussd_gateway_enabled: true na configuração
  2. Verifique se um padrão de rota corresponde ao código curto (lembre-se de incluir * como fallback)
  3. Verifique o status do par M3UA na interface Web (página de Pares)

Callback Não Recebendo Requisições​

Sintomas: Os logs do gateway mostram o início do USSD, mas sua aplicação nunca recebe o POST HTTP.

Possíveis causas:

  • URL de callback é inatingível a partir do host OmniSS7
  • Firewall bloqueando HTTP de saída do OmniSS7
  • Aplicação de callback não está em execução

Resolução:

  1. Teste a conectividade: curl -v http://your-app:9000/ussd a partir do host OmniSS7
  2. Verifique as regras do firewall para HTTP de saída
  3. Verifique se sua aplicação de callback está ouvindo na porta configurada

Sessões Expirando Prematuramente​

Sintomas: Sessões de múltiplas interações terminam com "systemFailure" antes que o assinante possa responder.

Possíveis causas:

  • turn_timeout_ms é muito curto para sua base de assinantes
  • http_timeout_ms é muito curto para o tempo de processamento da sua aplicação
  • Latência de rede entre OmniSS7 e seu servidor de callback

Resolução:

  1. Aumente turn_timeout_ms (o padrão de 30 segundos deve ser suficiente para a maioria dos casos)
  2. Aumente http_timeout_ms se sua aplicação precisar de mais tempo de processamento
  3. Implemente o servidor de callback próximo ao OmniSS7 para reduzir a latência

Erros de Codificação GSM de 7 bits​

Sintomas: Erros gsm7_encode_failed nos logs, ou uma resposta 500 de /api/ussd/send cujo campo error contém {:gsm7_encode_failed, ...}.

Possíveis causas:

  • O texto contém caracteres fora do alfabeto padrão de 7 bits GSM (por exemplo, emoji, caracteres CJK)

Resolução:

  • Restringir o texto USSD ao conjunto básico de caracteres GSM: letras ASCII, dígitos, pontuação comum e alguns caracteres gregos/nórdicos
  • Veja 3GPP TS 23.038 Seção 6.2.1 para a tabela completa de caracteres

Documentação Relacionada​