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, defaultJWK) — The format of the public key to return Supported formats:JWK— JSON Web Key format (default)PEM— Privacy Enhanced Mail formatRSA— 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-kidheader - 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-kidheader - 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 requestsdef 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 headerkey_id = response.headers.get('x-firmly-kid')public_key_pem = response.textreturn public_key_pem, key_id
# Get JWK format (default)curl https://cc.firmly.work/api/v1/payment/key# Get PEM formatcurl https://cc.firmly.work/api/v1/payment/key?format=PEM# Get RSA formatcurl https://cc.firmly.work/api/v1/payment/key?format=RSA# Get PEM format and extract key ID from headercurl -i https://cc.firmly.work/api/v1/payment/key?format=PEM | grep x-firmly-kid
<?php// Fetch public key in JWK formatfunction 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 headersfunction 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 headersforeach ($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/kwMp8AiHeZya7zgO9ZulKrNvFYcQK2eIvkbl7VlhxYt6bnJ0urrUrJbuM/bbRg3yiwXtAN/BsHWTm6JwWSjRx3PQMIm0Yb+HGj2YM6moJ9YFACqtZB2zjkE98Q/TOhfAnYuoSIPsY3k9U1iJmi6gpaZ7E01QFGoRlAwB55yMETl3UT7uodGLRPBz/JGhRuDCJ1dVEfzcojUxOt7FFbRIGDGQzMTvmskRID3N50z6UwJOFwmP6N17qIMYCbr3IQg0fU75HsL+lChpA8m+EnvK0hL4CcNnBVqupxzhsKq2SSLigNBFC6J4gs3mV7L7qu1Q8u/Cg5tGVZiQIDAQAB-----END PUBLIC KEY-----
RSA Format Response
-----BEGIN RSA PUBLIC KEY-----MIIBCgKCAQEAyURqBPP1k/kwMp8AiHeZya7zgO9ZulKrNvFYcQK2eIvkbl7VlhxYt6bnJ0urrUrJbuM/bbRg3yiwXtAN/BsHWTm6JwWSjRx3PQMIm0Yb+HGj2YM6moJ9YFACqtZB2zjkE98Q/TOhfAnYuoSIPsY3k9U1iJmi6gpaZ7E01QFGoRlAwB55yMETl3UT7uodGLRPBz/JGhRuDCJ1dVEfzcojUxOt7FFbRIGDGQzMTvmskRID3N50z6UwJOFwmP6N17qIMYCbr3IQg0fU75HsL+lChpA8m+EnvK0hL4CcNnBVqupxzhsKq2SSLigNBFC6J4gs3mV7L7qu1Q8u/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 kidin the JWE protected header — use the key’skid(also returned in thex-firmly-kidresponse 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/keyasync 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 jsonfrom jwcrypto import jwk, jwe# jwk_dict is the JWK returned by GET /api/v1/payment/keydef 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
- Credit Card Encryption: Primary use is for encrypting credit card data for payment endpoints
- Tokenization: Used with payment tokenization endpoints
- 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 orderconst 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());
Related Endpoints
- Complete Order — Complete checkout for an existing cart
- Place Order — Create a cart and place an order in one call
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. |