UCP Checker
UCP Webhooks: How Stores Tell Agents an Order Changed — and How to Verify It

UCP Webhooks: How Stores Tell Agents an Order Changed — and How to Verify It

Checkout is not the end of an order. It's the beginning.

After an agent buys something, the interesting part starts: the order gets accepted, charged, partially fulfilled, shipped, maybe refunded. Every one of those changes happens on the merchant's side — long after the agent has stopped watching. So how does the agent ever find out? It can poll, hammering the order endpoint every few minutes hoping something changed. Or the store can just tell it the moment it happens. That second option is a UCP webhook, and as of UCP 2026-04-08 it has a spec-defined, cryptographically verified shape.

This is the natural sequel to the agent profile. That post was about the platform proving its identity to a store before it places an order. Webhooks are the same handshake running in reverse: now the store proves its identity to the platform when it reports what happened to that order. Same UCP-Agent header, same signing_keys, same RFC 9421 signatures — pointed the other direction. If you understood the agent profile, you already understand 80% of webhooks.

What a UCP webhook actually is

The spec calls the operation an Order Event Webhook. The direction is business → platform: when something about an order changes, the merchant POSTs to the platform to say so.

The single most important design decision is this: the payload is a current-state snapshot, not a diff. The merchant MUST send the full order entity on every update, never an incremental delta. There is no "field X changed from A to B" event — every webhook is the complete, current order, and the platform reconciles against what it last knew.

That snapshot carries the fields you'd expect on an order:

  • id — the order identifier
  • checkout_id — links the order back to the checkout that created it, for reconciliation
  • line_items — the array of order line items, each with its own derived status
  • fulfillment — fulfillment expectations and events
  • adjustments — post-order changes (refunds, corrections), each with a status
  • totals — the current cost breakdown

Sending the whole entity every time is what makes the system self-healing: if a platform misses a delivery, the next one carries the complete truth anyway. No event-ordering nightmares, no replaying a stream of deltas to rebuild state.

One webhook, many event types

If you've wired up Stripe, you're used to a webhook per event — charge.succeeded, invoice.paid, dozens of named types. UCP is deliberately different: there is one operation, the Order Event Webhook, and the variety lives inside the snapshot as typed event logs. You subscribe once and receive everything that happens to an order. So when people ask "what are the different webhooks in UCP?", the honest answer is: one webhook, several kinds of event — and the events are what you actually branch on.

Two logs carry them.

Fulfillment events — an append-only log tracking actual shipments. Each entry has a type that is an open string, so a merchant can model its real fulfillment process instead of squeezing into a fixed enum. The spec's example values are the ones you'd reach for first:

processing · shipped · in_transit · delivered · failed_attempt · canceled · undeliverable · returned_to_sender

Append-only is the point: a shipment that goes processing → shipped → in_transit → delivered keeps every step inside the snapshot, so the platform sees the whole journey rather than just the latest dot.

Adjustment events — post-order money movements, also an open-string type, typically:

refund · return · credit · price_adjustment · dispute · cancellation

Each adjustment also carries a status enum — pending, completed, failed — so a refund that's been initiated but not yet settled is distinguishable from one that has cleared.

The rollup a platform usually renders is the per-line-item status, derived from quantities: processing, partial, fulfilled, removed. Notably there is no top-level order status field in UCP — status lives at the line-item level, because a real order is often several things at once (one line shipped, one refunded, one still processing). The snapshot model captures that honestly instead of flattening it to a single misleading label.

One more thing worth saying plainly: checkout and identity linking are not webhooks. Checkout is a synchronous request/response, and identity linking is an OAuth redirect flow. The Order Event Webhook is the only place UCP pushes asynchronously from the merchant back to the platform — which is exactly why getting its verification right matters so much.

How the platform advertises where to send them

Discovery reuses the profile machinery from the last post. The platform declares its webhook endpoint as config.webhook_url on the order capability in its agent profile:

{
  "ucp": {
    "version": "2026-04-08",
    "capabilities": {
      "dev.ucp.shopping.order": [
        {
          "version": "2026-04-08",
          "config": { "webhook_url": "https://platform.example.com/webhooks/ucp/orders" }
        }
      ]
    }
  }
}

So the merchant learns where to deliver order events the same way it learned everything else about the platform: by reading the profile at /.well-known/ucp-agent. The profile isn't just a credential — it's the address book too.

How a webhook is signed and verified

This is where the agent-profile groundwork pays off. A spec-compliant order webhook is a signed HTTP request, and verification is symmetric to verifying an agent.

An illustrative delivery looks like this:

POST /webhooks/ucp/orders HTTP/1.1
Host: platform.example.com
UCP-Agent: profile="https://store.example.com/.well-known/ucp"
Webhook-Id: 1f4a9c2e-7b3d-4c1a-9f0e-2a6b8d4e1c00
Webhook-Timestamp: 1749120000
Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "content-type");keyid="store-2026"
Signature: sig1=:MEUCIQ…:
Content-Type: application/json

{ "id": "order_8x2…", "checkout_id": "co_…", "line_items": [ … ], "totals": { … } }

Walking the headers:

  • UCP-Agent carries the business profile URL (as an RFC 8941 dictionary). This is the pointer that tells the platform whose keys to fetch — the mirror of how a store resolves an agent.
  • Content-Digest is the SHA-256 digest of the raw request body (RFC 9530). The signature covers the digest, not the body directly, so any tampering with the JSON invalidates it.
  • Signature-Input / Signature are the RFC 9421 HTTP Message Signature (ECDSA P-256 / ES256). Signature-Input also declares exactly which components are covered — in the spec's example: @method, @authority, @path, content-digest, and content-type — and keyid names which key signed it.
  • Webhook-Id is a unique event identifier (use it to dedupe retries), and Webhook-Timestamp is the unix time the event occurred (use it to reject stale deliveries). Both are required headers carried alongside the signature rather than covered by it.

To verify, the platform: reads the business profile URL from UCP-Agent, fetches that profile, matches the keyid to a kid in the business's signing_keys, confirms the Content-Digest against the body it received, and checks the signature. Keys come from signing_keys in the business's UCP profile — exactly the resolution path from the agent-profile post, just resolving a store instead of an agent.

The rules that make it trustworthy

The spec leans on a few hard MUSTs that are easy to skip and painful to skip:

  • The business MUST send the full order entity, not deltas (covered above).
  • The business MUST retry failed deliveries — so the platform should expect duplicates and dedupe on Webhook-Id.
  • The platform MUST respond quickly with a 2xx to acknowledge receipt, then process the event asynchronously. Doing real work inline before you ACK is how you cause the sender to time out and retry, multiplying load.

And the rule that turns "authenticated" into "authorized": the signer must actually own the referenced order. A valid signature from some business isn't enough — it has to be from this order's business. Otherwise any party with a conformant profile could forge status updates for orders that aren't theirs.

How Playground implements it

In Playground (shipped in v0.10.12), the platform accepts business→platform order webhooks at /webhooks/ucp/orders and verifies them per the spec:

  • Each delivery's RFC 9421 signature is checked against the sending business's published key — resolved from the UCP-Agent profile and matched by kid.
  • The body digest is confirmed.
  • The signer is authorized against the order's owning business — a webhook signed by a business that doesn't own the referenced order is rejected.
  • Unsigned or invalid deliveries are refused; verified deliveries are acknowledged and recorded for inspection.

And closing the loop with the last post: the webhook URL is advertised as config.webhook_url on the order capability in Playground's agent profile. The profile we made schema-conformant is the same profile that now tells stores where to send order events.

Why this matters

For merchants, verified webhooks are how you keep an agent's view of an order honest without exposing a polling endpoint to the world. Sign the full entity, retry on failure, and any conformant platform can trust your updates — using the keys you already publish for everything else.

For platforms, this is the other half of being a real party in agentic commerce. The agent profile let you prove who you are on the way in; webhook verification lets you trust what comes back. Together they're a closed loop: named, signed traffic in both directions, with no anonymous hop anywhere in the order lifecycle.

That closed loop — not the checkout call itself — is what makes agent-mediated commerce something a merchant can actually run a business on.

Try it

See the order capability + webhook_url in our profile: ucpplayground.com/.well-known/ucp-agent Read the changelog: ucpplayground.com/changelog Read the spec: ucp.dev/specification/order · RFC 9421 Check your store: ucpchecker.com/check

Related reading: This is the downstream of the UCP agent profile — same signing machinery, reversed. For the buyer side of identity, see UCP identity linking; for why we built an agent that places real orders, see Why We Built UCP Playground.

About UCP Checker

UCP Checker is the independent validation and monitoring layer for the Universal Commerce Protocol. We crawl, validate and grade every public UCP profile we can find, run the merchant directory, the UCP Score and live adoption stats, and — through UCP Playground — test how real AI agents behave against real stores. Verified order webhooks are how those agents stay in sync with the orders they place.

Check your domain's UCP status

See if your storefront is ready for agentic commerce in seconds.

Weekly UCP Report

Get the agentic commerce digest every Monday

Real adoption data, ecosystem trends, new spec versions, and the stores that broke or recovered this week. Read by founders and engineers building the next generation of commerce.

Free forever No spam Unsubscribe anytime

View a sample report →

Weekly UCP Report
Issue #53 · Oct 5, 2026
+664
new verified stores
Verified rate
Latest spec
Cart capability