Uma integração.
Dois fluxos.
Autentique usuários NIO no seu serviço e envie notificações de ativação ou cancelamento. Cada fluxo tem seu próprio contrato e sua própria credencial.
Login do parceiro
NIO → elegibilidade → código de uso único.
Notificações NIO
Ativação ou cancelamento → recebimento durável.
A URL base de produção é https://oauth-nio.oston.io. Para integrar, solicite à Oston a chave do seu app e o segredo do backend (veja Cadastro e homologação). Os exemplos usam seu_app no lugar da chave real.
Autenticação do parceiro
A integração exige um backend. O usuário informa suas credenciais somente no NIO. A Oston verifica a identidade e a elegibilidade para os produtos cadastrados no app solicitado.
Cadastre a integração
A Oston cadastra seu app com uma chave (por exemplo seu_app), a URL HTTPS exata de retorno que você informar e um segredo exclusivo do seu backend. Cada app tem sua própria chave e seu próprio segredo; não compartilhe credenciais entre apps ou ambientes.
O segredo tem 32 bytes aleatórios, representados por 64 caracteres hexadecimais minúsculos. Guarde-o apenas no backend; nunca em aplicativos, páginas, URLs ou logs.
Inicie com state e PKCE
- Gere um
stateimprevisível de 32 a 128 caracteres e umcode_verifieraleatório de 43 a 128 caracteres. Use apenasA–Z a–z 0–9 . _ ~ -. - Guarde ambos na sessão de login do seu backend. Calcule
code_challenge = BASE64URL(SHA256(code_verifier)), sem padding. - Redirecione o navegador para
/logincom os parâmetros abaixo. A sessão do navegador com a Oston deve sobreviver à ida ao NIO e ao retorno.
/loginRedirecionamento do navegadorhttps://oauth-nio.oston.io/login
?app=seu_app
&state={state_da_sessao}
&code_challenge={challenge_base64url}
&code_challenge_method=S256
As quebras de linha são apenas para leitura. Envie uma única URL com os parâmetros codificados.
Uma nova tentativa no mesmo navegador substitui a anterior. GET /callback é reservado ao retorno do NIO; o parceiro não chama esse endpoint diretamente.
Receba o retorno no backend
https://backend.parceiro.example/callback
?code={codigo_de_uso_unico}
&state={state_original}
Compare o state recebido com o da sessão e consuma essa transação uma única vez. Rejeite divergências. Remova os parâmetros do navegador e impeça cache, envio de referrer e registro da query string nos seus logs.
Troque o código por uma decisão
Faça a troca no backend em até 60 segundos. O código é ligado ao app e ao PKCE e só pode ser consumido uma vez, inclusive em requisições concorrentes.
/api/auth/exchangeBackend → OstonAuthorization: Basic BASE64(app:segredo_do_backend)
Content-Type: application/json
{
"code": "codigo_recebido",
"code_verifier": "verificador_original"
}
Resposta de sucesso · 200
{
"app": "seu_app",
"subject": "identificador-opaco-estavel-especifico-do-app",
"eligible": true,
"eligibility_source": "open",
"authenticated_at": 1792500000,
"expires_at": 1792503600
}
| Campo | Como usar |
|---|---|
app | Deve coincidir com o app que iniciou o login. |
subject | Identificador opaco e estável para esse app. Não é CPF e difere entre apps. |
eligible | Exija true para conceder acesso. |
eligibility_source | Aceite live ou open em produção; rejeite qualquer outro valor (por exemplo mock, que existe somente em testes). |
authenticated_at / expires_at | Segundos Unix UTC. Os valores acima são ilustrativos; valide a expiração recebida. |
A pedido do NIO, enquanto a nova verificação de elegibilidade não está disponível, todo usuário autenticado no NIO recebe eligible: true com eligibility_source: "open". A autenticação NIO (PKCE, nonce e assinatura) continua obrigatória. Quando a verificação voltar, o valor passa a ser live e usuários sem produto recebem access_denied. Não trate open como prova de assinatura.
Exemplo de troca (Node.js)
const response = await fetch('https://oauth-nio.oston.io/api/auth/exchange', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${APP_KEY}:${APP_SECRET}`).toString('base64'),
'Content-Type': 'application/json',
},
body: JSON.stringify({ code, code_verifier: session.codeVerifier }),
signal: AbortSignal.timeout(10_000),
});
const result = await response.json();
const valid = response.ok
&& result.app === APP_KEY
&& result.eligible === true
&& ['live', 'open'].includes(result.eligibility_source)
&& result.expires_at > Date.now() / 1000;
if (!valid) return denyAccess();
// result.subject identifica o usuário no seu app; a sessão expira até result.expires_at.
Crie sua sessão com expiração absoluta no máximo em expires_at. Após uma hora, reinicie o login para consultar novamente a elegibilidade. Não há refresh token, logout global ou revogação instantânea. A resposta não é um JWT transferível.
Tokens NIO/Salesforce e CPF não são retornados ao parceiro. Esta integração não oferece as antigas rotas /api/auth/refresh, /api/user ou /api/user/products.
Apps móveis
Apps iOS e Android usam o mesmo contrato, sempre com um backend. O segredo nunca vai para o aplicativo: qualquer valor embutido em um app publicado pode ser extraído.
- O app gera o próprio
verifieraleatório e envia apenas ochallenge(S256) ao seu backend, abrindo a URL de início do login no navegador do sistema:ASWebAuthenticationSessionno iOS ou Custom Tabs no Android (por exemploWebBrowser.openAuthSessionAsyncno Expo). Não use WebView: o NIO e as lojas desaconselham login em navegador embutido. - Seu backend cria
stateecode_verifier, guarda junto com ochallengedo app e redireciona parahttps://oauth-nio.oston.io/login. - O retorno chega à URL HTTPS cadastrada do seu backend, que valida o
statee faz a troca com o segredo. - O backend redireciona para o esquema do app (por exemplo
seuapp://auth?handoff=…) com um código de uso único e curta duração, sem tokens. - O app troca esse código no seu backend enviando o
verifieroriginal e recebe a sessão do seu serviço.
Ligar o código de handoff ao verifier do app impede que outro aplicativo que registre o mesmo esquema use o código interceptado. Prefira Universal Links ou App Links quando disponíveis.
Exemplo (React Native / Expo)
const verifier = Crypto.randomUUID() + Crypto.randomUUID();
const challenge = base64url(await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256, verifier, { encoding: Crypto.CryptoEncoding.BASE64 }));
const result = await WebBrowser.openAuthSessionAsync(
`https://login.seubackend.example/nio/login?challenge=${challenge}`,
'seuapp://auth',
);
if (result.type !== 'success') return;
const { handoff, error } = Linking.parse(result.url).queryParams ?? {};
// POST handoff + verifier ao seu backend → sessão do seu serviço
No iOS, o sistema exibe "deseja usar <domínio> para iniciar sessão" com o domínio da primeira URL aberta. Use um domínio da sua marca para o início do login.
Notificações de ativação e cancelamento
O NIO envia eventos de entrada e ativação de produto, ou de cancelamento, referentes a um usuário e serviço. A rota informa o tipo do evento. O esquema dos campos NIO ainda será confirmado: o recebimento atual preserva o corpo sem interpretar usuário, serviço ou assinatura.
/api/webhooks/nio/activation/api/webhooks/nio/cancellationCredencial independente
Use Authorization: Bearer <token>. O token contém 64 caracteres hexadecimais minúsculos, gerados a partir de 32 bytes aleatórios e entregues por canal seguro. Ele é independente dos tokens OAuth e do segredo do parceiro.
POST /api/webhooks/nio/activation
Authorization: Bearer <token_entregue_ao_NIO>
Content-Type: application/json
{}
{} demonstra apenas o transporte; não define o esquema comercial do evento. Para cancelamentos, use a rota /api/webhooks/nio/cancellation.
Formato e tamanho
Envie texto UTF-8 com application/json, application/xml, text/xml ou text/plain. É aceito charset=utf-8 ou charset=UTF-8, com ou sem espaço após o ponto e vírgula. Sem Content-Type, o padrão é application/json.
O corpo é opaco: JSON malformado, arrays, XML, espaços e corpo vazio são preservados. Não envie binários, compressão ou outras codificações. Cabeçalhos arbitrários, query strings e credenciais não fazem parte do registro.
Mantenha as notificações abaixo de 190 KiB. O limite real é 262.144 bytes para o envelope inteiro na fila, incluindo o corpo em base64 e metadados. Portanto, 256 KiB não é o limite do corpo original.
Confirmação de recebimento · 200
{
"accepted": true,
"receipt_id": "identificador-gerado-pelo-gateway"
}
A confirmação acontece depois que a fila aceita a mensagem. O armazenamento é assíncrono, com retenção prevista de cinco anos. Isso não significa que uma assinatura já foi ativada ou cancelada; o processamento comercial ainda depende do esquema NIO.
Uma nova tentativa HTTP gera outro recibo, mesmo com corpo idêntico. Não há garantia de ordenação entre eventos. O armazenamento trata repetições do mesmo recibo, mas a deduplicação de eventos comerciais ainda não faz parte do contrato. Esses eventos não alteram o login ou revogam sessões no estado atual.
Erros e retentativas
Login e troca de código
Após uma transação válida, falhas de login retornam à URL cadastrada com error e o state original, sem código. Transações inválidas ou expiradas retornam JSON HTTP 400 sem redirecionamento.
| Erro / situação | Ação do parceiro |
|---|---|
access_denied | Não conceda acesso: não foi encontrado um produto permitido para o app. |
salesforce_unavailable | A consulta falhou. Não interprete como ausência de assinatura. |
customer_document_missing, invalid_customer_document, identity_mismatch, invalid_token, authorization_failed | Não crie sessão. Reinicie o login quando apropriado; acione a Oston se persistir. |
salesforce_environment_mismatch | Negue acesso e acione a Oston para verificar a configuração. |
401 · invalid_client na troca | Verifique app e segredo do backend. |
400 · invalid_request / invalid_grant na troca | Verifique o formato; reinicie o login para obter um novo código e PKCE. |
| 429 / 503 | Aguarde antes de uma nova tentativa. Não conceda acesso com base em uma falha. |
Timeout na troca: o código pode já ter sido consumido. Reinicie o login; a troca não oferece repetição idempotente da resposta.
Notificações NIO
| Resposta | Ação do NIO |
|---|---|
200 · accepted: true | Guarde o receipt_id para correlação. A mensagem foi aceita na fila. |
| 401 / 403 | Verifique a credencial. Um 403 persistente também pode ser bloqueio WAF; solicite análise à Oston antes de rotacionar o token. |
| 413 | Reduza o tamanho do evento antes de reenviar. |
| 415 | Use um dos Content-Types suportados em UTF-8. |
| 429, 5xx ou timeout | Retente com espera exponencial e jitter. Preserve o evento para replay assistido. |
Um timeout pode ocorrer depois do enfileiramento; uma retentativa pode produzir outro recibo. Falhas da fila retornam 503 sanitizado, e o gateway ou o autorizador podem gerar outros 5xx. O serviço não transforma falhas em confirmações falsas.
Alta disponibilidade não significa 100% de respostas bem-sucedidas. O comportamento de retentativa e replay do NIO deve ser validado na homologação.
Cadastro e homologação
Envie à Oston
- Nome do app e plataformas (web, iOS, Android).
- URL HTTPS exata de retorno no seu backend, por exemplo
https://login.seubackend.example/nio/callback. Ela é comparada literalmente; alterações exigem novo cadastro. - Contato técnico para incidentes e rotação de credenciais.
Você recebe
- A chave do app (
app) e o segredo do backend, entregue por canal seguro. A Oston guarda apenas o hash do segredo; em caso de perda, um novo segredo é emitido.
Antes de entrar em produção
Resultados sintéticos ou eligibility_source=mock não comprovam a integração real.
- Confirme app, callback HTTPS e entrega segura das credenciais.
- Valide login elegível e inelegível, state, PKCE, troca única e expiração da sessão do parceiro.
- Em apps móveis, valide o login pelo navegador do sistema, o handoff de uso único e o retorno ao app.
- NIO: envie ativação e cancelamento, registre os recibos e confirme a persistência com a Oston.
- Exercite credencial inválida, tamanho excedido, indisponibilidade e retentativas sem perder eventos.
- Confirme o esquema por usuário/serviço, duplicidade, ordenação e replay antes de depender de efeitos sobre assinaturas.
Para suporte, informe ambiente, horário UTC e identificadores de correlação disponíveis. Não envie tokens, senhas, CPF ou payloads pessoais em tickets.