Procare Pay Integration Service

Complete API Documentation for Payment Processing

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:

  1. Fetch keys: Call this endpoint to get current PAN and SAD public keys
  2. Import keys: Parse the key data and import into your encryption library (RSA)
  3. Encrypt sensitive data:
    • Use PAN key to encrypt credit card numbers or bank account numbers
    • Use SAD key to encrypt CVV/CVV2 codes
  4. Send encrypted data: Include encrypted values in transaction requests using fields like encryptedAccountNumber and encryptedCvv2
  5. Include key ID: Some APIs require you to send the keyId so 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 keyId field 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.