OAuth Token Authentication
To make authorized API calls to Nopan, clients must first obtain an OAuth 2.0 access token.
This token is issued using the client_credentials grant and requires mutual TLS (mTLS) with your client certificate.
The resulting access token must be included in all subsequent API requests using the Authorization header.
All token requests must use mutual TLS (mTLS) with your registered client certificate.
Without a valid certificate, the token endpoint will reject your request.
Requesting an OAuth Token
Use the following examples to request an OAuth 2.0 access token for authenticating with Nopan APIs:
- cURL
- Java
- JavaScript
curl --cert client-cert.pem --key client-key.pem \
-X POST https://api.nopan.com/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id={{your_organization_id}}" \
--data-urlencode "scope=payments:process"
private static final String TOKEN_ENDPOINT_URL = "https://api.nopan.com/auth/token";
private static final String ORGANIZATION_ID = "your-organization-id";
public HttpResponse tokenRequest() throws IOException
{
String form = Map.of(
"grant_type", "client_credentials",
"client_id", ORGANIZATION_ID,
"scope", "payments:process"
)
.entrySet()
.stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
// Create an http request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TOKEN_ENDPOINT_URL))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form))
.build();
// Create a client with the certificate and key
SSLContext sslContext = ;
HttpClient client = HttpClientBuilder.create()
.setSSLSocketFactory(createSslContext(CERT_FILE_PATH, KEY_FILE_PATH, KEY_PASSWORD))
.build();
// Send the request and get the response
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response;
}
private static SSLContext createSslContext(String certFilePath, String keyFilePath, String keyPassword) throws Exception {
// Load Certificate from PEM file
X509Certificate certificate;
try (FileReader certReader = new FileReader(certFilePath); PEMParser pemParser = new PEMParser(certReader)) {
X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject();
certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
}
// Load Private Key from PEM file
KeyPair keyPair;
try (FileReader keyReader = new FileReader(keyFilePath); PEMParser pemParser = new PEMParser(keyReader)) {
Object pemObject = pemParser.readObject();
if (pemObject instanceof PEMKeyPair) {
keyPair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair((PEMKeyPair) pemObject);
} else if (pemObject instanceof PrivateKeyInfo) {
keyPair = new KeyPair(null, new JcaPEMKeyConverter().setProvider("BC").getPrivateKey((PrivateKeyInfo) pemObject));
} else {
throw new IOException("Unsupported key type in PEM file: " + pemObject.getClass().getName());
}
}
// Create an in-memory KeyStore
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null); // Initialize a new empty KeyStore
// Add the certificate and private key to the KeyStore
char[] passwordChars = keyPassword.toCharArray();
keyStore.setKeyEntry("client-alias", keyPair.getPrivate(), passwordChars, new Certificate[]{certificate});
// Create KeyManagerFactory to manage our private key
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, passwordChars);
// Create TrustManagerFactory (using default system truststores)
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
// Create and initialize the SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
return sslContext;
}
// Read the certificate and private key files from the filesystem.
const cert = fs.readFileSync(CERT_FILE_PATH);
const key = fs.readFileSync(KEY_FILE_PATH);
async function getToken() {
try {
// Create a custom HTTPS agent configured for mutual TLS (mTLS).
// This agent will present the client certificate during the TLS handshake.
const httpsAgent = new https.Agent({
cert: cert,
key: key,
});
// Prepare the form data for the request body.
const formData = {
grant_type: 'client_credentials',
client_id: ORGANIZATION_ID,
scope: 'payments:process'
};
const requestBody = querystring.stringify(formData);
console.log("Sending POST request to " + TOKEN_ENDPOINT_URL);
console.log("Request Body: " + requestBody);
// Send the request. We pass the custom httpsAgent in the request configuration.
const response = await axios.post(TOKEN_ENDPOINT_URL, requestBody, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
httpsAgent: httpsAgent
});
} catch (error) {
console.error("\nAn error occurred while making the token request.");
// Axios provides rich error details
if (error.response) {
// The request was made and the server responded with a status code
console.error("Error Status: ", error.response.status);
console.error("Error Data: ", error.response.data);
console.error("Error Headers: ", error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.error("No response received for the request: ", error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error("Error setting up request: ", error.message);
}
}
}
Scopes
We currently support next scopes:
payments:readpayments:processdata:reportsTo request non-default processing scope, include it in the token request:
curl --cert client-cert.pem --key client-key.pem \
-X POST https://api.nopan.com/auth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id={{your_organization_id}}" \
--data-urlencode "scope=payments:process"
Inspecting Your OAuth Access Token
Access tokens are issued as JWTs. You usually don’t need to decode the JWT because expires_in within the /auth/token response tells you token lifetime.
Your response will look like this:
{
"access_token": "...",
"expires_in": 7200,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0,
"scope": "payments:process"
}
expires_in: lifetime in seconds (e.g., 7200 = 2 hours).refresh_expires_in: always0. There are no refresh tokens; always request a new access token when the old one expires.
Same token could be used for multiple requests within the allowed scope.
Renew tokens automatically a few minutes before expires_in elapses (e.g., 5 minutes before expiration).
Check the OpenAPI spec for /auth/token spec for the response format.
Using the Token
After you obtain the token, include it in your API calls:
curl -X POST https://api.nopan.com/payments/initiate \
-H "Authorization: Bearer <access-token>" \
-H "Idempotency-Key: 63c2e3f0-12aa-41bb-ae62-f3d91fdbb762" \
-H "Signature: nopan_sig=<signature>" \
-H "Signature-Input: nopan_sig=("@status");keyid="your-key-id";created=1766678793 \
-H "Content-Type: application/json" \
-d '{
"clientTransactionId": "order123",
"transactionType": "ONE_TIME",
"paymentDetails": {
"amount": 100,
"currency": "PLN",
"country": "PL",
"description": "Product ID 1234 purchase"
},
"providerDetails": {
"providerId": "BLIK"
},
"payerDetails": {
"payerId": "payerUUID",
"oneTimeCode": "123456"
}
}'
For signing requests, see HTTP Signatures.
Common errors
Debugging Access Token
Decode the token only when debugging auth issues or validating configuration. There are a few ways to inspect your JWT token.
Using the Command Line
Decode the token payload locally for safe inspection:
TOKEN=eyJhbGciOi...<snip>...XYZ
# Extract payload and decode
echo "$TOKEN" |
cut -d '.' -f2 |
base64 -d | jq
Using the JWT.io Debugger
Paste your token into https://jwt.io to decode and inspect its contents.
Avoid using real production tokens in online tools.
Example output:
{
"exp": 1735750000,
"iat": 1735745000,
"scope": "payments:process",
"client_id": "your-organization-id",
...
}