Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

Get Public Key

GET https://cc.firmly.work/api/v1/payment/key

​​ 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.

​​ Authentication

This endpoint requires no authentication and is publicly accessible.

​​ Query Parameters

  • format (string, default JWK) — The format of the public key to return Supported 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 the 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 jwk = await response.json();
// jwk.kid is also returned in the x-firmly-kid response header.
return jwk;
}

import requests
def get_public_key_pem():
"""Fetch public key in PEM format"""
response = requests.get(
'https://cc.firmly.work/api/v1/payment/key',
params={'format': 'PEM'}
)
if response.status_code != 200:
raise Exception(f"Failed to fetch public key: {response.status_code}")
# Key ID is returned in the x-firmly-kid header
key_id = response.headers.get('x-firmly-kid')
public_key_pem = response.text
return public_key_pem, key_id

# Get JWK format (default)
curl https://cc.firmly.work/api/v1/payment/key
# Get PEM format
curl https://cc.firmly.work/api/v1/payment/key?format=PEM
# Get RSA format
curl https://cc.firmly.work/api/v1/payment/key?format=RSA
# Get PEM format and extract key ID from header
curl -i https://cc.firmly.work/api/v1/payment/key?format=PEM | grep x-firmly-kid

<?php
// Fetch public key in JWK format
function getPublicKey() {
$url = 'https://cc.firmly.work/api/v1/payment/key';
$response = file_get_contents($url);
if ($response === false) {
throw new Exception('Failed to fetch public key');
}
$publicKey = json_decode($response, true);
echo "Key ID: " . $publicKey['kid'] . "\n";
echo "Key Type: " . $publicKey['kty'] . "\n";
return $publicKey;
}
// Fetch PEM format with headers
function getPublicKeyPEM() {
$context = stream_context_create([
'http' => ['method' => 'GET']
]);
$url = 'https://cc.firmly.work/api/v1/payment/key?format=PEM';
$response = file_get_contents($url, false, $context);
// Extract key ID from headers
foreach ($http_response_header as $header) {
if (stripos($header, 'x-firmly-kid:') === 0) {
$keyId = trim(substr($header, 13));
break;
}
}
return ['pem' => $response, 'kid' => $keyId];
}
?>

​​ 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-----

​​ Encrypting the Card

The payment endpoints expect encrypted_card to be a JWE compact serialization string, not a raw RSA ciphertext. Encrypt the card object with:

  • Key-management algorithm (alg): RSA-OAEP-256
  • Content-encryption algorithm (enc): A256GCM
  • kid in the JWE protected header — use the key’s kid (also returned in the x-firmly-kid response header)

The plaintext is the card object:


{
"number": "4111111111111111",
"name": "John Smith",
"verification_value": "123",
"month": "08",
"year": "2026"
}

import { importJWK, CompactEncrypt } from 'jose';
// jwk is the JWK object returned by GET /api/v1/payment/key
async function encryptCard(card, jwk) {
const key = await importJWK(jwk, 'RSA-OAEP-256');
return new CompactEncrypt(new TextEncoder().encode(JSON.stringify(card)))
.setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM', kid: jwk.kid })
.encrypt(key);
}
// Returns a JWE compact string: header..encrypted_key.iv.ciphertext.tag

import json
from jwcrypto import jwk, jwe
# jwk_dict is the JWK returned by GET /api/v1/payment/key
def encrypt_card(card, jwk_dict):
key = jwk.JWK(**jwk_dict)
protected = {
"alg": "RSA-OAEP-256",
"enc": "A256GCM",
"kid": jwk_dict["kid"],
}
token = jwe.JWE(
json.dumps(card).encode("utf-8"),
recipient=key,
protected=protected,
)
return token.serialize(compact=True)

​​ 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:

​​ Fetch Public Key

Call this endpoint to get the current encryption key

​​ Encrypt Credit Card

Encrypt the card object as a JWE (RSA-OAEP-256 / A256GCM) with the fetched key.

​​ Complete Order

Send the JWE string as encrypted_card to Complete Order (or Place Order).

​​ Complete Example Flow


import { importJWK, CompactEncrypt } from 'jose';
// 1. Fetch the public key (JWK)
const jwk = await fetch('https://cc.firmly.work/api/v1/payment/key').then((r) => r.json());
// 2. Encrypt the card as a JWE (RSA-OAEP-256 / A256GCM)
const key = await importJWK(jwk, 'RSA-OAEP-256');
const encryptedCard = await new CompactEncrypt(
new TextEncoder().encode(JSON.stringify({
number: '4111111111111111',
name: 'John Smith',
verification_value: '123',
month: '08',
year: '2026'
}))
)
.setProtectedHeader({ alg: 'RSA-OAEP-256', enc: 'A256GCM', kid: jwk.kid })
.encrypt(key);
// 3. Complete the order
const order = await fetch(
'https://cc.firmly.work/api/v2/payment/domains/staging.luma.gift/complete-order',
{
method: 'POST',
headers: {
'x-firmly-authorization': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
encrypted_card: encryptedCard,
billing_info: billingInfo
})
}
).then((r) => r.json());

​​ Error Responses

This endpoint is public and returns the current key under normal operation. Failures are rare: a malformed format query parameter (client error) or a temporary inability to load the key (server error).

Code HTTP Trigger
InvalidInputQuery 400 The format query parameter is not one of JWK, PEM, RSA
StoreUnavailable 503 The payment host could not load the current key. Retry with backoff.