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

UCP Security

This page covers how UCP requests are authenticated, verified, and protected.

​​ Request authentication

UCP requests carry a set of standard headers. Firmly supports two caller-authentication schemes — RFC 9421 HTTP message signatures (agent signing) and a destination API key — plus the headers UCP uses for idempotency and tracing:

Header Purpose
Signature RFC 9421 HTTP message signature (ES256) proving the request’s authenticity
Signature-Input RFC 9421 signature metadata — covered components, keyid, created, and algorithm
Content-Digest RFC 9530 SHA-256 digest of the request body (present on requests that carry a body)
UCP-Agent Agent profile URL identifying the caller (e.g., profile="https://<agent-host>/ucp-agent")
X-API-Key Destination API key (an alternative to agent signing)
Idempotency-Key Prevents duplicate processing of retried mutations (see Idempotency)
Request-Id Unique identifier for request tracing

Firmly evaluates the API key first: if an X-API-Key is present it is validated and, on success, the request proceeds without signature verification. Otherwise the request is handled on the agent-signing path below.

​​ Agent signature verification (RFC 9421)

When a request carries Signature and Signature-Input, Firmly verifies it per RFC 9421:

  1. Parse UCP-Agent for the caller’s profile URL. A signature with no UCP-Agent header is rejected with 400 missing_agent_header — the profile URL is the only way to locate the signing keys.
  2. Fetch the agent profile document from that HTTPS URL and read its signing_keys (a JWKS). Profiles are cached at the edge (~1 hour), fetched with a 3-second timeout and a 64 KB size cap. A profile that cannot be fetched, is malformed, or is missing signing_keys fails with 502 agent_profile_fetch_failed.
  3. Verify the ES256 (ECDSA P-256 / SHA-256) signature. The keyid in Signature-Input must match a JWK kid in the profile.

Covered components:

Component When required
@method, @authority, @path Always
@query When the request URL has a query string
ucp-agent When the UCP-Agent header is present
idempotency-key On state-changing methods (POST/PUT/PATCH/DELETE) that send the header
content-digest When the request carries a body — verified against the SHA-256 of the body

A signature that fails any check is rejected with 401 agent_signature_invalid.

​​ Destination API-key authentication

Destinations that integrate over an API key present it in X-API-Key (keys are prefixed fucp_live). Firmly resolves the merchant-and-destination configuration, hashes the presented key (SHA-256), and matches it against that destination’s active keys:

Outcome Response
A key is required for this merchant/destination but none was presented 401 api_key_required
Key is malformed, unknown, inactive, or not valid for this merchant 401 api_key_invalid
Key is configured for a different UCP version than the requested route 401 api_key_invalid

A merchant/destination can be configured to require an API key as its public default; in that case an unsigned, keyless request is rejected with 401 api_key_required.

​​ Response signing

Firmly signs its own successful (2xx) responses with RFC 9421, setting Signature, Signature-Input, and Content-Digest. The signature uses an ES256 (ECDSA P-256) key whose public JWK is published in the discovery manifest’s signing_keys array (key id ucp-firmly-key-1), so callers can verify Firmly’s responses. Discovery (/.well-known/ucp) responses and streaming (text/event-stream) responses are not signed.

​​ Domain validation

Before building a manifest or translating a call, Firmly resolves and validates the merchant domain:

Step Description
1. Resolve domain Discovery reads the merchant domain from the x-firmly-host header (or the domain query parameter); REST and MCP take it from the URL path (/domain/{domain})
2. Validate merchant Load the shop configuration; an unknown merchant fails with 404 merchant_not_found
3. Check UCP status A merchant with UCP disabled (ucp_disabled) fails with 404 ucp_disabled

​​ Abuse prevention

Measure How it works
Agent signatures RFC 9421 signatures prove request authenticity and integrity, with the body covered by content-digest
Idempotency Duplicate mutations with the same Idempotency-Key return the cached response; a reused key with a different body returns 409 (see Idempotency)
Replay dedup Handled at the idempotency layer rather than the signature layer — the verifier does not enforce a created freshness window beyond rejecting timestamps more than 24 hours in the future
Audit logging Every request records the caller (agent profile or destination), request id, and outcome on the request trace

​​ Rate limits

Rate limiting is not currently enforced by the UCP service. Firmly-wide rate limits may apply as a platform policy; where enforced, thresholds are communicated during onboarding and exceeding them returns 429 Too Many Requests with a retry-after header.

​​ Transport security

  • HTTPS only (enforced at the edge)
  • All communication encrypted in transit
  • TLS 1.2+ required