--- title: Complete checkout description: Learn how to complete buyer purchases using the Checkout MCP server. source_url: html: 'https://shopify.dev/docs/agents/get-started/complete-checkout' md: 'https://shopify.dev/docs/agents/get-started/complete-checkout.md' --- # Complete checkout After buyers select products from the Catalog, they need to complete checkout to finalize their purchase. [Checkout MCP Tools](https://shopify.dev/docs/agents/checkout/mcp) implement UCP's [checkout capability](https://ucp.dev/specification/checkout/), 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. *** ## What you'll learn 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` *** ## Requirements * Complete the [Search the Catalog](https://shopify.dev/docs/agents/get-started/search-catalog) tutorial * A product variant ID for the selected item for checkout * The shop domain from the selected product (for example, `ecowear-example.myshopify.com`) *** ## Step 1: Create a checkout session Using the variant ID from the Catalog lookup, create a checkout session with the [`create_checkout`](https://shopify.dev/docs/agents/checkout/mcp#create_checkout) tool: 1. Create a `checkout.js` file that creates a checkout session called against the `shopDomain`'s MCP endpoint (`/api/ucp/mcp`): ##### checkout.js ```javascript 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-agent': { profile: 'https://agent.example/profiles/shopping-agent.json' } }, checkout: { currency: 'USD', line_items: [ { quantity: 1, item: { id: variantId } } ], buyer: { email: 'buyer@example.com' } } } } }) }) .then(res => res.json()) .then(data => { if (data.result?.structuredContent) { const checkout = data.result.structuredContent; 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)); ``` ##### {} MCP input reference ```json { "jsonrpc": "2.0", "method": "tools/call", "id": 1, "params": { "name": "create_checkout", "arguments": { "meta": { "ucp-agent": { "profile": "https://agent.example/profiles/shopping-agent.json" } }, "checkout": { "currency": "USD", "line_items": [ { "quantity": 1, "item": { "id": "" } } ], "buyer": { "email": "buyer@example.com" } } } } } ``` ##### {} Response ```json { "jsonrpc": "2.0", "id": 1, "result": { "structuredContent": { "ucp": { "version": "2026-01-11", "capabilities": { "dev.ucp.shopping.checkout": [ { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/checkout", "schema": "https://ucp.dev/services/shopping/openrpc.json" } ], "dev.ucp.shopping.fulfillment": [ { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/fulfillment", "schema": "https://ucp.dev/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" } ] } }, "id": "gid://shopify/Checkout/abc123?key=xyz789", "status": "incomplete", "currency": "USD", "buyer": { "email": "buyer@example.com" }, "line_items": [ { "id": "gid://shopify/CartLine/li_1?cart=abc123", "item": { "id": "gid://shopify/ProductVariant/11111111111", "title": "Organic Cotton Crewneck Sweater", "price": 8900, "image_url": "https://cdn.shopify.com/example/sweater.jpg" }, "quantity": 1, "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" }, { "type": "items_discount", "amount": 0, "display_text": "Discount" }, { "type": "total", "amount": 8900, "display_text": "Total" } ] } ], "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" } ], "fulfillment": {}, "expires_at": "2026-02-20T15:17:07Z", "messages": [ { "type": "warning", "code": "missing_shipping_address", "content": "Shipping address is required", "path": "$.fulfillment" } ], "continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789" } } } ``` 2. Execute the script from your terminal: ## Terminal ```bash node checkout.js ``` The response includes a `status` field that tells you what to do next: | Status | Description | | - | - | | `incomplete` | Missing required information. Check `messages` for what's needed. | | `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). ## {} Response (focus on create\_checkout status) ```json { "jsonrpc": "2.0", "id": 1, ... "result": { "structuredContent": { "status": "incomplete", ... "messages": [ { "type": "warning", "code": "missing_shipping_address", "content": "Shipping address is required", "path": "$.fulfillment" } ], "continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789" } } } ``` *** ## Step 2: Update checkout details The [`update_checkout`](https://shopify.dev/docs/agents/checkout/mcp#update_checkout) tool call enables you to provide missing information identified in the `messages` array. 1. Update checkout with the missing shipping address while preserving the existing line items and buyer information: ##### checkout.js ```javascript 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-agent': { profile: 'https://agent.example/profiles/shopping-agent.json' } }, checkout: { currency: 'USD', line_items: [ { quantity: 1, item: { id: variantId } } ], buyer: { email: 'buyer@example.com' } } } } }) }) .then(res => res.json()) .then(data => { const checkout = data.result?.structuredContent; // 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 // Include ALL checkout data - UCP replaces the entire state 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: { meta: { 'ucp-agent': { profile: 'https://agent.example/profiles/shopping-agent.json' } }, id: checkout.id, checkout: { // Preserve existing line items line_items: [ { quantity: 1, item: { id: variantId } } ], // Preserve existing buyer info buyer: { email: 'buyer@example.com' }, // Add the missing fulfillment data 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' } ] } ] }, // Add payment information 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)); ``` ##### {} MCP input reference ```json { "jsonrpc": "2.0", "method": "tools/call", "id": 2, "params": { "name": "update_checkout", "arguments": { "meta": { "ucp-agent": { "profile": "https://agent.example/profiles/shopping-agent.json" } }, "id": "", "checkout": { "line_items": [ { "quantity": 1, "item": { "id": "" } } ], "buyer": { "email": "buyer@example.com" }, "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" } } } } } ``` ##### {} Response ```json { "jsonrpc": "2.0", "id": 2, "result": { "structuredContent": { "ucp": { "version": "2026-01-11", "capabilities": { "dev.ucp.shopping.checkout": [ { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/checkout", "schema": "https://ucp.dev/services/shopping/openrpc.json" } ], "dev.ucp.shopping.fulfillment": [ { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/fulfillment", "schema": "https://ucp.dev/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" } ] } }, "id": "gid://shopify/Checkout/abc123?key=xyz789", "status": "requires_escalation", "currency": "USD", "buyer": { "email": "buyer@example.com" }, "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" }, { "type": "fulfillment", "amount": 500, "display_text": "Shipping" }, { "type": "tax", "amount": 752, "display_text": "Tax" }, { "type": "total", "amount": 10052, "display_text": "Total" } ], "fulfillment": {}, "expires_at": "2026-02-20T15:17:07Z", "messages": [], "continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789" } } } ``` **Caution:** Checkout MCP requires the complete checkout state on every request. `update_checkout` replaces the entire checkout with the data you provide, so if you omit fields like `line_items` or `buyer`, they're removed from the checkout. Notice that [the line items included when checkout session was created](#step-1-create-a-checkout-session) are repeated in the example above. *** ## Step 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`](https://shopify.dev/docs/agents/checkout/mcp#complete_checkout) to finalize the order: ##### {} MCP input reference ```json { "jsonrpc": "2.0", "method": "tools/call", "id": 3, "params": { "name": "complete_checkout", "arguments": { "meta": { "ucp-agent": { "profile": "https://agent.example/profiles/shopping-agent.json" }, "idempotency-key": "" }, "id": "", "checkout": { "payment": { "instruments": [ "id": "pm_1234567890abc", "handler_id": "gpay_7k2m", "type": "card" ] } } } } } ``` ##### {} Response ```json { "jsonrpc": "2.0", "id": 3, "result": { "structuredContent": { "ucp": { "version": "2026-01-11", "capabilities": { "dev.ucp.shopping.checkout": [ { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/checkout", "schema": "https://ucp.dev/services/shopping/openrpc.json" } ], "dev.ucp.shopping.fulfillment": [ { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/fulfillment", "schema": "https://ucp.dev/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" } ] } }, "id": "gid://shopify/Checkout/abc123?key=xyz789", "status": "completed", "currency": "USD", "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" }, { "type": "fulfillment", "amount": 500, "display_text": "Shipping" }, { "type": "tax", "amount": 712, "display_text": "Tax" }, { "type": "total", "amount": 10012, "display_text": "Total" } ], "order": { "id": "gid://shopify/Order/9876543210", "permalink_url": "https://ecowear-example.myshopify.com/orders/9876543210" }, "messages": [] } } } ``` ### Optional: 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](https://shopify.dev/docs/api/checkout-ui-extensions/latest) 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: | Parameter | Required | Description | | - | - | - | | `ec_version` | Yes | The version of the embedded checkout protocol. Must be `2026-01-11`. | | `ec_auth` | Yes | The JWT token retrieved from your server-side authentication. | | `ec_delegate` | Yes | A comma-separated list of delegations: `fulfillment.address_change`, `payment.instruments_change`, `payment.credential`. | The resulting URL will look like: ## Construct the embedded checkout URL ```txt 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. #### iOS Use `WKWebView` to render the embedded checkout: ```swift 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', ... })" ) } } ``` #### Android Use `WebView` to render the embedded checkout: ```kotlin import android.webkit.WebView import android.webkit.JavascriptInterface class CheckoutActivity : AppCompatActivity() { private lateinit var webView: WebView fun loadEmbeddedCheckout(url: String) { webView = findViewById(R.id.webView) webView.settings.javaScriptEnabled = true // Register message handler for Checkout events webView.addJavascriptInterface( EmbeddedCheckoutConsumer(), "EmbeddedCheckoutProtocolConsumer" ) webView.loadUrl(url) } inner class EmbeddedCheckoutConsumer { @JavascriptInterface fun postMessage(message: String) { // Process ECP message from Checkout println("Received message: $message") // Respond using evaluateJavascript webView.evaluateJavascript( "EmbeddedCheckoutProtocol.postMessage({ type: 'response', ... })", null ) } } } ``` #### Web For web applications, open the embedded checkout in an iframe or new window: ```javascript if (checkout.status === 'requires_escalation' && checkout.continue_url) { const embeddedURL = await createEmbeddedCheckoutURL(checkout); window.open(embeddedURL, '_blank'); } ``` ![Embedded checkout UI](https://shopify.dev/assets/assets/images/agents/branded-checkout-default-DdWr9slg.png) **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](https://shopify.dev/docs/agents/checkout/ecp). *** ## Next steps * [About Shopify Catalog](https://shopify.dev/docs/agents/catalog): Learn how Catalog search and lookup work across the Shopify ecosystem. * [Checkout for agents](https://shopify.dev/docs/agents/checkout): Learn how to embed checkout in agentic commerce apps. * [UCP Checkout specification](https://ucp.dev/specification/checkout/): Explore the full UCP Checkout capability specification. * [UCP Order specification](https://ucp.dev/specification/order/): Learn about confirmed transactions and post-purchase events, including line items, fulfillment expectations and events, and adjustments like refunds and returns. ***