Testing and Validation
9.1 UCP Profile Validation
The Profile is the entry point for every AI agent connection and must pass rigorous validation.Basic Checks
# 1. Verify HTTPS and no redirects
curl -sI "https://mystore.com/.well-known/ucp" | head -5
# Expected: HTTP/2 200 (must not be 3xx)
# 2. Check Cache-Control
curl -sI "https://mystore.com/.well-known/ucp" | grep -i cache-control
# Expected: cache-control: public, max-age=N (N >= 60)
# 3. Check Content-Type
curl -sI "https://mystore.com/.well-known/ucp" | grep -i content-type
# Expected: content-type: application/json
# 4. Fetch and validate JSON
curl -s "https://mystore.com/.well-known/ucp" | jq .
Profile Field Validation Checklist
| Field | Validation Rule | Required |
|---|---|---|
supported_versions | Non-empty array containing valid date-formatted version strings | Yes |
services | At least one service; base_url must be HTTPS | Yes |
capabilities | At least one capability; namespace format must be correct | Yes |
capabilities.*.version | Valid date format; within the supported_versions range | Yes |
payment_handlers | Array with reverse-DNS naming format | No |
signing_keys | JWK array; each key must have kid, kty, crv, x, y, use, alg | No |
Automated Profile Validation Script
#!/bin/bash
URL="https://mystore.com/.well-known/ucp"
echo "=== UCP Profile Validation ==="
# Check HTTP status code (do not follow redirects)
STATUS=$(curl -so /dev/null -w "%{http_code}" --max-redirs 0 "$URL")
if [ "$STATUS" = "200" ]; then
echo "PASS: HTTP 200 (no redirect)"
else
echo "FAIL: HTTP $STATUS (expected 200, no redirects allowed)"
fi
# Check HTTPS
if [[ "$URL" == https://* ]]; then
echo "PASS: HTTPS"
else
echo "FAIL: Must be HTTPS"
fi
# Check Cache-Control
CACHE=$(curl -sI "$URL" | grep -i "cache-control" | tr -d '\r')
if echo "$CACHE" | grep -qi "public"; then
echo "PASS: Cache-Control includes public"
else
echo "FAIL: Cache-Control must include 'public'"
fi
# Check JSON validity
BODY=$(curl -s "$URL")
if echo "$BODY" | jq . > /dev/null 2>&1; then
echo "PASS: Valid JSON"
else
echo "FAIL: Invalid JSON"
fi
# Check required fields
if echo "$BODY" | jq -e '.supported_versions | length > 0' > /dev/null 2>&1; then
echo "PASS: supported_versions present"
else
echo "FAIL: supported_versions missing or empty"
fi
if echo "$BODY" | jq -e '.services | keys | length > 0' > /dev/null 2>&1; then
echo "PASS: services present"
else
echo "FAIL: services missing or empty"
fi
if echo "$BODY" | jq -e '.capabilities | keys | length > 0' > /dev/null 2>&1; then
echo "PASS: capabilities present"
else
echo "FAIL: capabilities missing or empty"
fi
# Check signing key format
KEYS=$(echo "$BODY" | jq -r '.signing_keys | length')
if [ "$KEYS" -gt 0 ]; then
echo "INFO: $KEYS signing key(s) found"
echo "$BODY" | jq -r '.signing_keys[] | " kid=\(.kid) alg=\(.alg) crv=\(.crv)"'
else
echo "INFO: No signing keys (Webhook signing not configured)"
fi
echo "=== Validation Complete ==="
9.2 Checkout Flow Testing
Complete Checkout Flow
BASE="https://mystore.com/ucp"
TOKEN="Bearer ucp_pk_test_xxxx"
# Step 1: Create a checkout session
SESSION=$(curl -s -X POST "$BASE/checkout/sessions" \
-H "Content-Type: application/json" \
-H "Authorization: $TOKEN" \
-d '{
"line_items": [{"product_id": "prod_001", "quantity": 1}],
"buyer": {"email": "test@example.com"}
}')
echo "Create: $(echo $SESSION | jq -r '.checkout_session.status')"
# Expected: incomplete
SESSION_ID=$(echo $SESSION | jq -r '.checkout_session.id')
# Step 2: Update buyer information and shipping
curl -s -X PATCH "$BASE/checkout/sessions/$SESSION_ID" \
-H "Content-Type: application/json" \
-H "Authorization: $TOKEN" \
-d '{
"buyer": {
"name": "Test User",
"shipping_address": {
"line1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94102",
"country_code": "US"
}
},
"fulfillment": {"method": "standard_shipping"}
}' | jq '.checkout_session.status'
# Step 3: Query session status
STATUS=$(curl -s "$BASE/checkout/sessions/$SESSION_ID" \
-H "Authorization: $TOKEN" | jq -r '.checkout_session.status')
echo "After update: $STATUS"
# Expected: incomplete or ready_for_complete
# Step 4: Cancel the session (recommended in test environments instead of completing)
curl -s -X POST "$BASE/checkout/sessions/$SESSION_ID/cancel" \
-H "Content-Type: application/json" \
-H "Authorization: $TOKEN" \
-d '{"reason": "test_cancellation"}' | jq '.checkout_session.status'
# Expected: canceled
State Machine Validation
Test all six status transition paths for correctness:| Test Scenario | Initial Status | Operation | Expected Result |
|---|---|---|---|
| Create empty session | - | Create | incomplete |
| Provide complete information | incomplete | Update | ready_for_complete |
| Submit for completion | ready_for_complete | Complete | complete_in_progress |
| Cancel an in-progress session | incomplete | Cancel | canceled |
| Cancel a completed session | completed | Cancel | 400 error |
| Update a canceled session | canceled | Update | 400 error |
| Trigger human escalation | incomplete | Specific condition | requires_escalation |
9.3 Product Data Validation
Every product record must pass the following validation:Required field checks:
[x] id: Non-empty and unique
[x] name: Non-empty string
[x] price.amount: Positive integer (minor currency units)
[x] price.currency_code: Valid ISO 4217 code (e.g., USD, EUR, JPY)
[x] availability: One of in_stock / out_of_stock / preorder
[x] images: At least one item; URL must be accessible via HTTPS
Optional field format checks:
[x] variants[].sku: If present, must be unique
[x] variants[].price.amount: Positive integer
[x] gtin: If present, must conform to GTIN-13 or GTIN-14 format
[x] compare_at_price.amount: If present, must be greater than price.amount
Amount consistency:
[x] All price objects use the same currency_code
[x] Amount values are consistent with the currency's minor unit (USD uses cents, JPY uses yen)
9.4 Signature Verification Testing
If Webhook signing is configured, test both signature creation and verification:# Simulate a merchant sending a signed Webhook
# 1. Create a test request body
BODY='{"event_type":"order.created","order_id":"ord_test_001"}'
# 2. Compute Content-Digest
DIGEST=$(echo -n "$BODY" | openssl dgst -sha256 -binary | base64)
echo "Content-Digest: sha-256=:$DIGEST:"
# 3. Verify signing keys in the merchant Profile
curl -s "https://mystore.com/.well-known/ucp" | jq '.signing_keys'
# Confirm kid, alg (ES256), crv (P-256) and other fields are correct
9.5 OAuth Flow Testing
# 1. Verify authorization server discovery
curl -s "https://mystore.com/.well-known/oauth-authorization-server" | jq .
# Check: authorization_endpoint, token_endpoint, revocation_endpoint
# 2. Verify PKCE support
# Check that code_challenge_methods_supported includes "S256"
# 3. Verify scope support
# Check that scopes_supported includes "ucp:scopes:checkout_session"
# 4. Test the token revocation endpoint (RFC 7009)
curl -s -X POST "https://mystore.com/oauth/revoke" \
-d "token=expired_test_token&token_type_hint=access_token&client_id=test" \
-w "\nHTTP Status: %{http_code}\n"
# Expected: 200 (should return 200 even for invalid tokens)
9.6 Performance Benchmarks
UCP endpoints should respond within reasonable timeframes:| Endpoint Type | Response Time Target | Concurrency Requirement |
|---|---|---|
/.well-known/ucp Profile | Under 100ms | 100+ QPS |
| Product search | Under 500ms | 50+ QPS |
| Product details | Under 200ms | 100+ QPS |
| Checkout operations | Under 1s | 20+ QPS |
| OAuth token endpoint | Under 300ms | 50+ QPS |
# Simple performance test
# Profile endpoint latency
for i in $(seq 1 10); do
curl -so /dev/null -w "%{time_total}\n" "https://mystore.com/.well-known/ucp"
done
# Product search concurrency test (requires ab or wrk)
ab -n 100 -c 10 "https://mystore.com/ucp/catalog/products?query=test&limit=10"
9.7 Common Issues and Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| AI agent cannot discover Profile | 3xx redirect or non-HTTPS | Ensure a direct 200 response with no redirects |
| Cache-Control validation fails | max-age below 60 seconds | Set public, max-age=3600 |
| Prices display incorrectly | Not using minor currency units | Confirm that amount is an integer (cents), not a decimal |
| Signature verification fails | kid mismatch or expired key | Check that the kid in signing_keys matches the Signature-Input |
| OAuth flow breaks | Missing PKCE support | Implement S256 code_challenge verification |
| Capability negotiation returns empty | Version mismatch | Ensure both parties have overlapping supported_versions |
| Webhooks not received | Signature verification rejected | Verify Content-Digest and Signature are correct |
Next chapter: Case Studies and Extensions — Buyer Consent extension, AP2 Mandate, and real-world integration paths