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

Firmly Card UCP Handler

The Firmly Card handler exposes normal card payment through UCP by accepting an encrypted card payload and completing the order through Firmly Vault.

  • Handler namespace: ai.firmly.card
  • Handler id: firmly_card

​​ Contract

  • Discovery stays on the merchant domain: https://<merchant-domain>/.well-known/ucp
  • UCP callers fetch the public key from a UCP route, not Vault directly
  • Vault remains the key source of truth and the only service that decrypts card data
  • Public request fields follow UCP schemas; Firmly / Vault internal names stay internal
  • The JWE plaintext carries only card fields; the checkout it applies to is resolved from the UCP request, not from a field inside the payload
  • Merchant binding comes from the UCP route domain and checkout session, not from a duplicated payload field

​​ UCP schema alignment

The public handler uses UCP names, not Firmly internal names:

Layer Shape
Payment handler namespace ai.firmly.card
Payment handler id firmly_card
Payment instrument UCP card payment instrument
Payment credential wrapper UCP encrypted credential: credential.type = "encrypted", credential.encrypted_data = "<compact-jwe>"
JWE plaintext Simplified Firmly card payload using UCP-style field names: number, expiry_month, expiry_year, name, cvc

Internal Vault field names such as encrypted_card, billing_info, verification_value, month, or year are not exposed in the UCP-facing contract. They are only used after the UCP service maps the request to Vault.

​​ Discovery snippet

This handler is advertised only when the destination, merchant, UCP mode, Vault card path, and merchant PSP path are all enabled.


{
"payment_handlers": {
"ai.firmly.card": [
{
"id": "firmly_card",
"version": "2026-04-08",
"spec": "https://api.firmly.work/api/2026-04-08/ucp/payment-handlers/ai.firmly.card",
"schema": "https://api.firmly.work/api/2026-04-08/ucp/payment-handlers/ai.firmly.card/schema.json",
"available_instruments": [
{
"type": "card",
"constraints": {
"brands": ["visa", "mastercard", "amex", "discover"]
}
}
],
"config": {
"environment": "sandbox",
"provider_id": "ai.firmly",
"public_key_url": "https://api.firmly.work/api/2026-04-08/ucp/rest/domain/<merchant-domain>/payment-handlers/ai.firmly.card/key",
"encryption_algorithm": "RSA-OAEP-256"
}
}
]
}
}

​​ Handler schema

The schema URL in the handler declaration tells the caller how to construct the handler-specific config and credential payload:


https://api.firmly.work/api/2026-04-08/ucp/payment-handlers/ai.firmly.card/schema.json

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.firmly.work/api/2026-04-08/ucp/payment-handlers/ai.firmly.card/schema.json",
"title": "Firmly Card UCP Payment Handler",
"description": "Schema for submitting a Firmly-encrypted card credential through UCP complete checkout.",
"type": "object",
"additionalProperties": false,
"required": ["payment"],
"properties": {
"payment": {
"type": "object",
"additionalProperties": false,
"required": ["instruments"],
"properties": {
"instruments": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/paymentInstrument" }
}
}
}
},
"$defs": {
"paymentInstrument": {
"type": "object",
"additionalProperties": true,
"required": ["id", "handler_id", "type", "credential"],
"properties": {
"id": { "type": "string" },
"handler_id": { "const": "firmly_card" },
"type": { "const": "card" },
"selected": { "type": "boolean" },
"billing_address": { "$ref": "https://ucp.dev/2026-04-08/schemas/shopping/types/postal_address.json" },
"credential": { "$ref": "#/$defs/encryptedCredential" }
}
},
"encryptedCredential": {
"type": "object",
"additionalProperties": false,
"required": ["type", "encrypted_data"],
"properties": {
"type": { "const": "encrypted" },
"encrypted_data": { "type": "string" }
}
},
"encryptedCardPlaintext": {
"type": "object",
"additionalProperties": false,
"required": ["number", "expiry_month", "expiry_year", "name", "cvc"],
"properties": {
"number": { "type": "string", "minLength": 12, "maxLength": 19 },
"expiry_month": { "type": "integer", "minimum": 1, "maximum": 12 },
"expiry_year": { "type": "integer", "minimum": 2000 },
"name": { "type": "string", "minLength": 1 },
"cvc": { "type": "string", "minLength": 3, "maxLength": 4 }
}
},
"handlerConfig": {
"type": "object",
"additionalProperties": false,
"required": ["environment", "provider_id", "public_key_url", "encryption_algorithm"],
"properties": {
"environment": { "enum": ["sandbox", "production"] },
"provider_id": { "const": "ai.firmly" },
"public_key_url": { "type": "string", "format": "uri" },
"encryption_algorithm": { "const": "RSA-OAEP-256" }
}
}
}
}

This illustration abbreviates the live schema for readability. Two $defs are worth calling out: encryptedCardPlaintext is the JWE plaintext the caller encrypts (number, expiry_month, expiry_year, name, cvc — nothing more), and handlerConfig mirrors the config block from the discovery snippet above. The caller encrypts an encryptedCardPlaintext object and sends the compact JWE as credential.encrypted_data on the card instrument.

​​ Public key route


GET /api/:ucpVersion/ucp/rest/domain/:domain/payment-handlers/ai.firmly.card/key

The UCP service validates merchant / destination eligibility, fetches the active public key from Vault internally, and returns the JWK. The key is bound to the merchant domain and checkout session.

​​ Why this matters

Today, UCP’s primary payment path on Google’s surfaces is Google Pay. The Firmly Card handler adds a card-payment path through Firmly’s existing Vault infrastructure — opening UCP to flows that need card payment without Google Pay.