Get Public Keys (PAN and SAD)
GET
/v1/rest/keys
Description: Retrieves both public encryption keys required for securely transmitting sensitive payment data - the PAN (Primary Account Number) key and the SAD (Sensitive Authentication Data) key.
What is this endpoint used for?
This endpoint provides the encryption keys you need to protect sensitive payment information before sending it to the API. Think of it like getting a secure lock box:
- PAN Key: Used to encrypt credit card numbers and bank account numbers
- SAD Key: Used to encrypt CVV/CVV2 codes (the security code on the back of credit cards)
- Data Security: Ensures sensitive payment data is encrypted before transmission over the internet
- PCI Compliance: Helps meet Payment Card Industry Data Security Standard requirements
Security Notice: These public keys change periodically for security purposes. Always fetch fresh keys before encrypting payment data. Do not hardcode or cache these keys for extended periods (recommended: fetch keys at application startup or before each encryption session).
Authentication
This endpoint requires bearer token authentication using the Authorization header.
Note: The bearer token must be a valid Cognito token obtained through the OAuth2 authentication flow.
Code Examples
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
public class PublicKey
{
[JsonProperty("keyId")]
public string KeyId { get; set; }
[JsonProperty("key")]
public string Key { get; set; }
[JsonProperty("exponent")]
public string Exponent { get; set; }
[JsonProperty("modulus")]
public string Modulus { get; set; }
}
public class PublicKeysResponse
{
[JsonProperty("panKey")]
public PublicKey PanKey { get; set; }
[JsonProperty("sadKey")]
public PublicKey SadKey { get; set; }
}
public class KeysClient
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl = "https://your-api-domain.com";
public KeysClient()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(_baseUrl);
}
public async Task<PublicKeysResponse> GetPublicKeysAsync()
{
try
{
// Make the GET request
HttpResponseMessage response = await _httpClient.GetAsync("/v1/rest/keys");
// Ensure the request was successful
response.EnsureSuccessStatusCode();
// Read and deserialize the response
string responseBody = await response.Content.ReadAsStringAsync();
PublicKeysResponse keys = JsonConvert.DeserializeObject<PublicKeysResponse>(responseBody);
return keys;
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request error: {e.Message}");
throw;
}
}
}
// Example usage:
var client = new KeysClient();
PublicKeysResponse keys = await client.GetPublicKeysAsync();
Console.WriteLine($"PAN Key ID: {keys.PanKey.KeyId}");
Console.WriteLine($"SAD Key ID: {keys.SadKey.KeyId}");
// Now use these keys to encrypt sensitive data before sending to the API
Imports System.Net.Http
Imports System.Threading.Tasks
Imports Newtonsoft.Json
Public Class PublicKey
<JsonProperty("keyId")>
Public Property KeyId As String
<JsonProperty("key")>
Public Property Key As String
<JsonProperty("exponent")>
Public Property Exponent As String
<JsonProperty("modulus")>
Public Property Modulus As String
End Class
Public Class PublicKeysResponse
<JsonProperty("panKey")>
Public Property PanKey As PublicKey
<JsonProperty("sadKey")>
Public Property SadKey As PublicKey
End Class
Public Class KeysClient
Private ReadOnly _httpClient As HttpClient
Private ReadOnly _baseUrl As String = "https://your-api-domain.com"
Public Sub New()
_httpClient = New HttpClient()
_httpClient.BaseAddress = New Uri(_baseUrl)
End Sub
Public Async Function GetPublicKeysAsync() As Task(Of PublicKeysResponse)
Try
' Make the GET request
Dim response As HttpResponseMessage = Await _httpClient.GetAsync("/v1/rest/keys")
' Ensure the request was successful
response.EnsureSuccessStatusCode()
' Read and deserialize the response
Dim responseBody As String = Await response.Content.ReadAsStringAsync()
Dim keys As PublicKeysResponse = JsonConvert.DeserializeObject(Of PublicKeysResponse)(responseBody)
Return keys
Catch ex As HttpRequestException
Console.WriteLine($"Request error: {ex.Message}")
Throw
End Try
End Function
End Class
' Example usage:
Dim client As New KeysClient()
Dim keys As PublicKeysResponse = Await client.GetPublicKeysAsync()
Console.WriteLine($"PAN Key ID: {keys.PanKey.KeyId}")
Console.WriteLine($"SAD Key ID: {keys.SadKey.KeyId}")
' Now use these keys to encrypt sensitive data before sending to the API
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
class PublicKey {
@SerializedName("keyId")
private String keyId;
@SerializedName("key")
private String key;
@SerializedName("exponent")
private String exponent;
@SerializedName("modulus")
private String modulus;
// Getters
public String getKeyId() { return keyId; }
public String getKey() { return key; }
public String getExponent() { return exponent; }
public String getModulus() { return modulus; }
}
class PublicKeysResponse {
@SerializedName("panKey")
private PublicKey panKey;
@SerializedName("sadKey")
private PublicKey sadKey;
// Getters
public PublicKey getPanKey() { return panKey; }
public PublicKey getSadKey() { return sadKey; }
}
public class KeysClient {
private final HttpClient httpClient;
private final String baseUrl;
private final Gson gson;
public KeysClient() {
this.baseUrl = "https://your-api-domain.com";
this.httpClient = HttpClient.newHttpClient();
this.gson = new Gson();
}
public PublicKeysResponse getPublicKeys() throws Exception {
// Construct the endpoint URL
String endpoint = baseUrl + "/v1/rest/keys";
// Build the HTTP request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Accept", "application/json")
.GET()
.build();
// Send the request and get response
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString()
);
// Check response status
if (response.statusCode() == 200) {
return gson.fromJson(response.body(), PublicKeysResponse.class);
} else {
throw new Exception("Request failed with status: " + response.statusCode());
}
}
// Example usage
public static void main(String[] args) {
try {
KeysClient client = new KeysClient();
PublicKeysResponse keys = client.getPublicKeys();
System.out.println("PAN Key ID: " + keys.getPanKey().getKeyId());
System.out.println("SAD Key ID: " + keys.getSadKey().getKeyId());
// Now use these keys to encrypt sensitive data
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}
}
require 'net/http'
require 'uri'
require 'json'
class KeysClient
def initialize(base_url = 'https://your-api-domain.com')
@base_url = base_url
end
def get_public_keys
# Construct the endpoint URL
uri = URI("#{@base_url}/v1/rest/keys")
# Create HTTP request
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.read_timeout = 30
request = Net::HTTP::Get.new(uri.path)
request['Accept'] = 'application/json'
# Send request and handle response
response = http.request(request)
case response.code.to_i
when 200
JSON.parse(response.body)
else
raise "Request failed with status: #{response.code} - #{response.message}"
end
rescue StandardError => e
puts "Request error: #{e.message}"
raise
end
end
# Example usage:
client = KeysClient.new
keys = client.get_public_keys
puts "PAN Key ID: #{keys['panKey']['keyId']}"
puts "SAD Key ID: #{keys['sadKey']['keyId']}"
# Now use these keys to encrypt sensitive data before sending to the API
puts "\nFull Response:"
puts JSON.pretty_generate(keys)
'net/http'
require 'uri'
require 'json'
class KeysClient
def initialize(base_url = 'https://your-api-domain.com')
@base_url = base_url
end
def get_public_keys
# Construct the endpoint URL
uri = URI("#{@base_url}/v1/rest/keys")
# Create HTTP request
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
http.read_timeout = 30
request = Net::HTTP::Get.new(uri.path)
request['Accept'] = 'application/json'
# Send request and handle response
response = http.request(request)
case response.code.to_i
when 200
JSON.parse(response.body)
else
raise "Request failed with status: #{response.code} - #{response.message}"
end
rescue StandardError => e
puts "Request error: #{e.message}"
raise
end
end
# Example usage:
client = KeysClient.new
keys = client.get_public_keys
puts "PAN Key ID: #{keys['panKey']['keyId']}"
puts "SAD Key ID: #{keys['sadKey']['keyId']}"
# Now use these keys to encrypt sensitive data before sending to the API
puts "\nFull Response:"
puts JSON.pretty_generate(keys)
Response Structure
The response contains two key objects: panKey and sadKey. Each key object has the following structure:
| Field | Type | Description |
|---|---|---|
| keyId | string | Unique identifier for this key version |
| key | string | The complete public key in PEM format (base64 encoded) |
| exponent | string | The RSA public exponent (typically "AQAB" for exponent value 65537) |
| modulus | string | The RSA modulus (the public component of the RSA key pair) |
Example Response
{
"panKey": {
"keyId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz",
"exponent": "AQAB",
"modulus": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
},
"sadKey": {
"keyId": "z9y8x7w6-v5u4-3210-zyxw-vu9876543210",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlk",
"exponent": "AQAB",
"modulus": "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA"
}
}
HTTP Status Codes
| Status Code | Description |
|---|---|
| 200 | Success - Both public keys retrieved successfully |
| 500 | Internal Server Error - Server encountered an error retrieving keys |
Understanding Public Key Encryption
For Non-Technical Users
Think of public key encryption like a mailbox with a slot:
- Public Key (this endpoint): Like the slot in the mailbox - anyone can drop mail in, but only the owner can open it
- Encrypting Data: When you encrypt a credit card number with the public key, it's like putting it in the mailbox - it's secure during transit
- Private Key (kept secret): Only the payment system has the key to open the mailbox and read the card number
- Two Keys: PAN for card/bank numbers, SAD for security codes - like having two different secure mailboxes
For Developers
Encryption Workflow:
- Fetch keys: Call this endpoint to get current PAN and SAD public keys
- Import keys: Parse the key data and import into your encryption library (RSA)
-
Encrypt sensitive data:
- Use PAN key to encrypt credit card numbers or bank account numbers
- Use SAD key to encrypt CVV/CVV2 codes
- Send encrypted data: Include encrypted values in transaction requests using fields like
encryptedAccountNumberandencryptedCvv2 - Include key ID: Some APIs require you to send the
keyIdso the server knows which key to use for decryption
Technical Note: The keys use RSA encryption with PKCS#1 v1.5 padding. The
key field contains the complete public key in PEM format. The exponent and modulus fields provide the raw RSA components if you need to construct the key manually.
When to Fetch Keys
| Scenario | Recommendation |
|---|---|
| Application Startup | Fetch keys when your application starts and cache them in memory |
| Before Payment Session | Fetch fresh keys at the beginning of a payment/checkout session |
| After Encryption Failure | If encryption fails with a key error, fetch new keys and retry |
| Scheduled Refresh | Refresh keys every 4-6 hours to ensure you have the latest version |
| Do NOT Cache Long-Term | Never store keys in databases or file systems for extended periods |
Common Use Cases
1. Processing One-Time Payments
// Fetch current keys
PublicKeysResponse keys = await GetPublicKeysAsync();
// Encrypt credit card number with PAN key
string encryptedCardNumber = EncryptWithRSA(
cardNumber,
keys.PanKey.Key
);
// Encrypt CVV with SAD key
string encryptedCvv = EncryptWithRSA(
cvv,
keys.SadKey.Key
);
// Send transaction request with encrypted data
var transaction = new TransactionRequest
{
EncryptedAccountNumber = encryptedCardNumber,
EncryptedCvv2 = encryptedCvv,
CardExpiry = "1225",
Amount = new Amount { Dollars = 150.00m },
AccountHolderName = "John Doe"
};
2. Creating Saved Payment Methods
// Fetch keys
PublicKeysResponse keys = await GetPublicKeysAsync();
// Encrypt bank account number for ACH
string encryptedAccountNumber = EncryptWithRSA(
bankAccountNumber,
keys.PanKey.Key
);
// Create saved payment method
var paymentMethod = new SavedPaymentMethodRequest
{
EncryptedAccountNumber = encryptedAccountNumber,
AbaRoutingNumber = "123456789",
PaymentMethodType = "Checking",
AccountHolderFirstName = "Jane",
AccountHolderLastName = "Smith"
};
3. Implementing Key Rotation
public class KeyManager
{
private PublicKeysResponse _cachedKeys;
private DateTime _keysFetchedAt;
private readonly TimeSpan _keyRefreshInterval = TimeSpan.FromHours(4);
public async Task<PublicKeysResponse> GetCurrentKeysAsync()
{
// Check if we need to refresh keys
if (_cachedKeys == null ||
DateTime.UtcNow - _keysFetchedAt > _keyRefreshInterval)
{
_cachedKeys = await FetchKeysFromApiAsync();
_keysFetchedAt = DateTime.UtcNow;
}
return _cachedKeys;
}
}
Security Best Practices
- Always use HTTPS: Even though you're getting public keys, use secure connections to prevent man-in-the-middle attacks
- Validate key format: Ensure received keys are valid RSA public keys before using them
- Never log sensitive data: Don't log credit card numbers or CVV codes, even before encryption
- Clear sensitive data: Overwrite credit card data in memory immediately after encryption
- Handle encryption errors: If encryption fails, don't fall back to sending unencrypted data
- Keep libraries updated: Use up-to-date encryption libraries to avoid security vulnerabilities
- Test in sandbox: Always test your encryption implementation in a sandbox environment first
Troubleshooting
Encryption Fails
- Verify you're using RSA encryption with PKCS#1 v1.5 padding
- Check that the public key is correctly imported/parsed
- Ensure input data is the correct format (string, not binary)
- Try fetching fresh keys in case of key rotation
Transaction Rejected with "Invalid Encrypted Data"
- Confirm you used the PAN key for account numbers (not the SAD key)
- Confirm you used the SAD key for CVV codes (not the PAN key)
- Check that the encrypted data is base64 encoded
- Verify no extra characters or padding were added to the encrypted string
500 Internal Server Error
- This is rare for this endpoint - retry after a brief delay
- If it persists, contact Procare Pay support
- Check system status page for any announced issues
Related Endpoints
- GET /v1/rest/keys/pan - Get only the PAN key (if you only need to encrypt account numbers)
- GET /v1/rest/keys/sad - Get only the SAD key (if you only need to encrypt CVV codes)
Additional Notes
- Public keys are rotated periodically for security. The system maintains backward compatibility for a grace period during rotation.
- The
keyIdfield helps the server identify which private key to use for decryption if multiple key versions are active. - These keys use 2048-bit RSA encryption, which is the industry standard for secure data transmission.
- If you only process credit card transactions, you technically only need the PAN and SAD keys. For ACH-only merchants, you only need the PAN key (since ACH doesn't have CVV codes).
- The API accepts encrypted data in base64 encoding. Ensure your encryption library outputs base64 or convert the binary encrypted data to base64 before sending.