Procare Pay Integration Service

Complete API Documentation for Payment Processing

Get SAD Public Key

GET /v1/rest/keys/sad

Description: Retrieves the public encryption key used for encrypting Sensitive Authentication Data (SAD) - specifically CVV/CVV2 security codes on credit cards.

What is this endpoint used for?

This endpoint provides the encryption key specifically for protecting CVV security codes:

  • CVV Codes: Encrypt the 3-digit security code on the back of Visa, Mastercard, and Discover cards
  • CVV2/CVC2: Encrypt the 4-digit security code on the front of American Express cards
  • Card-Not-Present Transactions: Essential for online and phone orders where you need to verify the customer has the physical card
  • PCI Compliance: Ensures CVV codes are never stored or transmitted in plain text
Critical Security Note:
  • This key is ONLY for encrypting CVV/CVV2 security codes
  • Do NOT use this key to encrypt credit card numbers (use the PAN key from /v1/rest/keys/pan)
  • CVV codes must NEVER be stored - even encrypted - after transaction authorization
  • Only use CVV codes for immediate transaction processing

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

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

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

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

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

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

    // Example: Encrypt a CVV code
    public string EncryptCvv(string cvv, PublicKey sadKey)
    {
        // Validate CVV format
        if (string.IsNullOrEmpty(cvv) || (cvv.Length != 3 && cvv.Length != 4))
        {
            throw new ArgumentException("CVV must be 3 or 4 digits");
        }

        // 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 System.Security.Cryptography
        // return RsaEncrypt(cvv, sadKey.Key);

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

// Example usage:
var client = new SadKeyClient();
PublicKey sadKey = await client.GetSadKeyAsync();

Console.WriteLine($"SAD Key ID: {sadKey.KeyId}");

// Encrypt a CVV code (remember: never store this!)
string encryptedCvv = client.EncryptCvv("123", sadKey);

// Use immediately in transaction, then discard
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 SadKeyClient
    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 GetSadKeyAsync() As Task(Of PublicKey)
        Try
            ' Make the GET request
            Dim response As HttpResponseMessage = Await _httpClient.GetAsync("/v1/rest/keys/sad")

            ' Ensure the request was successful
            response.EnsureSuccessStatusCode()

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

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

    ' Example: Encrypt a CVV code
    Public Function EncryptCvv(cvv As String, sadKey As PublicKey) As String
        ' Validate CVV format
        If String.IsNullOrEmpty(cvv) OrElse (cvv.Length <> 3 AndAlso cvv.Length <> 4) Then
            Throw New ArgumentException("CVV must be 3 or 4 digits")
        End If

        ' 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 SadKeyClient()
Dim sadKey As PublicKey = Await client.GetSadKeyAsync()

Console.WriteLine($"SAD Key ID: {sadKey.KeyId}")

' Encrypt CVV (never store this - use immediately!)
Dim encryptedCvv As String = client.EncryptCvv("123", sadKey)
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 SadKeyClient {
    private final HttpClient httpClient;
    private final String baseUrl;
    private final Gson gson;

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

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

        // 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 CVV code
    public String encryptCvv(String cvv, PublicKey sadKey) {
        // Validate CVV format
        if (cvv == null || (cvv.length() != 3 && cvv.length() != 4)) {
            throw new IllegalArgumentException("CVV must be 3 or 4 digits");
        }

        // 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 {
            SadKeyClient client = new SadKeyClient();
            PublicKey sadKey = client.getSadKey();

            System.out.println("SAD Key ID: " + sadKey.getKeyId());

            // Encrypt CVV (NEVER store - use immediately!)
            String encryptedCvv = client.encryptCvv("123", sadKey);
            System.out.println("Encrypted CVV ready for immediate transmission");
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
require 'net/http'
require 'uri'
require 'json'
require 'openssl'
require 'base64'

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

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

    # 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 CVV code
  def encrypt_cvv(cvv, sad_key)
    # Validate CVV format
    raise ArgumentError, 'CVV must be 3 or 4 digits' unless cvv&.match?(/^\d{3,4}$/)

    # Import the public key
    public_key_pem = sad_key['key']
    public_key = OpenSSL::PKey::RSA.new(public_key_pem)

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

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

# Example usage:
client = SadKeyClient.new
sad_key = client.get_sad_key

puts "SAD Key ID: #{sad_key['keyId']}"

# Encrypt CVV (NEVER store - use immediately in transaction!)
encrypted_cvv = client.encrypt_cvv('123', sad_key)
puts "Encrypted CVV ready for immediate use"

# IMPORTANT: Clear CVV from memory immediately after encryption
cvv = nil
encrypted_cvv = nil  # After sending in transaction

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": "z9y8x7w6-v5u4-3210-zyxw-vu9876543210",
  "key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzyxwvutsrqponmlkjihg\nfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfed\ncbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZ\nYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWV\nUTSRQPONMLKJIHGFEDCBA\n-----END PUBLIC KEY-----",
  "exponent": "AQAB",
  "modulus": "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA9876543210zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA"
}

HTTP Status Codes

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

Understanding CVV/CVV2 Security Codes

What is a CVV Code?

Card Type Code Name Digits Location
Visa CVV2 3 Back of card, signature panel
Mastercard CVC2 3 Back of card, signature panel
Discover CID 3 Back of card, signature panel
American Express CID 4 Front of card, above card number

Why CVV Codes Matter

  • Cardholder Verification: Proves the customer has the physical card (not just the number)
  • Fraud Prevention: Not stored in merchant databases, so data breaches don't expose CVV codes
  • PCI Requirement: CVV codes must never be stored after authorization - not even encrypted
  • Lower Fees: Providing CVV can reduce transaction fees by lowering fraud risk

When to Use the SAD Key

✅ DO Use SAD Key For:

  • Card-Not-Present Transactions: Online purchases, phone orders, mail orders
  • E-commerce Checkout: When customer enters CVV during payment
  • One-Time Payments: Immediate transaction processing
  • First Transaction: Initial verification when saving a payment method

❌ DO NOT Use SAD Key For:

  • Recurring Payments: CVV is not used for saved payment method transactions
  • ACH Transactions: Bank accounts don't have CVV codes
  • Storage: Never encrypt and store CVV codes - use immediately then discard
  • Card-Present Transactions: Physical terminal transactions don't require CVV

Complete Payment Workflow

Step-by-Step Guide

// Step 1: Fetch both PAN and SAD keys
var panKey = await GetPanKeyAsync();
var sadKey = await GetSadKeyAsync();

// Step 2: Collect payment information from customer
string cardNumber = "4111111111111111";  // Collect from secure form
string cvv = "123";                       // Collect from secure form
string expiry = "1225";                   // Collect from secure form

// Step 3: Validate input
if (!IsValidCardNumber(cardNumber)) { /* handle error */ }
if (!IsValidCvv(cvv)) { /* handle error */ }

// Step 4: Encrypt sensitive data
string encryptedCard = EncryptWithPanKey(cardNumber, panKey);
string encryptedCvv = EncryptWithSadKey(cvv, sadKey);

// Step 5: Clear plain text from memory
cardNumber = null;
cvv = null;

// Step 6: Send transaction request
var request = new TransactionRequest
{
    EncryptedAccountNumber = encryptedCard,
    EncryptedCvv2 = encryptedCvv,
    CardExpiry = expiry,
    Amount = new Amount { Dollars = 100.00m },
    AccountHolderName = "John Doe"
};

var result = await ProcessTransactionAsync(merchantId, request);

// Step 7: Clear encrypted CVV from memory (don't store it!)
encryptedCvv = null;

PCI Compliance Rules for CVV

Critical PCI DSS Requirements:
  • PCI DSS 3.2.3: Never store CVV codes after authorization - not even encrypted
  • Immediate Use Only: CVV should be encrypted, sent in transaction, then immediately discarded
  • No Logging: Never log CVV codes in application logs, debug output, or error messages
  • Memory Clearing: Overwrite CVV data in memory immediately after use
  • No Database Storage: Never insert CVV into any database table, even temporarily

Correct CVV Handling Timeline

  1. 0:00 - Customer enters CVV in payment form
  2. 0:01 - Application receives CVV (in memory only)
  3. 0:02 - Fetch SAD key from API
  4. 0:03 - Encrypt CVV with SAD key
  5. 0:04 - Clear plain text CVV from memory
  6. 0:05 - Send encrypted CVV in transaction request
  7. 0:06 - Receive transaction response
  8. 0:07 - Clear encrypted CVV from memory
  9. 0:08 - Display transaction result to customer

Total CVV lifetime: 8 seconds (in memory only, never persisted)

Common Mistakes to Avoid

Mistake Why It's Wrong Correct Approach
Storing encrypted CVV in database Violates PCI DSS 3.2.3 Use CVV immediately, never store
Using PAN key to encrypt CVV Wrong key, decryption will fail Use SAD key for CVV, PAN key for card numbers
Logging CVV in error messages Security breach, PCI violation Never log sensitive data
Caching CVV for "retry" scenarios PCI violation, security risk Ask customer to re-enter CVV if retry needed
Sending CVV with saved payment method Not required, creates security risk Only send CVV for initial verification

Testing

Test CVV Codes (Sandbox Environment)

  • Any 3-digit value: Will work in sandbox for testing (e.g., "123", "999")
  • Amex (4 digits): Use "1234" or "9999" for testing
  • Production: Always use the actual CVV from the customer's card

Troubleshooting

"Invalid CVV" Error in Transaction

  • Verify you encrypted with the SAD key (not the PAN key)
  • Check CVV is 3 digits (or 4 for Amex)
  • Ensure no spaces or special characters in CVV before encryption
  • Confirm CVV was entered correctly by customer

"CVV Required" Error

  • Some merchants require CVV for all card-not-present transactions
  • Check merchant configuration settings
  • Ensure encryptedCvv2 field is included in request

Encryption Fails

  • Verify CVV is a string of digits (not integer)
  • Check RSA padding is PKCS#1 v1.5 (not OAEP)
  • Ensure public key is correctly imported from PEM format
  • Try fetching a fresh SAD key

Related Endpoints

  • GET /v1/rest/keys - Get both PAN and SAD keys in one request
  • GET /v1/rest/keys/pan - Get the PAN key for encrypting card/account numbers

Additional Notes

  • SAD stands for "Sensitive Authentication Data" - the payment industry term for CVV/CVV2/CVC2 security codes.
  • The SAD key is separate from the PAN key to provide defense in depth - compromising one key doesn't expose both types of data.
  • This endpoint returns the same SAD key as the sadKey field in the GET /v1/rest/keys response.
  • CVV codes are sometimes called CVC, CVV2, CVC2, or CID depending on the card brand, but they all serve the same purpose.
  • For recurring billing or subscription payments using saved payment methods, CVV is typically not required after the initial setup.
  • Some card-present (physical terminal) transactions may capture CVV, but it's primarily designed for card-not-present scenarios.
  • The requirement to never store CVV applies even to encrypted values - this is a strict PCI DSS rule to protect cardholders.