Agentic commerce changes what a "checkout request" looks like.
In the Universal Commerce Protocol (UCP), the thing hitting your complete_checkout endpoint is often not a browser at all. Itβs a platform's backend or an AI agent calling your API directly via REST or MCP. Thereβs no page load, no JavaScript execution, nothing for a browser-based bot detection script to run in.
This is the gap we built our Automation Intelligence API to fill: It detects automation from HTTP request metadata alone β headers, method, URL, IP β with no JavaScript agent required.
In this post, weβll walk through how to wire it into a UCP merchant integration, and β because UCP happens to give you two separate places to look β how to run it on both the caller of your checkout API and, when the platform provides it, the buyer behind the transaction.
What a UCP checkout request actually looks like
UCP's spec and reference implementations live in the Universal-Commerce-Protocol GitHub org: The protocol itself in ucp, and runnable reference merchant servers (Python/FastAPI and Node.js/Hono) in samples.
The Checkout capability is what you'll integrate against.
Its REST binding defines five operations, all under /checkout-sessions:
| Operation | Method | Endpoint |
|---|---|---|
| Create Checkout | POST |
/checkout-sessions |
| Get Checkout | GET |
/checkout-sessions/{id} |
| Update Checkout | PUT |
/checkout-sessions/{id} |
| Complete Checkout | POST |
/checkout-sessions/{id}/complete |
| Cancel Checkout | POST |
/checkout-sessions/{id}/cancel |
(Source: Checkout β REST Binding)
Every request, regardless of operation, must carry a UCP-Agent header identifying the calling platform's profile, plus required Idempotency-Key and Request-Id headers.
Platforms may additionally authenticate using RFC 9421 HTTP Message Signatures via Signature/Signature-Input headers.
MCP is a separate binding. The same five operations exist, but they arrive as tools/call requests to a single MCP endpoint, with the operation name in params.name and the payload in params.arguments.
The checkout session ID moves out of the URL path and into the arguments, Request-Id isn't part of the binding, and the platform profile travels in meta["ucp-agent"] inside the body.
The detection call is the same either way.
The Automation Intelligence API works from headers, method, URL, and IP, none of which change shape between a REST payload and a JSON-RPC envelope. In our documentation, the Bot Detection overview lists "AI agents or assistants calling your API directly, MCP servers, or any backend-to-backend traffic" as the traffic the Automation Intelligence API targets, because none of it requires a browser.
Two different things to point Automation Intelligence at
UCP integration gives you signal about two distinct actors, and they deserve two distinct checks.
| The caller | The buyer | |
|---|---|---|
| What it is | Whatever opened the HTTP connection to your checkout-sessions (or /mcp) endpoint |
The human the platform says is completing the purchase |
| Where it comes from | The raw HTTP request itself β headers, method, URL, client IP | signals["dev.ucp.buyer_ip"] / signals["dev.ucp.user_agent"] in the UCP request body, when the platform includes them |
| Availability | Always present | Optional |
The signals object is documented in the protocol specification's Signals section.
One check tells you about the thing that's actually talking to your server. The other tells you what the platform says it observed about the buyer's connection, on a surface you never talked to. Both are useful, but not interchangeable.
Detecting the caller (REST or MCP)
The Automation Intelligence API has a single endpoint, POST /edge (also called the Collect Intelligence endpoint).
Its request body (EdgeRequest) requires:
headersβ an ordered array of{name, value}pairsmethodβ the HTTP methodurlβ the absolute URL that was requested- at least one of
ipv4_address/ipv6_address
REST example
Say a platform sends this to your complete_checkout endpoint:
POST /checkout-sessions/chk_9f2a/complete HTTP/1.1
Host: merchant.example.com
UCP-Agent: profile="https://platform.example/.well-known/ucp"
User-Agent: platform-client/1.0.0
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Request-Id: req_abc123
Authorization: Bearer sk-live-abc123
Content-Type: application/json
{ "payment": { "instruments": [ { "handler_id": "gpay_1234" } ] } }MCP example
Over MCP, the same operation arrives as a tools/call to your MCP endpoint instead:
POST /mcp HTTP/1.1
Host: merchant.example.com
Content-Type: application/json
UCP-Agent: profile="https://platform.example/.well-known/ucp"
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Digest: sha-256=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:
Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "content-type" "ucp-agent" "idempotency-key");keyid="platform-2026"
Signature: sig1=:MEUCIQDXyK9N3p5Rt...:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "complete_checkout",
"arguments": {
"meta": {
"ucp-agent": { "profile": "https://platform.example/.well-known/ucp" },
"idempotency-key": "550e8400-e29b-41d4-a716-446655440000"
},
"id": "chk_9f2a",
"checkout": {
"payment": { "instruments": [ { "handler_id": "gpay_1234" } ] }
}
}
}
}Either way you forward that request β minus the secret values β to /edge:
POST https://api.fpjs.io/v4/edge
Authorization: Bearer FPJS_SECRET_API_KEY
{
"headers": [
{ "name": "Host", "value": "merchant.example.com" },
{ "name": "UCP-Agent", "value": "profile=\"https://platform.example/.well-known/ucp\"" },
{ "name": "User-Agent", "value": "platform-client/1.0.0" },
{ "name": "Idempotency-Key", "value": "550e8400-e29b-41d4-a716-446655440000" },
{ "name": "Request-Id", "value": "req_abc123" },
{ "name": "Authorization", "value": "" },
{ "name": "Content-Type", "value": "application/json" }
],
"method": "POST",
"url": "https://merchant.example.com/checkout-sessions/chk_9f2a/complete",
"ipv4_address": "34.162.244.71",
// custom correlation value
"linked_id": "chk_9f2a",
// arbitrary metadata
"tags": { "source": "ucp_transport", "user_agent": "platform-client/1.0.0" }
}ipv4_address here is the actual peer connecting to your server, not anything from the UCP payload.
linked_id set to the checkout session ID lets you pull every Automation Intelligence event for a given checkout later via GET /v4/events?source=edge or a single event via GET /v4/events/{event_id}.
The endpoint shown is the global region. Use eu.api.fpjs.io or ap.api.fpjs.io if your workspace is in the EU or Asia.
Web Bot Auth is also supported
If the calling agent signs its requests per Web Bot Auth, the request carries Signature-Agent, Signature-Input (with tag="web-bot-auth"), and Signature headers, separate from β and possibly alongside β any UCP-level RFC 9421 signature.
Forward those three headers through in your headers[] array exactly like any other header (the values intact, unlike Authorization), and Fingerprint verifies the signature server-side against the published key directory, returning a response where bot_info.identity is "signed".
- Visit ourΒ Web Bot Auth testing pageΒ for a free, public endpoint where you can send a signed request and get clear feedback on whether your signature validates correctly.
Reading the response
bot_info is only present in the response when a bot is detected β no bot_info means no automation signal, not a confirmed human. When present, per AI agent detection, bot_info.identity is the field to act on:
verifiedβ identity confirmed by Fingerprint.signedβ the WBA signature checked out against the agent's published key directory.unknownβ recognized as automation, but it didn't present a verifiable identity.spoofedβ it claimed an identity that couldnβt be verified.
Detecting the buyer, when UCP has the signals
Now the second, complementary check. When a platform includes signals on a complete_checkout request:
{
"payment": { "instruments": [ /* ... */ ] },
"signals": {
"dev.ucp.buyer_ip": "203.0.113.42",
"dev.ucp.user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."
}
}That's a second, independent thing worth running through the Automation Intelligence API. This is the human the transaction is for, and their IP and browser are worth the same bot or IP intelligence (proxy, VPN, datacenter, geolocation) checks you'd apply to a caller you connected to directly. Signals aren't limited to completion, so you can run this earlier in the flow as well.
{
"headers": [
{ "name": "User-Agent", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..." }
],
"method": "POST",
"url": "https://merchant.example.com/checkout-sessions/chk_9f2a/complete",
"ipv4_address": "203.0.113.42",
"linked_id": "chk_9f2a",
"tags": { "source": "ucp_signal", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..." }
}A single User-Agent string, with no accompanying header set, is a weaker input than a real captured request β you're mainly getting IP intelligence on the buyer's IP, plus whatever automation signal Fingerprint can infer from the UA string. That's still useful β a buyer_ip that resolves to a datacenter or a known VPN exit node is worth knowing about.
Putting both checks together
- Caller
identityissigned/verified, no buyer signal present. Nothing further to review from this pair. - Caller
identityisspoofed. Worth reviewing regardless of what the buyer signal says; a caller failing its own claimed identity check is a stronger signal than anything derived from a buyer IP you didn't observe yourself. - Caller looks clean, buyer IP resolves to a datacenter or known proxy/VPN. Doesn't necessarily mean fraud (plenty of legitimate users route through VPNs), but it's a reasonable input into a broader risk model alongside order value, shipping/billing mismatch, and history.
- No buyer signal at all. UCP doesn't require platforms to send it, and many won't in agent-initiated purchases.
Store both events (or at least both event_ids) against the checkout, and let your fraud/risk logic decide what to do with the combination.
Where this stands today
Two things worth flagging before you build on this.
The first is that Automation Intelligence API is currently in Public Preview, so the response schema is subject to change.
The second is that UCP is an actively developed spec, too. The ucp repository has an open issue tracker and regular pull requests, and the protocol uses dated versioning (this post reflects the 2026-04-08 release) specifically because backward-incompatible changes are expected to keep shipping as the spec matures.
Further reading
GitHub:
- Universal Commerce Protocol
- UCP specification & docs source (
ucprepo) - UCP sample merchant servers (
samplesrepo)
UCP.dev:
Fingerprint docs:





