diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index 1924cb3ad..9490b8f05 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -105,4 +105,5 @@ worktree yaml zapatillas yml +jwks keyid diff --git a/docs/assets/partner/endorsed/Affirm.svg b/docs/assets/partner/endorsed/Affirm.svg index 1403f1884..801511c32 100644 --- a/docs/assets/partner/endorsed/Affirm.svg +++ b/docs/assets/partner/endorsed/Affirm.svg @@ -1,7 +1,7 @@ - diff --git a/docs/specification/cart.md b/docs/specification/cart.md index 590cdb9a5..80d51db97 100644 --- a/docs/specification/cart.md +++ b/docs/specification/cart.md @@ -211,6 +211,15 @@ consistent data structures when converting a cart to a checkout session. {{ schema_fields('context', 'checkout') }} +### Signals + +Environment data provided by the platform to support authorization +and abuse prevention. Signal values MUST NOT be buyer-asserted claims. See +[Signals](overview.md#signals) for details and privacy +requirements. + +{{ schema_fields('types/signals', 'checkout') }} + ### Total {{ schema_fields('types/total_resp', 'checkout') }} diff --git a/docs/specification/catalog/index.md b/docs/specification/catalog/index.md index 4c4e54c12..da03c8a26 100644 --- a/docs/specification/catalog/index.md +++ b/docs/specification/catalog/index.md @@ -71,6 +71,14 @@ explicit currency codes confirming the resolution. {{ schema_fields('types/context', 'catalog') }} +### Signals + +Environment data provided by the platform to support authorization +and abuse prevention. Signal values MUST NOT be buyer-asserted claims. See +[Signals](../overview.md#signals) for details and privacy requirements. + +{{ schema_fields('types/signals', 'catalog') }} + ### Product A catalog item representing a sellable item with one or more purchasable variants. diff --git a/docs/specification/checkout-a2a.md b/docs/specification/checkout-a2a.md index 7fe42a6d0..b6f7d6a27 100644 --- a/docs/specification/checkout-a2a.md +++ b/docs/specification/checkout-a2a.md @@ -224,8 +224,8 @@ When a user is ready to make a payment, `payment` must be submitted to the business agent to complete the checkout process. `payment` is a structured data type specified as part of UCP. When processing a payment to complete the checkout, `payment` must be submitted to the business agent as a `DataPart` with -attribute name `a2a.ucp.checkout.payment`. Any associated risk signals should be -sent with attribute name `a2a.ucp.checkout.risk_signals`. +attribute name `a2a.ucp.checkout.payment`. Any associated signals should be sent +with attribute name `a2a.ucp.checkout.signals`. Upon completion of the checkout process, the business agent must return the checkout object containing an `order` attribute with `id` and `permalink_url`. @@ -247,7 +247,10 @@ checkout object containing an `order` attribute with `id` and `permalink_url`. "a2a.ucp.checkout.payment": { ...paymentObject }, - "a2a.ucp.checkout.risk_signals":{...content} + "a2a.ucp.checkout.signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ..." + } } } ], diff --git a/docs/specification/checkout-rest.md b/docs/specification/checkout-rest.md index 8a3eb222c..5ffed0ef0 100644 --- a/docs/specification/checkout-rest.md +++ b/docs/specification/checkout-rest.md @@ -766,8 +766,9 @@ place to set these expectations via `messages`. } ] }, - "risk_signals": { - //... risk signal related data (device fingerprint / risk token) + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ..." } } ``` diff --git a/docs/specification/checkout.md b/docs/specification/checkout.md index ee286321c..8ca950e86 100644 --- a/docs/specification/checkout.md +++ b/docs/specification/checkout.md @@ -428,6 +428,18 @@ binding transaction data. {{ schema_fields('context', 'checkout') }} +### Signals + +Environment data provided by the platform to support authorization +and abuse prevention. Unlike `context` (buyer-asserted preferences) and `buyer` +(self-reported identity), signal values MUST NOT be buyer-asserted claims — +platforms provide signals based on direct observation or by relaying +independently verifiable third-party attestations. See +[Signals](overview.md#signals) for details and privacy +requirements. + +{{ schema_fields('types/signals', 'checkout') }} + ### Fulfillment Option {{ extension_schema_fields('fulfillment.json#/$defs/fulfillment_option', 'checkout') }} diff --git a/docs/specification/examples/encrypted-credential-handler.md b/docs/specification/examples/encrypted-credential-handler.md index e78b96161..97b175fb5 100644 --- a/docs/specification/examples/encrypted-credential-handler.md +++ b/docs/specification/examples/encrypted-credential-handler.md @@ -328,8 +328,9 @@ Content-Type: application/json } ] }, - "risk_signals": { - // ... the key value pair for potential risk signal data + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ..." } } ``` diff --git a/docs/specification/examples/platform-tokenizer-payment-handler.md b/docs/specification/examples/platform-tokenizer-payment-handler.md index f9b0f0a4f..62bbc41e6 100644 --- a/docs/specification/examples/platform-tokenizer-payment-handler.md +++ b/docs/specification/examples/platform-tokenizer-payment-handler.md @@ -396,8 +396,9 @@ Content-Type: application/json } ] }, - "risk_signals": { - // ... the key value pair for potential risk signal data + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ..." } } ``` diff --git a/docs/specification/overview.md b/docs/specification/overview.md index 089aeaba7..44c0ce59d 100644 --- a/docs/specification/overview.md +++ b/docs/specification/overview.md @@ -1192,26 +1192,6 @@ and cart context, then returns the resolved result. Platforms **MUST** treat the the [Payment Handler Guide](payment-handler-guide.md#resolving-available_instruments) for the full resolution semantics. -### Risk Signals - -To aid in fraud assessment, the Platform **MAY** include additional risk signals -in the `complete` call, providing the Business with more context about the -transaction's legitimacy. The structure and content of these risk signals are -not strictly defined by this specification, allowing flexibility based on the -agreement between the Platform and Business or specific payment handler -requirements. - -**Example (Flexible Structure):** - -```json -{ - "risk_signals": { - "session_id": "abc_123_xyz", - "score": 0.95, - } -} -``` - ### Implementation Scenarios The following scenarios illustrate how different payment handlers and @@ -1321,8 +1301,9 @@ POST /checkout-sessions/{id}/complete } ] }, - "risk_signals": { - // ... + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ..." } } ``` @@ -1386,8 +1367,9 @@ POST /checkout-sessions/{id}/complete } ] }, - "risk_signals": { - // ... host could send risk_signals here + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ..." } } ``` @@ -1464,9 +1446,9 @@ POST /checkout-sessions/{id}/complete } ] }, - "risk_signals": { - "session_id": "abc_123_xyz", - "score": 0.95 + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "com.example.risk_score": 0.95 }, "ap2": { "checkout_mandate": "eyJhbGciOiJ...", // Signed proof of checkout terms @@ -1548,19 +1530,17 @@ certified and handle: ### Fraud Prevention Integration -While UCP does not define fraud prevention APIs, the payment architecture -supports fraud signal integration: +UCP supports fraud prevention through [Signals](#signals) and the +payment architecture: +- Platforms provide transaction environment [signals](#signals) (IP, user + agent) on catalog, cart, and checkout requests - Businesses can require additional fields in handler configurations (e.g., 3DS requirements) -- Platforms can submit device fingerprints and session data alongside credentials - Payment credential providers can perform risk assessment during credential acquisition - Businesses can reject high-risk transactions and request additional - verification - -Future extensions **MAY** standardize fraud signal schemas, but the current -architecture allows flexible integration with existing fraud prevention systems. + verification via signal feedback ### Payment Architecture Extensions @@ -1692,6 +1672,65 @@ Sensitive data (such as Payment Credentials or PII) **MUST** be handled according to PCI-DSS and GDPR guidelines. UCP encourages the use of tokenized payment data to minimize business and platform liability. +### Signals + +Businesses require environment data for authorization, rate +limiting, and abuse prevention. Signal values **MUST NOT** be buyer-asserted +claims — platforms provide signals based on direct observation (e.g., +connection IP, user agent) or by relaying independently verifiable +third-party attestations, such as cryptographically signed results from an +external verifier that the business can validate against the provider's +published key set. + +All signal keys **MUST** use reverse-domain naming to ensure provenance and +prevent collisions when multiple extensions contribute to the shared namespace. +Well-known signals use the `dev.ucp` namespace (e.g., `dev.ucp.buyer_ip`); +extension signals use their own namespace (e.g., `com.example.device_id`). + +```json +{ + "signals": { + "dev.ucp.buyer_ip": "203.0.113.42", + "dev.ucp.user_agent": "Mozilla/5.0 ...", + "com.example.attestation": { + "provider_jwks": "https://example.com/.well-known/jwks.json", + "kid": "example-key-2026-01", + "payload": { "id": "att-7c3e9f", "pass": true, "...": "..." }, + "sig": "base64url..." + } + } +} +``` + +Signal fields may contain personally identifiable information +(PII). Platforms **SHOULD** include only signals relevant to the current +transaction. Businesses **SHOULD NOT** persist signal data beyond the +operational needs of the transaction (e.g., order finalization, fraud review). + +Businesses **MAY** use messages with code `signal` to request additional +data. The `path` field identifies the requested signal; the message `type` +determines enforcement. An `error` blocks status progression until the +signal is provided; an `info` is advisory and non-blocking. + +```json +{ + "messages": [ + { + "type": "error", + "code": "signal", + "path": "$.signals['dev.ucp.buyer_ip']", + "content": "Buyer IP is required to proceed." + }, + { + "type": "info", + "code": "signal", + "path": "$.signals['dev.ucp.user_agent']", + "content": "Providing user agent may improve checkout outcomes." + } + ] +} +``` + ### Transaction Integrity and Non-Repudiation For scenarios requiring cryptographic proof of authorization (e.g., autonomous diff --git a/docs/specification/payment-handler-template.md b/docs/specification/payment-handler-template.md index 63eb1d497..baed124c0 100644 --- a/docs/specification/payment-handler-template.md +++ b/docs/specification/payment-handler-template.md @@ -303,8 +303,8 @@ Content-Type: application/json } ] }, - "risk_signals": { - // risk signal objects here + "signals": { + // Platform-observed signals (buyer connection and device) } } ``` diff --git a/source/schemas/shopping/cart.json b/source/schemas/shopping/cart.json index 746a2087a..dee6a3a6a 100644 --- a/source/schemas/shopping/cart.json +++ b/source/schemas/shopping/cart.json @@ -69,6 +69,13 @@ "update": "optional" } }, + "signals": { + "$ref": "types/signals.json", + "ucp_request": { + "create": "optional", + "update": "optional" + } + }, "buyer": { "$ref": "types/buyer.json", "description": "Optional buyer information for personalized estimates.", diff --git a/source/schemas/shopping/catalog_lookup.json b/source/schemas/shopping/catalog_lookup.json index 622b0a1f6..6642b01db 100644 --- a/source/schemas/shopping/catalog_lookup.json +++ b/source/schemas/shopping/catalog_lookup.json @@ -36,6 +36,9 @@ }, "context": { "$ref": "types/context.json" + }, + "signals": { + "$ref": "types/signals.json" } } }, diff --git a/source/schemas/shopping/catalog_search.json b/source/schemas/shopping/catalog_search.json index 6e37094c6..4d7516915 100644 --- a/source/schemas/shopping/catalog_search.json +++ b/source/schemas/shopping/catalog_search.json @@ -16,6 +16,9 @@ "context": { "$ref": "types/context.json" }, + "signals": { + "$ref": "types/signals.json" + }, "filters": { "$ref": "types/search_filters.json" }, diff --git a/source/schemas/shopping/checkout.json b/source/schemas/shopping/checkout.json index ed2b5a37b..5d5bfab37 100644 --- a/source/schemas/shopping/checkout.json +++ b/source/schemas/shopping/checkout.json @@ -56,6 +56,29 @@ "create": "optional", "update": "optional", "complete": "omit" + } + }, + "signals": { + "$ref": "types/signals.json", + "ucp_request": { + "create": "optional", + "update": "optional", + "complete": "optional" + } + }, + "risk_signals": { + "type": "object", + "description": "Deprecated. Use signals instead. Will be removed in the next version.", + "deprecated": true, + "additionalProperties": true, + "ucp_request": { + "complete": { + "transition": { + "from": "optional", + "to": "omit", + "description": "Replaced by signals. Will be removed in the next version." + } + } }, "ucp_response": "omit" }, diff --git a/source/schemas/shopping/types/signals.json b/source/schemas/shopping/types/signals.json new file mode 100644 index 000000000..874823290 --- /dev/null +++ b/source/schemas/shopping/types/signals.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/types/signals.json", + "title": "Signals", + "description": "Environment data provided by the platform to support authorization and abuse prevention. Values MUST NOT be buyer-asserted claims — platforms provide signals based on direct observation or independently verifiable third-party attestations. All signal keys MUST use reverse-domain naming to ensure provenance and prevent collisions when multiple extensions contribute to the shared namespace.", + "type": "object", + "propertyNames": { + "pattern": "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$", + "description": "Reverse-domain identifier (e.g., dev.ucp.buyer_ip, com.example.device_id)." + }, + "properties": { + "dev.ucp.buyer_ip": { + "type": "string", + "description": "Client's IP address (IPv4 or IPv6)." + }, + "dev.ucp.user_agent": { + "type": "string", + "description": "Client's HTTP User-Agent header or equivalent." + } + }, + "additionalProperties": true +} diff --git a/source/services/shopping/rest.openapi.json b/source/services/shopping/rest.openapi.json index 19547166a..145693a78 100644 --- a/source/services/shopping/rest.openapi.json +++ b/source/services/shopping/rest.openapi.json @@ -294,22 +294,7 @@ "required": true, "content": { "application/json": { - "schema": { - "allOf": [ - { "$ref": "#/components/schemas/checkout" }, - { - "title": "Risk Signals", - "description": "Optional risk signals for fraud detection.", - "type": "object", - "properties": { - "risk_signals": { - "type": "object", - "description": "Key-value pairs of risk signals." - } - } - } - ] - } + "schema": { "$ref": "#/components/schemas/checkout" } } } },