Autenticação
Visão Geral
A API FyPay utiliza autenticação baseada em OAuth 2.0 (clientId + clientSecret) combinada com certificado de cliente X.509 (mTLS). O certificado é apresentado no handshake TLS da chamada de geração de token; as demais rotas usam apenas o token Bearer.
Por que mTLS?
O mTLS (mutual TLS) oferece segurança superior comparado a credenciais simples:
- Autenticação mútua: Tanto o cliente quanto o servidor se autenticam no handshake TLS
- Não-repúdio: Certificados vinculados à conta garantem rastreabilidade
- Proteção contra roubo de credenciais: Mesmo com
clientId/clientSecretvazados, o atacante precisaria do certificado e da chave privada
Pré-requisitos
Antes de iniciar, você precisará:
Certificado em formato PEM, chave RSA 2048, válido por 2 anos e vinculado à sua conta, junto com a respectiva chave privada. Nos exemplos abaixo, os arquivos se chamam client.crt (certificado) e client.key (chave privada).
Gere seu clientId e clientSecret no Painel FyPay, em Configurações → Credenciais. O clientSecret é exibido uma única vez: copie-o e guarde-o em um cofre de segredos.
Configure seu cliente HTTP para apresentar o certificado e a chave privada no handshake TLS (mecanismo nativo de TLS da sua linguagem).
O certificado deve estar vinculado à sua conta. Certificados não vinculados, ou vinculados a outra conta, são rejeitados mesmo que tecnicamente válidos.
O certificado é entregue apenas em PEM; não há arquivo .pfx. Se sua stack exigir PKCS#12 (por exemplo, alguns cenários Windows/.NET), converta localmente:
openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfxEndpoint de Autenticação
POST /api/auth/token
Gera um token JWT de acesso. O tempo de vida do token, em segundos, vem no campo expires_in da resposta.
Esta é a única rota que exige o certificado de cliente. Ele deve ser apresentado no handshake TLS (mTLS), e não em um header HTTP.
Request
curl -X POST https://api.fypay.me/api/auth/token \
--cert client.crt \
--key client.key \
-H "Content-Type: application/json" \
-d '{
"clientId": "6967c0a0-3566-48d0-8c13-717038b52279",
"clientSecret": "seu-client-secret"
}'| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
clientId | string (UUID v4) | Sim | Identificador da credencial gerada no painel |
clientSecret | string | Sim | Segredo da credencial, exibido uma única vez no painel |
Response (201 Created)
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 600,
"scope": "email profile"
}| Campo | Descrição |
|---|---|
access_token | Token JWT a ser enviado em Authorization: Bearer <token> |
token_type | Sempre Bearer |
expires_in | Tempo de vida do token em segundos. Use este valor para agendar a renovação |
scope | Escopo do token |
Exemplo Prático: Node.js
Instalação
npm install undiciCódigo Completo
const fs = require('fs');
const { Agent, request } = require('undici');
const mtlsAgent = new Agent({
connect: {
cert: fs.readFileSync('./client.crt'),
key: fs.readFileSync('./client.key'),
},
});
async function getToken() {
const { statusCode, body } = await request('https://api.fypay.me/api/auth/token', {
method: 'POST',
dispatcher: mtlsAgent,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: process.env.FYPAY_CLIENT_ID,
clientSecret: process.env.FYPAY_CLIENT_SECRET,
}),
});
const data = await body.json();
if (statusCode !== 201) {
throw new Error(`Erro ao obter token: ${data.code} ${data.message}`);
}
console.log('Token obtido com sucesso!');
console.log('Expira em:', data.expires_in, 'segundos');
return data;
}
getToken().catch(console.error);Usando o módulo nativo https, o equivalente é new https.Agent({ cert, key }) passado na opção agent da requisição.
Exemplo Prático: Python
Instalação
pip install requestsCódigo Completo
import os
import requests
url = 'https://api.fypay.me/api/auth/token'
payload = {
'clientId': os.environ.get('FYPAY_CLIENT_ID'),
'clientSecret': os.environ.get('FYPAY_CLIENT_SECRET'),
}
try:
response = requests.post(
url,
json=payload,
cert=('client.crt', 'client.key'),
timeout=30,
)
response.raise_for_status()
data = response.json()
print('Token obtido com sucesso!')
print(f"Expira em: {data['expires_in']} segundos")
except requests.exceptions.RequestException as e:
print(f'Erro ao obter token: {e}')
if e.response is not None:
print(f'Resposta: {e.response.text}')Outras linguagens
Em todas elas, o certificado e a chave são carregados pelo mecanismo nativo de TLS do cliente HTTP:
| Linguagem | Mecanismo |
|---|---|
| PHP (cURL) | CURLOPT_SSLCERT = client.crt e CURLOPT_SSLKEY = client.key |
| C# (.NET) | HttpClientHandler.ClientCertificates.Add(X509Certificate2.CreateFromPemFile("client.crt", "client.key")) |
| Go | tls.LoadX509KeyPair("client.crt", "client.key") em tls.Config.Certificates |
| Java | Converta para PKCS#12 com openssl pkcs12 -export e carregue como KeyStore no SSLContext |
Utilizando o Token
Após obter o token, inclua-o no header Authorization das demais requisições. Essas rotas não exigem certificado de cliente:
curl -X GET https://api.fypay.me/api/balance \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Renovação de Token
Não assuma uma duração fixa para o token. Leia expires_in em cada resposta e renove o token antes que ele expire.
Estratégia Recomendada
const fs = require('fs');
const { Agent, request } = require('undici');
class TokenManager {
constructor(clientId, clientSecret, certPath, keyPath) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.agent = new Agent({
connect: {
cert: fs.readFileSync(certPath),
key: fs.readFileSync(keyPath),
},
});
this.token = null;
this.expiresAt = 0;
}
async getValidToken() {
const safetyMarginMs = 30000;
if (this.token && Date.now() < this.expiresAt - safetyMarginMs) {
return this.token;
}
return this.refreshToken();
}
async refreshToken() {
const { statusCode, body } = await request('https://api.fypay.me/api/auth/token', {
method: 'POST',
dispatcher: this.agent,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: this.clientId,
clientSecret: this.clientSecret,
}),
});
const data = await body.json();
if (statusCode !== 201) {
throw new Error(`${data.code}: ${data.message}`);
}
this.token = data.access_token;
this.expiresAt = Date.now() + data.expires_in * 1000;
return this.token;
}
}
const tokenManager = new TokenManager(
process.env.FYPAY_CLIENT_ID,
process.env.FYPAY_CLIENT_SECRET,
'./client.crt',
'./client.key'
);
const token = await tokenManager.getValidToken();Validação do Certificado
O sistema realiza as seguintes validações no certificado apresentado no handshake:
- Handshake mTLS: O certificado deve ter sido emitido pela FyPay e você deve possuir a chave privada correspondente
- Validade: O certificado precisa estar dentro do período de validade (2 anos a partir da emissão)
- Registro: O fingerprint SHA-256 do certificado deve estar registrado e ativo
- Vinculação à conta: O certificado deve estar vinculado à mesma conta da credencial (
clientId)
Certificados não vinculados ou vinculados a outra conta serão rejeitados, mesmo que tecnicamente válidos.
Respostas de erro
Todos os erros retornados por POST /api/auth/token seguem o mesmo formato:
{
"statusCode": 400,
"timestamp": "2026-04-24T16:59:25.577Z",
"path": "/api/auth/token",
"method": "POST",
"code": "PUB_CERT_HEADER_MISSING",
"message": "Client certificate header is missing",
"userMessage": "Client certificate is required.",
"details": {
"hint": "Present your client certificate during the TLS handshake (e.g. curl --cert client.crt --key client.key)."
},
"errorId": "81421e7a5afbd9cf25212f51dac08da1"
}code: identificador estável legível por máquina. Faça o tratamento com base neste campo.message: descrição técnica resumida (em inglês, voltada para logs).userMessage: descrição voltada ao usuário final.details.hint: orientação prática para desenvolvedores integrando a API.errorId: identificador único da requisição. Informe-o ao contatar o suporte.
Referência de códigos de erro
| HTTP | Código | Significado |
|---|---|---|
| 400 | PUB_CERT_HEADER_MISSING | Nenhum certificado de cliente foi apresentado no handshake TLS |
| 400 | PUB_CERT_MALFORMED_PEM | Certificado em formato PEM inválido |
| 400 | PUB_REQUEST_BODY_INVALID | Corpo JSON inválido |
| 401 | PUB_CERT_NOT_VERIFIED | O certificado apresentado no handshake foi rejeitado |
| 401 | PUB_CERT_NOT_YET_VALID | A data de início de validade do certificado está no futuro |
| 401 | PUB_CERT_EXPIRED | O certificado expirou |
| 401 | PUB_CERT_NOT_REGISTERED | O certificado não está registrado ou foi revogado |
| 401 | PUB_INVALID_CREDENTIALS | clientId ou clientSecret incorretos |
| 403 | PUB_CERT_NOT_AUTHORIZED_FOR_ACCOUNT | O certificado não está vinculado à conta do clientId |
| 403 | PUB_ACCOUNT_INACTIVE | Credenciais corretas, mas a conta não está ativa |
| 502 | PUB_AUTH_UPSTREAM_ERROR | Erro inesperado no serviço de autenticação |
| 503 | PUB_AUTH_UPSTREAM_UNAVAILABLE | Serviço de autenticação temporariamente indisponível |
PUB_CERT_HEADER_MISSING (400)
Nenhum certificado de cliente foi apresentado no handshake TLS.
Como resolver: configure seu cliente HTTP para enviar o certificado e a chave privada na conexão TLS (por exemplo, curl --cert client.crt --key client.key). Se houver um proxy ou gateway entre sua aplicação e a FyPay que termine o TLS, a conexão com api.fypay.me precisa partir de um cliente que apresente o certificado.
PUB_CERT_MALFORMED_PEM (400)
Certificado em formato PEM inválido.
PUB_REQUEST_BODY_INVALID (400)
O corpo JSON não satisfez o esquema (campo ausente, tipo errado, ou clientId não é um UUID v4). Verifique details.violations para identificar o(s) campo(s) com problema e envie Content-Type: application/json.
PUB_CERT_NOT_VERIFIED (401)
O certificado apresentado no handshake TLS foi rejeitado.
Como resolver: use o certificado emitido pela FyPay para sua conta e a chave privada correspondente. Certificados de outras autoridades certificadoras não são aceitos.
PUB_CERT_NOT_YET_VALID (401)
A data notBefore do certificado está no futuro.
Como resolver: sincronize o relógio do sistema via NTP. Se a data realmente estiver no futuro, aguarde ou solicite um novo certificado.
PUB_CERT_EXPIRED (401)
A data notAfter do certificado expirou. Os certificados valem 2 anos a partir da emissão.
Como resolver: solicite um novo certificado ao suporte e substitua-o na sua integração. Planeje a troca antes do vencimento.
PUB_CERT_NOT_REGISTERED (401)
O fingerprint SHA-256 do certificado não está no registro: ele nunca foi registrado ou foi revogado.
Como resolver: calcule o fingerprint local com openssl x509 -noout -fingerprint -sha256 -in client.crt e informe-o ao suporte junto com o errorId da resposta.
PUB_INVALID_CREDENTIALS (401)
O clientId não é reconhecido, ou o clientSecret não corresponde. A plataforma deliberadamente não diferencia esses casos (resistência a enumeração).
Como resolver: verifique o clientId e o clientSecret. Como o clientSecret só é exibido uma vez, se você o perdeu gere uma nova credencial em Configurações → Credenciais.
PUB_CERT_NOT_AUTHORIZED_FOR_ACCOUNT (403)
O certificado é válido e registrado, mas não está vinculado à conta que possui o clientId informado.
Como resolver: verifique se você está usando o par certificado/clientId da mesma conta.
PUB_ACCOUNT_INACTIVE (403)
As credenciais estão corretas, mas a conta não está ativa. Novas tentativas não terão sucesso.
Como resolver: contate o suporte com o errorId da resposta.
PUB_AUTH_UPSTREAM_UNAVAILABLE (503)
O serviço de autenticação está temporariamente inacessível.
Como resolver: tente novamente com backoff exponencial (por exemplo, 1s, 2s, 4s). Se o problema persistir por mais de um minuto, contate o suporte.
PUB_AUTH_UPSTREAM_ERROR (502)
O serviço de autenticação retornou um erro inesperado.
Como resolver: contate o suporte com o errorId.
Resolução de problemas
"Recebo PUB_CERT_HEADER_MISSING mesmo enviando o certificado"
O certificado precisa ir no handshake TLS, não em um header HTTP. Confirme que o cliente HTTP está configurado com certificado e chave (por exemplo, --cert/--key no curl, cert=(crt, key) no Python requests) e que nenhum proxy intermediário termina o TLS antes de chegar à FyPay.
"Recebo ocasionalmente PUB_CERT_NOT_YET_VALID"
O relógio do sistema está atrás da data notBefore do certificado. Configure o NTP e garanta que o relógio do host esteja com poucos segundos de diferença do UTC. Contêineres podem herdar a deriva de relógio do host; verifique com date -u dentro do contêiner.
"Recebo PUB_CERT_NOT_REGISTERED após trocar o certificado"
Calcule o fingerprint do novo certificado (openssl x509 -noout -fingerprint -sha256 -in client.crt) e confirme com o suporte que ele está registrado e vinculado à sua conta antes de fazer a transição.