Agent Identity
Be tested

Agent Identity & Webhooks

The Playground identifies itself cryptographically on every request and verifies the order webhooks your store sends back. This page explains how to verify the Playground's identity and how to deliver signed order events to it.

The agent profile

Every request the Playground makes carries a UCP-Agent header pointing at its public profile:

http
 UCP-Agent: profile="https://ucpplayground.com/.well-known/ucp-agent" 

That document is a standard UCP platform profile — a ucp member declaring the capabilities the Playground supports, plus a root-level keys array: a JWK Set of public EC keys used to verify its request signatures.

json
{
  "ucp": {
    "version": "2026-08-25",
    "services": {},
    "capabilities": { "dev.ucp.shopping.checkout": [ /* ... */ ] },
    "payment_handlers": {}
  },
  "keys": [
    { "kid": "...", "kty": "EC", "crv": "P-256", "x": "...", "y": "...", "use": "sig", "alg": "ES256" }
  ],
  "signing_keys": [ /* the same set, for verifiers still on v2026-04-08 */ ]
}
Note
keys is a sibling of ucp at the document root — not nested inside it. Verifiers resolve a key as profile.keys and match it to a request by kid. v2026-08-25 made keys the canonical field, replacing signing_keys; the Playground publishes both, and reads either from your profile, so verification works whichever version you are on.

Verifying the Playground's requests

Requests are signed with HTTP Message Signatures (RFC 9421, ECDSA P-256 / ES256), in the default shape of the UCP signatures specification. A signed request looks like this:

http
POST /ucp/v1/catalog/search HTTP/1.1
Content-Type: application/json
UCP-Agent: profile="https://ucpplayground.com/.well-known/ucp-agent"
Idempotency-Key: <uuid>
Content-Digest: sha-256=:<base64 sha-256 of body>:
Signature-Input: sig1=("@method" "@authority" "@path" "ucp-agent" "idempotency-key" "content-digest" "content-type");created=<unix time>;keyid="<kid from keys>"
Signature: sig1=:<base64 signature>:

The signature label is sig1 and its only parameters are created and keyid. There is no tag and no alg: UCP defines no tag of its own, and the algorithm comes from the published key (kty/crv). The covered components follow the request: @query when there is a query string, idempotency-key when that header is sent, content-digest and content-type when there is a body. To verify one:

  1. Read the profile URL from the UCP-Agent header and fetch it over HTTPS.
  2. Parse Signature-Input for the covered components and the keyid, and check the components cover the method, authority and path, plus each of the headers above that the request carries.
  3. Find the matching key in keys by kid.
  4. Confirm Content-Digest equals the SHA-256 of the raw body.
  5. Reconstruct the signature base and verify the signature against the public key.

Verification is optional — the spec makes signing a SHOULD, and you may process unsigned requests — but verifying lets you trust the caller's identity rather than the header string alone.

This applies to UCP requests over MCP and REST. A store-page (WebMCP) run is different: it loads your page in an ordinary browser and uses the tools the page registers, so those page loads carry no UCP-Agent header or request signature.

Sending order webhooks to the Playground

When the Playground completes an order with your store, it can receive your order events. The delivery URL is advertised as config.webhook_url on the order capability in its profile:

json
"dev.ucp.shopping.order": [
  {
    "version": "2026-08-25",
    "spec": "https://ucp.dev/2026-08-25/specification/order",
    "schema": "https://ucp.dev/2026-08-25/schemas/shopping/order.json",
    "config": { "webhook_url": "https://ucpplayground.com/webhooks/ucp/orders" }
  }
]

Your store must sign every webhook. Send these headers, signed with a key published in your own store profile's keys (or signing_keys if you are still on v2026-04-08 — both are read):

http
POST /webhooks/ucp/orders HTTP/1.1
Content-Type: application/json
UCP-Agent: profile="https://your-store.example/.well-known/ucp"
Content-Digest: sha-256=:<base64 sha-256 of body>:
Signature-Input: sig1=("@method" "@authority" "@path" "ucp-agent" "content-digest" "content-type");keyid="<your-kid>"
Signature: sig1=:<base64 signature>:

{"id":"order_abc123","event_id":"evt_123","type":"order.updated", ...}

How the Playground verifies your webhook

  • Signature — resolves your profile from UCP-Agent, matches the kid in your keys, checks the body digest, and verifies the RFC 9421 signature.
  • Ownership — confirms the signer owns the referenced order. A webhook signed by a business that did not place the order is rejected, even with a valid signature.

Responses you can expect:

  • 202 Accepted — verified and recorded.
  • 400 Bad Request — the signature verified but the body names no order.
  • 401 Unauthorized — missing UCP-Agent, unknown key, digest mismatch, or invalid signature.
  • 403 Forbidden — the order is unknown, or the signer does not own it.
Tip
Send the full order entity on every event (not deltas), include a fresh event_id, and retry failed deliveries. The Playground acknowledges quickly with a 2xx and records each delivery for inspection.