Skip to main content

Complete checkout

After buyers select products from the Catalog, they need to complete checkout to finalize their purchase. Checkout MCP Tools implement UCP's checkout capability, enabling your AI agent to create and manage checkout sessions, transitioning buyers through the purchase flow until the order is complete.

This tutorial shows you how to create a checkout session, update the checkout with buyer information, transition checkout based on status, complete checkout and if required, handle escalations with Embedded Checkout Protocol.


In this tutorial, you'll learn how to do the following tasks:

  • Create a checkout session with a selected product variant
  • Monitor checkout status and handle incomplete or requires_escalation states
  • Update checkout with missing information
  • Complete checkout when status reaches ready_for_complete

  • Complete the Search the Catalog tutorial
  • A product variant ID and shop domain.
  • The shop domain from the selected product (for example, ecowear-example.myshopify.com)

Anchor to Step 1: Create a checkout sessionStep 1: Create a checkout session

Using the variant ID from the Catalog lookup, create a checkout session with the create_checkout tool:

  1. Create a checkout.js file that creates a checkout session:

    import 'dotenv/config';
    import { randomUUID } from 'crypto';

    const bearerToken = process.env.BEARER_TOKEN;
    // From previous tutorial.
    const shopDomain = 'ecowear-example.myshopify.com';
    const variantId = 'gid://shopify/ProductVariant/11111111111';

    fetch(`https://${shopDomain}/api/ucp/mcp`, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${bearerToken}`
    },
    body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'tools/call',
    id: 1,
    params: {
    name: 'create_checkout',
    arguments: {
    _meta: {
    ucp: {
    profile: 'https://agent.example/profiles/shopping-agent.json'
    }
    },
    idempotency_key: randomUUID(),
    currency: 'USD',
    line_items: [
    {
    quantity: 1,
    item: {
    id: variantId
    }
    }
    ],
    buyer: {
    email: 'buyer@example.com'
    }
    }
    }
    })
    })
    .then(res => res.json())
    .then(data => {
    if (data.result?.content?.[0]?.text) {
    const checkout = data.result.content[0].text;
    console.log('Checkout ID:', checkout.id);
    console.log('Status:', checkout.status);
    if (checkout.messages?.length > 0) {
    console.log('Messages:', checkout.messages);
    }
    if (checkout.continue_url) {
    console.log('Continue URL:', checkout.continue_url);
    }
    }
    })
    .catch(err => console.error('Request failed:', err));
    {
    "jsonrpc": "2.0",
    "method": "tools/call",
    "id": 1,
    "params": {
    "name": "create_checkout",
    "arguments": {
    "_meta": {
    "ucp": {
    "profile": "https://agent.example/profiles/shopping-agent.json"
    }
    },
    "idempotency_key": "<UUID>",
    "currency": "USD",
    "line_items": [
    {
    "quantity": 1,
    "item": {
    "id": "<VARIANT_ID>"
    }
    }
    ],
    "buyer": {
    "email": "buyer@example.com"
    }
    }
    }
    }
    {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
    "content": [
    {
    "type": "text",
    "text": {
    "ucp": {
    "version": "2026-01-11",
    "capabilities": [
    { "name": "dev.ucp.shopping.checkout", "version": "2026-01-11" }
    ]
    },
    "id": "gid://shopify/Checkout/abc123?key=xyz789",
    "status": "incomplete",
    "currency": "USD",
    "line_items": [
    {
    "id": "li_1",
    "quantity": 1,
    "item": {
    "id": "gid://shopify/ProductVariant/11111111111",
    "title": "Organic Cotton Crewneck Sweater",
    "price": 8900
    },
    "base_amount": 8900,
    "subtotal": 8900,
    "total": 8900
    }
    ],
    "totals": [
    { "type": "subtotal", "amount": 8900 }
    ],
    "messages": [
    {
    "type": "error",
    "code": "missing_shipping_address",
    "severity": "recoverable",
    "content": "Shipping address is required"
    }
    ]
    }
    }
    ],
    "isError": false
    }
    }
  2. Execute the script from your terminal:

    Terminal

    node checkout.js

    The response includes a status field that tells you what to do next:

    • incomplete: Missing required information. Check messages for what's needed. In this case, the shipping address is missing.
    • requires_escalation: Buyer input needed. Redirect to continue_url.
    • ready_for_complete: Ready to finalize the order.

    In this case, the status is incomplete because checkout requires more information from the buyer (their shipping address in this case).


Anchor to Step 2: Update checkout detailsStep 2: Update checkout details

When status is incomplete, use update_checkout to provide missing information identified in the messages array.

We'll update the checkout with the missing shipping address:

import 'dotenv/config';
import { randomUUID } from 'crypto';

const bearerToken = process.env.BEARER_TOKEN;
const shopDomain = 'ecowear-example.myshopify.com';
const variantId = 'gid://shopify/ProductVariant/11111111111';

// Step 1: Create checkout
fetch(`https://${shopDomain}/api/ucp/mcp`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 1,
params: {
name: 'create_checkout',
arguments: {
_meta: {
ucp: {
profile: 'https://agent.example/profiles/shopping-agent.json'
}
},
idempotency_key: randomUUID(),
currency: 'USD',
line_items: [
{
quantity: 1,
item: { id: variantId }
}
],
buyer: { email: 'buyer@example.com' }
}
}
})
})
.then(res => res.json())
.then(data => {
const checkout = data.result?.content?.[0]?.text;
// Check if status is incomplete with missing_shipping_address
if (checkout?.status === 'incomplete') {
const missingAddress = checkout.messages?.find(
msg => msg.code === 'missing_shipping_address'
);
if (missingAddress) {
// Update checkout with shipping address
return fetch(`https://${shopDomain}/api/ucp/mcp`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: 2,
params: {
name: 'update_checkout',
arguments: {
id: checkout.id,
fulfillment: {
methods: [
{
type: 'shipping',
destinations: [
{
first_name: 'Jane',
last_name: 'Smith',
street_address: '123 Main Street',
address_locality: 'Brooklyn',
address_region: 'NY',
postal_code: '11201',
address_country: 'US'
}
]
}
]
},
payment: {
instruments: [
{
id: 'pm_1234567890abc',
handler_id: 'gpay',
type: 'card'
}
],
selected_instrument_id: 'pm_1234567890abc'
}
}
}
})
});
}
}
return null;
})
.then(res => res ? res.json() : null)
.then(data => {
if (data) {
console.log(JSON.stringify(data, null, 2));
}
})
.catch(err => console.error('Request failed:', err));
{
"jsonrpc": "2.0",
"method": "tools/call",
"id": 2,
"params": {
"name": "update_checkout",
"arguments": {
"id": "<CHECKOUT_ID>",
"fulfillment": {
"methods": [
{
"type": "shipping",
"destinations": [
{
"first_name": "Jane",
"last_name": "Smith",
"street_address": "123 Main Street",
"address_locality": "Brooklyn",
"address_region": "NY",
"postal_code": "11201",
"address_country": "US"
}
]
}
]
},
"payment": {
"instruments": [
{
"id": "pm_1234567890abc",
"handler_id": "gpay",
"type": "card"
}
],
"selected_instrument_id": "pm_1234567890abc"
}
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"status": "requires_escalation",
"currency": "USD",
"totals": [
{ "type": "subtotal", "amount": 8900 },
{ "type": "fulfillment", "amount": 500, "display_text": "Shipping" },
{ "type": "total", "amount": 9400 }
],
"continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789"
}
}
],
"isError": false
}
}

Anchor to Step 3: Complete checkoutStep 3: Complete checkout

Depending on the status of the previous call of update_checkout, you may have everything you need.

  1. When status reaches ready_for_complete, call complete_checkout to finalize the order:

    {
    "jsonrpc": "2.0",
    "method": "tools/call",
    "id": 3,
    "params": {
    "name": "complete_checkout",
    "arguments": {
    "id": "<CHECKOUT_ID>",
    "idempotency_key": "<UUID>"
    }
    }
    }
    {
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
    "content": [
    {
    "type": "text",
    "text": {
    "id": "gid://shopify/Checkout/abc123?key=xyz789",
    "status": "completed",
    "currency": "USD",
    "totals": [
    { "type": "subtotal", "amount": 8900 },
    { "type": "fulfillment", "amount": 500, "display_text": "Shipping" },
    { "type": "tax", "amount": 712, "display_text": "Tax" },
    { "type": "total", "amount": 10012 }
    ]
    }
    }
    ],
    "isError": false
    }
    }

Anchor to Optional: handle escalations with Embedded Checkout ProtocolOptional: handle escalations with Embedded Checkout Protocol

When you updated checkout details in the previous step, notice that the value of status was requires_escalation, not ready_for_complete.

With this status the checkout requires buyer input that can't be collected via the API such as a business critical Checkout UI extension implemented by the merchant for age verification. Use the continue_url to render an embedded checkout sheet where buyers can complete these steps securely.

  1. Construct the embedded checkout URL

    Add the following query parameters to the continue_url to enable the Embedded Checkout Protocol:

    ParameterRequiredDescription
    ec_versionYesThe version of the embedded checkout protocol. Must be 2026-01-11.
    ec_authYesThe JWT token retrieved from your server-side authentication.
    ec_delegateYesA comma-separated list of delegations: fulfillment.address_change, payment.instruments_change, payment.credential.

    The resulting URL will look like:

    Construct the embedded checkout URL

    https://ecowear-example.myshopify.com/cart/c/abc123?ec_version=2026-01-11&ec_auth=eyJ...&ec_delegate=fulfillment.address_change,payment.instruments_change,payment.credential

    Note that the ec_-prefixed parameters contain characters that must be URL-escaped.

  2. Render in a web view

    Load the embedded checkout URL in a web view. Checkout coordinates with your application through two JavaScript global variables:

    • EmbeddedCheckoutProtocolConsumer: Created by your app. Checkout calls postMessage() on this object to send events to your application.
    • EmbeddedCheckoutProtocol: Created by Shopify Checkout. Your app calls postMessage() on this object to respond to Checkout events.

    Use WKWebView to render the embedded checkout:

    import WebKit

    class CheckoutViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!

    func loadEmbeddedCheckout(url: URL) {
    let config = WKWebViewConfiguration()
    // Register message handler for Checkout events
    config.userContentController.add(self, name: "EmbeddedCheckoutProtocolConsumer")
    webView = WKWebView(frame: view.bounds, configuration: config)
    view.addSubview(webView)
    webView.load(URLRequest(url: url))
    }

    // Handle messages from Checkout
    func userContentController(_ controller: WKUserContentController,
    didReceive message: WKScriptMessage) {
    // Process ECP message from Checkout
    print("Received message: \(message.body)")
    // Respond using evaluateJavaScript
    webView.evaluateJavaScript(
    "EmbeddedCheckoutProtocol.postMessage({ type: 'response', ... })"
    )
    }
    }
    Embedded checkout UI
    Info

    Instead of implementing the full Embedded Checkout Protocol described below, you can open the continue_url directly in an in-app browser to let buyers complete checkout.

    For a complete reference of all events and delegation patterns, see the Embedded Checkout Protocol reference.



Was this page helpful?