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.
Related
- UCP overview
- UCP implementation — service architecture, manifest
- Get Payment Public Key — Firmly Vault’s existing card encryption flow
- UCP roadmap