Order Webhooks

Order Webhooks

After the Playground's agent places an order with your store, your store can send signed order events back. This page covers where to send them, how the Playground verifies them, what it answers, and where to see each delivery in the run.

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 2026-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: the host of the profile in UCP-Agent must be the store's host (with or without www.), or a host your own UCP profile names as a service endpoint. Another store on the same parent domain does not count. 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 (unknown_order), or the signer does not own it (ownership_mismatch). The Playground records an order once your complete_checkout answer reaches it, so an order.created sent before that answer gets unknown_order: send webhooks after responding, and retry this one with backoff.
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.

Seeing your webhooks in a run

Every delivery for an order placed in a run shows in that run's Trace, after the call that returned the order, as a line such as Store sent order.created · signed · accepted, or the reason it was refused. Open the line for the event id and type, the host and key that signed it, whether the signature verified, whether the signer owns the order, our answer, the attempt, and how long after checkout it arrived. Deliveries that arrive after the run ended still show when the run is opened again, and Info counts them under Order updates from the store. A shared run shows the type, status and timing only, never the order body.

Something wrong on this page?