Procare Pay Integration Service

Complete API Documentation for Payment Processing

Get PAN Public Key

GET /v1/rest/keys/pan

Description: Retrieves the public encryption key used for encrypting Primary Account Numbers (PANs) - credit card numbers and bank account numbers.

What is this endpoint used for?

This endpoint provides the encryption key specifically for protecting account numbers:

  • Credit Card Numbers: Encrypt 13-19 digit card numbers (Visa, Mastercard, Amex, Discover, etc.)
  • Bank Account Numbers: Encrypt checking and savings account numbers for ACH transactions
  • PCI Compliance: Ensures sensitive cardholder data is encrypted before transmission
  • Lightweight Option: Use this endpoint if you only need to encrypt account numbers (not CVV codes)
Important: This key is ONLY for encrypting account numbers (credit cards or bank accounts). Do NOT use this key to encrypt CVV/CVV2 security codes - use the SAD key from /v1/rest/keys/sad for that purpose.

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 PanKeyClient
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://your-api-domain.com";

    public PanKeyClient()
    {
        _httpClient = new HttpClient();
        _httpClient.BaseAddress = new Uri(_baseUrl);
    }

    public async Task<PublicKey> GetPanKeyAsync()
    {
        try
        {
            // Make the GET request
            HttpResponseMessage response = await _httpClient.GetAsync("/v1/rest/keys/pan");

            // Ensure the request was successful
            response.EnsureSuccessStatusCode();

            // Read and deserialize the response
            string responseBody = await response.Content.ReadAsStringAsync();
            PublicKey panKey = JsonConvert.DeserializeObject<PublicKey>(responseBody);

            return panKey;
        }
        catch (HttpRequestException e)
        {
            Console.WriteLine($"Request error: {e.Message}");
            throw;
        }
    }

    // Example: Encrypt a credit card number
    public string EncryptCardNumber(string cardNumber, PublicKey panKey)
    {
        // Use RSA encryption library to encrypt with the public key
        // This is a placeholder - implement actual RSA encryption
        // with PKCS#1 v1.5 padding using your preferred crypto library

        // Example using BouncyCastle or System.Security.Cryptography
        // return RsaEncrypt(cardNumber, panKey.Key);

        throw new NotImplementedException("Implement RSA encryption here");
    }
}

// Example usage:
var client = new PanKeyClient();
PublicKey panKey = await client.GetPanKeyAsync();

Console.WriteLine($"PAN Key ID: {panKey.KeyId}");

// Encrypt a credit card number
string encryptedCard = client.EncryptCardNumber("4111111111111111", panKey);
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 PanKeyClient
    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 GetPanKeyAsync() As Task(Of PublicKey)
        Try
            ' Make the GET request
            Dim response As HttpResponseMessage = Await _httpClient.GetAsync("/v1/rest/keys/pan")

            ' Ensure the request was successful
            response.EnsureSuccessStatusCode()

            ' Read and deserialize the response
            Dim responseBody As String = Await response.Content.ReadAsStringAsync()
            Dim panKey As PublicKey = JsonConvert.DeserializeObject(Of PublicKey)(responseBody)

            Return panKey
        Catch ex As HttpRequestException
            Console.WriteLine($"Request error: {ex.Message}")
            Throw
        End Try
    End Function

    ' Example: Encrypt a bank account number
    Public Function EncryptAccountNumber(accountNumber As String, panKey As PublicKey) As String
        ' Use RSA encryption library to encrypt with the public key
        ' This is a placeholder - implement actual RSA encryption
        ' with PKCS#1 v1.5 padding

        Throw New NotImplementedException("Implement RSA encryption here")
    End Function
End Class

' Example usage:
Dim client As New PanKeyClient()
Dim panKey As PublicKey = Await client.GetPanKeyAsync()

Console.WriteLine($"PAN Key ID: {panKey.KeyId}")

' Encrypt a bank account number for ACH
Dim encryptedAccount As String = client.EncryptAccountNumber("9876543210", panKey)
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; }
}

public class PanKeyClient {
    private final HttpClient httpClient;
    private final String baseUrl;
    private final Gson gson;

    public PanKeyClient() {
        this.baseUrl = "https://your-api-domain.com";
        this.httpClient = HttpClient.newHttpClient();
        this.gson = new Gson();
    }

    public PublicKey getPanKey() throws Exception {
        // Construct the endpoint URL
        String endpoint = baseUrl + "/v1/rest/keys/pan";

        // 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(), PublicKey.class);
        } else {
            throw new Exception("Request failed with status: " + response.statusCode());
        }
    }

    // Example: Encrypt credit card number
    public String encryptCardNumber(String cardNumber, PublicKey panKey) {
        // Implement RSA encryption with PKCS#1 v1.5 padding
        // using javax.crypto or BouncyCastle library
        throw new UnsupportedOperationException("Implement RSA encryption here");
    }

    // Example usage
    public static void main(String[] args) {
        try {
            PanKeyClient client = new PanKeyClient();
            PublicKey panKey = client.getPanKey();

            System.out.println("PAN Key ID: " + panKey.getKeyId());

            // Encrypt credit card
            String encryptedCard = client.encryptCardNumber("4111111111111111", panKey);
            System.out.println("Encrypted card ready for transmission");
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
require 'net/http'
require 'uri'
require 'json'
require 'openssl'
require 'base64'

class PanKeyClient
  def initialize(base_url = 'https://your-api-domain.com')
    @base_url = base_url
  end

  def get_pan_key
    # Construct the endpoint URL
    uri = URI("#{@base_url}/v1/rest/keys/pan")

    # 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

  # Example: Encrypt credit card number
  def encrypt_card_number(card_number, pan_key)
    # Import the public key
    public_key_pem = pan_key['key']
    public_key = OpenSSL::PKey::RSA.new(public_key_pem)

    # Encrypt the card number using RSA with PKCS#1 v1.5 padding
    encrypted = public_key.public_encrypt(card_number, OpenSSL::PKey::RSA::PKCS1_PADDING)

    # Return base64 encoded encrypted data
    Base64.strict_encode64(encrypted)
  end
end

# Example usage:
client = PanKeyClient.new
pan_key = client.get_pan_key

puts "PAN Key ID: #{pan_key['keyId']}"

# Encrypt a credit card number
encrypted_card = client.encrypt_card_number('4111111111111111', pan_key)
puts "Encrypted card: #{encrypted_card[0..50]}..."

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

# Example: Encrypt credit card number
def encrypt_card_number(card_number, pan_key)
# Import the public key
public_key_pem = pan_key['key']
public_key = OpenSSL::PKey::RSA.new(public_key_pem)

# Encrypt the card number using RSA with PKCS#1 v1.5 padding
encrypted = public_key.public_encrypt(card_number, OpenSSL::PKey::RSA::PKCS1_PADDING)

# Return base64 encoded encrypted data
Base64.strict_encode64(encrypted)
end
end

# Example usage:
client = PanKeyClient.new
pan_key = client.get_pan_key

puts "PAN Key ID: #{pan_key['keyId']}"

# Encrypt a credit card number
encrypted_card = client.encrypt_card_number('4111111111111111', pan_key)
puts "Encrypted card: #{encrypted_card[0..50]}..."

Response 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 value 65537)
modulus string The RSA modulus (the public component of the RSA key pair)

Example Response

{
  "keyId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1234567890abcdefghij\nklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklm\nnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnop\nqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrst\nuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n-----END PUBLIC KEY-----",
  "exponent": "AQAB",
  "modulus": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
}

HTTP Status Codes

Status Code Description
200 Success - PAN public key retrieved successfully
500 Internal Server Error - Server encountered an error retrieving the key

What to Encrypt with the PAN Key

Credit Card Transactions

  • Card Number: The 13-19 digit credit card number (remove spaces and dashes)
  • Example: "4111111111111111" (Visa test card)
  • API Field: Send encrypted value in encryptedAccountNumber

ACH/Bank Account Transactions

  • Account Number: The checking or savings account number
  • Example: "9876543210"
  • API Field: Send encrypted value in encryptedAccountNumber
  • Note: The routing number (ABA) is NOT encrypted - send it in plain text in abaRoutingNumber
Do NOT encrypt with PAN key:
  • CVV/CVV2 security codes (use SAD key from /v1/rest/keys/sad)
  • Card expiration dates (send in plain text)
  • Cardholder names (send in plain text)
  • Billing addresses (send in plain text)

Encryption Implementation Guide

Step 1: Fetch the PAN Key

PublicKey panKey = await GetPanKeyAsync();

Step 2: Parse the Public Key

The key field contains a PEM-formatted RSA public key. Most encryption libraries can import this directly:

// C# example using BouncyCastle
var keyBytes = Convert.FromBase64String(panKey.Key.Replace("-----BEGIN PUBLIC KEY-----", "")
                                                    .Replace("-----END PUBLIC KEY-----", "")
                                                    .Replace("\n", ""));
// Import into your RSA implementation

Step 3: Encrypt the Account Number

// Use RSA with PKCS#1 v1.5 padding
byte[] dataToEncrypt = Encoding.UTF8.GetBytes(cardNumber);
byte[] encryptedData = rsaPublicKey.Encrypt(dataToEncrypt, RSAEncryptionPadding.Pkcs1);
string encryptedBase64 = Convert.ToBase64String(encryptedData);

Step 4: Send in API Request

var request = new TransactionRequest
{
    EncryptedAccountNumber = encryptedBase64,  // Your encrypted card number
    CardExpiry = "1225",                        // Plain text
    Amount = new Amount { Dollars = 100.00m },
    AccountHolderName = "John Doe"              // Plain text
};

Common Use Cases

1. ACH-Only Integrations

If you only process bank account payments (no credit cards), you only need the PAN key:

// Fetch PAN key only
PublicKey panKey = await GetPanKeyAsync();

// Encrypt bank account number
string encryptedAccount = EncryptWithPanKey(bankAccountNumber, panKey);

// Create ACH transaction
var achRequest = new TransactionRequest
{
    EncryptedAccountNumber = encryptedAccount,
    AbaRoutingNumber = "123456789",  // Plain text
    BankAccountType = "Checking",
    Region = "IL",  // Required for ACH
    // ... other fields
};

2. Saved Payment Method Creation

// Fetch PAN key
PublicKey panKey = await GetPanKeyAsync();

// Encrypt card number
string encryptedCard = EncryptWithPanKey("4111111111111111", panKey);

// Create saved payment method
var paymentMethod = new SavedPaymentMethodRequest
{
    EncryptedAccountNumber = encryptedCard,
    CardExpiry = "1225",
    PaymentMethodType = "CreditCard",
    AccountHolderFirstName = "Jane",
    AccountHolderLastName = "Smith"
};

3. Mobile/Client-Side Encryption

For mobile apps or client-side JavaScript, fetch the PAN key and encrypt on the device:

// JavaScript example (using Web Crypto API or JSEncrypt)
async function encryptCardNumber(cardNumber) {
    // Fetch PAN key
    const response = await fetch('https://api-domain.com/v1/rest/keys/pan');
    const panKey = await response.json();

    // Import public key
    const publicKey = await importRSAKey(panKey.key);

    // Encrypt card number
    const encrypted = await crypto.subtle.encrypt(
        { name: "RSA-OAEP" },
        publicKey,
        new TextEncoder().encode(cardNumber)
    );

    return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}

Best Practices

  • Remove formatting: Strip spaces, dashes, and any other characters from card/account numbers before encrypting
  • Validate before encrypting: Use Luhn algorithm to validate card numbers before encryption to catch typos early
  • Cache appropriately: Cache the PAN key for a session or a few hours, but refresh periodically
  • Handle errors gracefully: If encryption fails, don't expose error details to end users
  • Test with test cards: Use test card numbers (4111111111111111) in development/sandbox
  • Clear memory: Overwrite plain text card numbers in memory immediately after encryption
  • Never log sensitive data: Don't log card numbers, even temporarily during debugging

Troubleshooting

"Invalid encrypted account number" Error

  • Verify you're using RSA with PKCS#1 v1.5 padding (not OAEP)
  • Check that the encrypted data is base64 encoded
  • Ensure no extra whitespace or characters in the encrypted string
  • Confirm you encrypted the account number (not CVV or other data)

Key Import Fails

  • Verify the key is in PEM format with proper BEGIN/END markers
  • Check for line breaks (\n) in the PEM-formatted key
  • Try using the modulus and exponent fields to construct the key manually

Decryption Fails on Server

  • Fetch a fresh PAN key - the key may have been rotated
  • Include the keyId in your request if the API supports it
  • Verify the padding scheme matches (PKCS#1 v1.5)

Related Endpoints

  • GET /v1/rest/keys - Get both PAN and SAD keys in one request
  • GET /v1/rest/keys/sad - Get the SAD key for encrypting CVV codes

Additional Notes

  • The PAN key is separate from the SAD key to provide defense in depth - if one key is compromised, the other remains secure.
  • This endpoint returns the same PAN key as the panKey field in the GET /v1/rest/keys response.
  • PAN stands for "Primary Account Number" - the official term for credit card and bank account numbers in payment industry standards.
  • Keys are 2048-bit RSA, which provides strong security while maintaining reasonable performance.
  • The keyId helps track which key version was used, useful during key rotation periods.
  • Some payment terminals and point-of-sale systems may fetch the PAN key to encrypt track data from card swipes.