OAuth 2.0 Authentication

Configure OAuth 2.0 client credentials for API authentication, including how to provision credentials in the Business Portal, request and cache access tokens, and implement responsible token management patterns.

PayNearMe supports OAuth 2.0 for API version 3.0+ using the industry-standard client credentials grant. OAuth provides token-based authorization for system-to-system API access. You manage client credentials and access tokens in the PayNearMe Business Portal without contacting Customer Support for each token.

PayNearMe continues to support HMAC signature authentication for API requests and for callbacks. You can use both methods on the same site: requests that include a Bearer token in the Authorization header use OAuth while requests without that header use signature authentication.

📘

Authorization vs. Authentication

OAuth 2.0 is an authorization protocol. It grants your application controlled access to PayNearMe API resources using access tokens. It does not replace consumer-facing login flows in your application.

Client Pre-Requisites

Before you provision OAuth credentials, confirm the following:

  • Your integration uses API version 3.0+. OAuth is not available for API versions 1.8 or 2.0.
  • OAuth is enabled for your site by your PayNearMe Technical Account Manager (TAM).
  • You have Business Portal access with both Developer and Admin permissions so you can provision sites and manage credentials.
👍

PayNearMe Setup

Your TAM will enable OAuth for your site before you can provision credentials. Contact them if the API Client Credentials page is not available/visible in your Business Portal.

Quick Start

  1. In the Business Portal, provision your site for OAuth (one-time action per site).
  2. Generate client the following credentials: Client ID, Client Secret, and Audience.
  3. From your application, request an access token from the PayNearMe Token Endpoint URL using the client credentials grant.
  4. Cache the access token and reuse it for API calls until it is near expiration.
  5. Call PayNearMe API endpoints using the Authorization: Bearer {access_token} header.
  6. Request a new access token when the current token is near expiration or has expired.
sequenceDiagram
  participant Client as ClientApplication
  participant Portal as BusinessPortal
  participant Auth as TokenEndpoint
  participant API as PayNearMeAPI

  Client->>Portal: Provision site and generate credentials
  Client->>Auth: POST client_credentials token request
  Auth-->>Client: access_token and expires_in
  Client->>Client: Cache token until near expiry
  Client->>API: API call with Authorization Bearer token

Generating Credentials in the Business Portal

To generate and configure your OAuth credentials, log into the Business Portal and complete the following steps:

  1. Navigate to Developer > API Documentation > API Client Credentials.

  2. Click Provision Site. This is a one-time action for each site. A confirmation success message displays. Provisioning creates a scope in the PayNearMe identity provider and ties it to your site's external ID.

  3. Click Generate Credentials. A success confirmation message displays.

  4. Verify that Client ID, Client Secret, Audience, and Created Date display correctly.

  5. Use the clipboard icon to copy the Client Secret. Store the Client ID, Client Secret, and Audience in a secure location (e.g., a secrets manager).

As long as the Client Secret is not exposed, you do not need to routinely change it.

🚧

Credential Limits and Rotation Requirements

Each site can only maintain a maximum of two credential sets. Each set can request tokens from PayNearMe. If two sets exist, the Generate Credentials button is disabled until you delete an existing set. PayNearMe allows two sets so you can migrate smoothly from one Client ID/Client Secret pair to another.

Deleting Credentials

You must delete a credential set when:

  • The set was used only for testing, or
  • You suspect a security incident and need to rotate to a new Client Secret.

To delete a set, click Delete on the credential row and confirm removal.

❗️

Deleted Secrets Cannot be Recovered

After you delete a credential set, token requests with that Client ID and Client Secret return 401 Unauthorized errors. You must generate a new credential set and update your application configuration.

API Keys and Callbacks

Callback verification continues to use API keys and HMAC signatures--even if you use OAuth for outbound API calls. See HMAC Signature Authentication and API Key Rotation Guidelines.

When OAuth is in use for outbound API calls, the API Keys & Signatures page (Developer > API Documentation > API Keys & Signatures) will display the following message:

This site uses API keys for callbacks only. See API Client Credentials for API access setup.

Token Endpoints

Request access tokens from the PayNearMe login host for your environment:

EnvironmentToken URL
Sandboxhttps://login.paynearme-sandbox.com/oauth/token
Productionhttps://login.paynearme.com/oauth/token
👍

Using the Correct API URL

API requests use the JSON API base URL for your environment (e.g., https://api.paynearme-sandbox.com/json-api for sandbox). Do not send token requests to the /json-api host.

Requesting an Access Token

Send a POST request with grant_type, client_id, client_secret, and audience. You can send the body as JSON or as application/x-www-form-urlencoded.

Example Request (JSON Data)

curl --request POST \
     --url https://login.paynearme-sandbox.com/oauth/token \
     --header 'content-type: application/json' \
     --data '{
  "grant_type": "client_credentials",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "audience": "YOUR_TOKEN_AUDIENCE"
}'

Example Request (URL-Encoded Form Data)

curl --request POST \
     --url https://login.paynearme-sandbox.com/oauth/token \
     --header 'content-type: application/x-www-form-urlencoded' \
     --data 'grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&audience=YOUR_TOKEN_AUDIENCE'

Example Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 86400
}

The expires_inresponse parameter is the token lifetime in seconds. A typical value is 86400 (24 hours). Store expires_in and the time you received the token so your application can refresh the access_token before expiration.

Use the following code samples to request tokens from your application. Replace placeholders with your Client ID, Client Secret, Audience, and the correct Token URL for your environment.

require 'uri'
require 'net/http'
require 'json'

url = URI('https://login.paynearme-sandbox.com/oauth/token')
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request['content-type'] = 'application/json'
request.body = {
  grant_type: 'client_credentials',
  client_id: 'YOUR_CLIENT_ID',
  client_secret: 'YOUR_CLIENT_SECRET',
  audience: 'YOUR_TOKEN_AUDIENCE'
}.to_json

response = http.request(request)
puts response.read_body
<?php

$url = 'https://login.paynearme-sandbox.com/oauth/token';
$body = http_build_query([
  'grant_type' => 'client_credentials',
  'client_id' => 'YOUR_CLIENT_ID',
  'client_secret' => 'YOUR_CLIENT_SECRET',
  'audience' => 'YOUR_TOKEN_AUDIENCE',
]);

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);
echo $response;

?>
const https = require('https');
const querystring = require('querystring');

const body = querystring.stringify({
  grant_type: 'client_credentials',
  client_id: 'YOUR_CLIENT_ID',
  client_secret: 'YOUR_CLIENT_SECRET',
  audience: 'YOUR_TOKEN_AUDIENCE',
});

const options = {
  hostname: 'login.paynearme-sandbox.com',
  path: '/oauth/token',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(body),
  },
};

const req = https.request(options, (res) => {
  let data = '';
  res.on('data', (chunk) => { data += chunk; });
  res.on('end', () => { console.log(data); });
});

req.write(body);
req.end();
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program {
  static async Task Main() {
    var client = new HttpClient();
    var url = "https://login.paynearme-sandbox.com/oauth/token";
    var form = new FormUrlEncodedContent(new[] {
      new KeyValuePair<string,string>("grant_type", "client_credentials"),
      new KeyValuePair<string,string>("client_id", "YOUR_CLIENT_ID"),
      new KeyValuePair<string,string>("client_secret", "YOUR_CLIENT_SECRET"),
      new KeyValuePair<string,string>("audience", "YOUR_TOKEN_AUDIENCE"),
    });
    var response = await client.PostAsync(url, form);
    Console.WriteLine(await response.Content.ReadAsStringAsync());
  }
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import java.util.Map;

public class OAuthTokenRequest {
  public static void main(String[] args) throws Exception {
    String body = Map.of(
      "grant_type", "client_credentials",
      "client_id", "YOUR_CLIENT_ID",
      "client_secret", "YOUR_CLIENT_SECRET",
      "audience", "YOUR_TOKEN_AUDIENCE"
    ).entrySet().stream()
      .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "=" +
                URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
      .collect(Collectors.joining("&"));

    HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://login.paynearme-sandbox.com/oauth/token"))
      .header("Content-Type", "application/x-www-form-urlencoded")
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();

    HttpResponse<String> response = HttpClient.newHttpClient()
      .send(request, HttpResponse.BodyHandlers.ofString());
    System.out.println(response.body());
  }
}
#!/usr/bin/env python3
import json
import urllib.request
import urllib.parse

url = 'https://login.paynearme-sandbox.com/oauth/token'
data = urllib.parse.urlencode({
    'grant_type': 'client_credentials',
    'client_id': 'YOUR_CLIENT_ID',
    'client_secret': 'YOUR_CLIENT_SECRET',
    'audience': 'YOUR_TOKEN_AUDIENCE',
}).encode('utf-8')

req = urllib.request.Request(
    url,
    data=data,
    headers={'Content-Type': 'application/x-www-form-urlencoded'},
    method='POST',
)

with urllib.request.urlopen(req) as resp:
    print(resp.read().decode('utf-8'))

Authenticating API Requests

After you receive an access_token, send API requests with the Bearer scheme in the Authorization header. When you use OAuth for a request, you do not need to include an HMAC signature in the request body.

The following example creates an order in sandbox using an access token and the required parameters for the /create_order endpoint:

curl --request POST \
     --url https://api.paynearme-sandbox.com/json-api/create_order \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
     --data '{
  "site_identifier": "S2155373459",
  "timestamp": "1636142061",
  "version": "3.0",
  "order_amount": "500",
  "order_currency": "USD",
  "site_customer_identifier": "11223344",
  "order_type": "any",
  "order_is_standing": "true"
}'

Required body fields such as version, site_identifier, and timestamp still apply for version 3.0+ API calls. Omit the signature parameter when authenticating with OAuth.

Token Management

PayNearMe supports on-demand token requests. However, requesting a new token for every API call can exhaust shared token capacity and trigger rate limits. All clients must cache access tokens and refresh only when needed.

❗️

Caching Requirements

Do not request a new access token for every API call. Failure to cache and refresh responsibly can result in rate limiting, increased latency, and enforcement of issuance quotas.

Caching Tokens

After you receive a token, store it in a secure cache until it expires. Acceptable options include:

  • In-memory cache (single process)
  • Redis or another shared cache (multiple instances)
  • Secure server-side storage

Do not call the token endpoint before every API request.

Validating Tokens

Before making an API call:

  1. Check whether the cached token is still valid.
  2. Request a new token only if the current token is near expiration (within 5-10 minutes of expiration) or has already expired.

Refreshing Tokens

Renew tokens before they expire with proactive refresh, and use single-flight refresh so concurrent workers do not all call /oauth/token at once.

Proactive Refresh

When you receive a token:

  1. Store the expires_in from the response.
  2. Record the issued_at time (i.e., Unix time when you received the token).
  3. Compute the expiration time: expiration_time = issued_at + expires_in.

As a best practice, we recommend applying a clock-skew leeway buffer (leeway_buffer) of 60–120 seconds. Tokens should be refreshed when:

current_time ≥ expiration_time - leeway_buffer

For high-traffic systems, you can refresh at 80–90% of the token lifetime in a background job so that live request paths are not blocked upon token refresh.

Single-Flight Refresh

Ensure only one refresh runs at a time per credential set across threads or processes. Concurrent /oauth/token calls from many workers can cause token storms and rate limiting.

Error Handling

Errors on API calls and on the token endpoint require different responses. Refresh and retry once when an API call returns 401 responses. Use exponential backoff and capped retries when /oauth/token returns 429 or 5xx responses.

401 Responses On API Calls

If an API request returns 401 Unauthorized:

  1. Refresh the access token once.
  2. Retry the original request exactly once with the new token.
  3. Do not loop indefinitely.

If the retry fails, log correlation identifiers and surface a clear error to operators.

429 Responses On the Token Endpoint

If /oauth/token returns 429 Too Many Requests:

  • Honor the Retry-After header when present.
  • Use exponential backoff with jitter.
  • Do not retry aggressively.

5xx Responses On the Token Endpoint

For server errors, use exponential backoff with a capped number of retries and log telemetry for monitoring.

Incorrect vs Correct Patterns

Incorrect - New token on every API call (high risk):

def call_api():
    token = request_new_token()  # Do not do this on every call
    return requests.post(api_url, headers={'Authorization': f'Bearer {token}'}, json=payload)

Correct - Cache and refresh only when needed:

cached_token = None
token_exp = 0

def call_api():
    global cached_token, token_exp
    if not cached_token or time.time() >= token_exp - 300:
        cached_token, token_exp = request_new_token_with_expiry()
    return requests.post(
        api_url,
        headers={'Authorization': f'Bearer {cached_token}'},
        json=payload,
    )

Parent and Child Sites

OAuth client credentials can be configured at different levels:

  • Parent site: Credentials at the parent level can be used by the parent and all child sites.
  • Child site: Credentials at the child level apply only to that child, not to siblings or the parent.

A child site may use its parent’s token or its own token, depending on where credentials are configured.

Client Responsibilities

Your integration must:

  • Cache access tokens after the first successful token request
  • Refresh only when the token is near expiration or expired
  • Implement exponential backoff for token endpoint errors
  • Retry an API call once after refreshing on a 401 response
  • Prevent parallel token storms (single-flight refresh)

Failure to follow these patterns may result in rate limiting, service instability, higher latency, and enforcement of issuance quotas.

❗️

PayNearMe Safeguards

PayNearMe may apply additional controls, including:

  • Fine-grained machine-to-machine token quotas per Client ID
  • Traffic monitoring and alerting for abnormal token request volume

Clients that exceed reasonable issuance thresholds may be investigated or rate limited.

Frequently Asked Questions

Can I use signature authentication and OAuth at the same time?

Yes. If the request does not include a Bearer token in the Authorization header, PayNearMe uses HMAC signature authentication. If the request does include Authorization: Bearer {access_token}, PayNearMe uses OAuth for that request.

When would I still use signatures?

If your integration validates callbacks with HMAC signatures, you still need API keys and signature verification for those inbound messages. Outbound API calls can use OAuth while callbacks continue to use API keys.

Where do I find my site identifier?

Use the same Site ID (or Key Identifier) as for signature-based calls. See Authentication for locating your site identifier in the Business Portal.

Troubleshooting

401 Unauthorized from the Token Endpoint
  • Confirm Client ID, Client Secret, and Audience match the values shown when credentials were generated.
  • Verify you are calling the correct environment (sandbox vs production token URL).
  • If credentials were deleted, generate a new set. Deleted secrets cannot be restored.
401 Unauthorized from an API Call
  • Confirm the Authorization header is Bearer {access_token} with no typos or extra whitespace.
  • Check whether the access token has expired. If so, refresh and retry once.
  • Verify the Audience used to obtain the token matches your site configuration.
  • Ensure the sandbox token is not sent to production API hosts (or vice versa).
Rate Limiting or Token Exhaustion
  • Audit your code for a new token request on every API call then implement caching and proactive refresh instead.
  • Ensure only one refresh runs at a time across workers.
  • On 429 responses, respect Retry-After and back off.
OAuth Option Not Visible in the Business Portal

OAuth must be enabled for your site by PayNearMe. Contact your TAM or Implementation team if the API Client Credentials page is missing or the Provision Site action is unavailable.