Skip to main content
GET
https://cc.firmly.work
/
api
/
v1
/
payment
/
key
Get Public Key
curl --request GET \
  --url https://cc.firmly.work/api/v1/payment/key \
  --header 'Authorization: Bearer <token>'
{
  "x-firmly-kid": "<string>",
  "kid": "<string>",
  "kty": "<string>",
  "n": "<string>",
  "e": "<string>",
  "use": "<string>"
}

Overview

This endpoint retrieves the current public key used for encrypting credit card data before sending it to Firmly’s payment endpoints. The key supports multiple formats to accommodate different encryption libraries and platforms.
The public key is rotated periodically for security. Always fetch the current key before encrypting payment data rather than caching it for extended periods.

Authentication

This endpoint requires no authentication and is publicly accessible.

Query Parameters

format
string
default:"JWK"
The format of the public key to returnSupported formats:
  • JWK - JSON Web Key format (default)
  • PEM - Privacy Enhanced Mail format
  • RSA - PEM using RSAPublicKey format

Response Headers

x-firmly-kid
string
The key identifier (kid) - present for all formats

Response Formats

JWK Format (Default)

Returns a JSON Web Key with the following properties:
kid
string
Key identifier/version
kty
string
Key type (always “RSA”)
n
string
RSA modulus component (Base64URL encoded)
e
string
RSA exponent component (Base64URL encoded)
use
string
Key usage (always “enc” for encryption)

PEM Format

Returns the public key in PEM format as plain text:
  • Content-Type: text/plain
  • Key ID available in x-firmly-kid header
  • Standard PEM header/footer with base64 encoded key

RSA Format

Returns the public key in RSA-specific PEM format:
  • Content-Type: text/plain
  • Key ID available in x-firmly-kid header
  • Uses RSA PUBLIC KEY header/footer

Code Examples

// Fetch public key in JWK format (default)
async function getPublicKey() {
  const response = await fetch('https://cc.firmly.work/api/v1/payment/key');
  if (!response.ok) {
    throw new Error(`Failed to fetch public key: ${response.statusText}`);
  }
  
  const publicKey = await response.json();
  console.log('Key ID:', publicKey.kid);
  console.log('Key Type:', publicKey.kty);
  
  return publicKey;
}

// Use with Web Crypto API
async function importKey(jwk) {
  return await crypto.subtle.importKey(
    'jwk',
    jwk,
    {
      name: 'RSA-OAEP',
      hash: 'SHA-256'
    },
    false,
    ['encrypt']
  );
}

Response Examples

JWK Format Response

{
  "kid": "a81b2d581f2a42c09143eb6fdb918fff",
  "kty": "RSA",
  "n": "yURqBPP1k_kwMp8AiHeZya7zgO9ZulKrNvFYcQK2eIvkbl7VlhxYt6bnJ0urrUrJbuM_bbRg3yiwXtAN_BsHWTm6JwWSjRx3PQMIm0Yb-HGj2YM6moJ9YFACqtZB2zjkE98Q_TOhfAnYuoSIPsY3k9U1iJmi6gpaZ7E01QFGoRlAwB55yMETl3UT7uodGLRPBz_JGhRuDCJ1dVEfzcojUxOt7FFbRIGDGQzMTvmskRID3N50z6UwJOFwmP6N17qIMYCbr3IQg0fU75HsL-lChpA8m-EnvK0hL4CcNnBVqupxzhsKq2SSLigNBFC6J4gs3mV7L7qu1Q8u_Cg5tGVZiQ",
  "e": "AQAB",
  "use": "enc"
}

PEM Format Response

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyURqBPP1k/kwMp8AiHeZ
ya7zgO9ZulKrNvFYcQK2eIvkbl7VlhxYt6bnJ0urrUrJbuM/bbRg3yiwXtAN/BsH
WTm6JwWSjRx3PQMIm0Yb+HGj2YM6moJ9YFACqtZB2zjkE98Q/TOhfAnYuoSIPsY3
k9U1iJmi6gpaZ7E01QFGoRlAwB55yMETl3UT7uodGLRPBz/JGhRuDCJ1dVEfzcoj
UxOt7FFbRIGDGQzMTvmskRID3N50z6UwJOFwmP6N17qIMYCbr3IQg0fU75HsL+lC
hpA8m+EnvK0hL4CcNnBVqupxzhsKq2SSLigNBFC6J4gs3mV7L7qu1Q8u/Cg5tGVZ
iQIDAQAB
-----END PUBLIC KEY-----

RSA Format Response

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAyURqBPP1k/kwMp8AiHeZya7zgO9ZulKrNvFYcQK2eIvkbl7VlhxY
t6bnJ0urrUrJbuM/bbRg3yiwXtAN/BsHWTm6JwWSjRx3PQMIm0Yb+HGj2YM6moJ9
YFACqtZB2zjkE98Q/TOhfAnYuoSIPsY3k9U1iJmi6gpaZ7E01QFGoRlAwB55yMET
l3UT7uodGLRPBz/JGhRuDCJ1dVEfzcojUxOt7FFbRIGDGQzMTvmskRID3N50z6Uw
JOFwmP6N17qIMYCbr3IQg0fU75HsL+lChpA8m+EnvK0hL4CcNnBVqupxzhsKq2SS
LigNBFC6J4gs3mV7L7qu1Q8u/Cg5tGVZiQIDAQAB
-----END RSA PUBLIC KEY-----

Usage with Encryption Libraries

JavaScript (Web Crypto API)

async function encryptWithJWK(data, jwk) {
  // Import the JWK
  const publicKey = await crypto.subtle.importKey(
    'jwk',
    jwk,
    {
      name: 'RSA-OAEP',
      hash: 'SHA-256'
    },
    false,
    ['encrypt']
  );
  
  // Encrypt the data
  const encrypted = await crypto.subtle.encrypt(
    { name: 'RSA-OAEP' },
    publicKey,
    new TextEncoder().encode(data)
  );
  
  // Convert to base64
  return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}

Python (cryptography)

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
import base64

def encrypt_with_pem(data, pem_key):
    # Load the PEM key
    public_key = serialization.load_pem_public_key(
        pem_key.encode('utf-8')
    )
    
    # Encrypt the data
    encrypted = public_key.encrypt(
        data.encode('utf-8'),
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    
    # Return base64 encoded
    return base64.b64encode(encrypted).decode('utf-8')

Node.js (node-jose)

const jose = require('node-jose');

async function encryptWithJWK(data, jwk) {
  // Create key from JWK
  const key = await jose.JWK.asKey(jwk);
  
  // Encrypt the data
  const encrypted = await jose.JWE.createEncrypt({
    format: 'compact',
    contentAlg: 'A256GCM'
  }, key)
    .update(JSON.stringify(data))
    .final();
  
  return encrypted;
}

Best Practices

  • Key Rotation: The public key may be cached for up to. 15 minutes. Upon invalid key error, fetch the key and retry one time. Key validation should be removed
  • Format Selection: Choose the format that works best with your encryption library
  • Error Handling: Always handle network errors when fetching the key
  • Key Validation: Verify the key ID matches between encryption and submission

Common Use Cases

  1. Credit Card Encryption: Primary use is for encrypting credit card data for payment endpoints
  2. Tokenization: Used with payment tokenization endpoints
  3. Secure Data Transmission: Any sensitive data sent to Firmly payment endpoints

Checkout Flow Integration

This endpoint is the first step in the secure payment flow:
1

Fetch Public Key

Call this endpoint to get the current encryption key
2

Encrypt Credit Card

Use the public key to encrypt card data according to your chosen format
3

Complete Order

Send encrypted card data to one of the complete-order endpoints

Complete Example Flow

// 1. Get the public key
const publicKeyResponse = await fetch('https://cc.firmly.work/api/v1/payment/key');
const publicKey = await publicKeyResponse.json();

// 2. Encrypt credit card data
const encryptedCard = await encryptCard({
  number: "4111111111111111",
  name: "John Smith",
  verification_value: "123",
  month: "12",
  year: "2025"
}, publicKey);

// 3. Complete the order
const orderResponse = await fetch('https://cc.firmly.work/api/v1/payment/domains/staging.luma.gift/complete-order', {
  method: 'POST',
  headers: {
    'x-firmly-authorization': authToken,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    encrypted_card: encryptedCard,
    billing_info: billingInfo
  })
});

const order = await orderResponse.json();