--- title: Finish checkout with Checkout MCP description: >- Learn how to create and manage checkout sessions with Checkout MCP and direct buyers to the merchant's storefront to finish their purchase. source_url: html: 'https://shopify.dev/docs/agents/get-started/checkout' md: 'https://shopify.dev/docs/agents/get-started/checkout.md' --- # Finish checkout with Checkout MCP This guide is the fourth part of a four-part tutorial series that describes how to build an agentic commerce application with the Universal Commerce Protocol (UCP) using Shopify's MCP servers. It demonstrates how to create a checkout session, add buyer information, and refer the buyer to a merchant storefront to finish their purchase. By the end of this tutorial, you'll have extended the demo scripts from the [Search the catalog](https://shopify.dev/docs/agents/get-started/search-catalog) tutorial to create and manage checkout sessions using [Checkout MCP Tools](https://shopify.dev/docs/agents/checkout/mcp). **Coming soon:** Checkout MCP tools are coming soon. *** ## What you'll learn In this tutorial, you'll learn how to: * Discover the merchant's Checkout MCP endpoint from `/.well-known/ucp` * Create a checkout session with a selected product variant * Add buyer email with `update_checkout` and collect it interactively * Append UTM attribution to `continue_url` before referring buyers * Optionally cancel a checkout session when the buyer abandons the flow *** ## Requirements * Complete the [Search the catalog](https://shopify.dev/docs/agents/get-started/search-catalog) tutorial *** ## Step 1: Discover merchant capabilities Before calling Checkout MCP, you need the merchant's MCP endpoint. Merchants that support UCP expose a [business profile](https://ucp.dev/specification/overview/) at `/.well-known/ucp` on their storefront origin. That document describes their UCP version, services (including the Checkout MCP endpoint), and capabilities. The following example shows the shape of a well-known document: ## {shop}.example.com/.well-known/ucp ```json { "ucp": { "version": "2026-01-23", "supported_versions": { "2026-01-23": "https://{shop}.example.com/.well-known/ucp" }, "services": { "dev.ucp.shopping": [ { "version": "2026-01-23", "spec": "https://ucp.dev/specification/overview/", "transport": "mcp", "endpoint": "https://{shop}.example.com/api/ucp/mcp", "schema": "https://ucp.dev/services/shopping/openrpc.json" } ] }, "capabilities": { "dev.ucp.shopping.checkout": [ { "version": "2026-01-23", "spec": "https://ucp.dev/specification/checkout", "schema": "https://ucp.dev/schemas/shopping/checkout.json" } ] } } } ``` You can use this document to confirm what the merchant supports before starting checkout. Create a `checkout.js` file that includes a helper function that fetches this document and returns the MCP endpoint for a given merchant origin. You'll use this helper in the next step when you create the checkout. ## checkout.js ```javascript const AGENT_PROFILE = 'https://shopify.dev/ucp/agent-profiles/2026-01-23/checkout-only.json'; export async function getMcpEndpoint(merchantOrigin) { try { const res = await fetch(`${merchantOrigin}/.well-known/ucp`); if (!res.ok) throw new Error(res.status); const ucp = await res.json(); const shopping = ucp?.ucp?.services?.['dev.ucp.shopping']; const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp'); if (mcp?.endpoint) return mcp.endpoint; } catch (_) {} return `${merchantOrigin}/api/ucp/mcp`; } ``` Be sure to replace `AGENT_PROFILE` with your hosted profile URL from the [Define a profile](https://shopify.dev/docs/agents/get-started/profile#step-2-host-the-profile) step. *** ## Step 2: Create checkout Now that you have a way to discover the MCP endpoint, create the checkout session. Add `createCheckout` to `checkout.js`: ##### checkout.js ```javascript const AGENT_PROFILE = 'https://agent.example/profiles/ucp-demo-agent.json'; async function getMcpEndpoint(merchantOrigin) { try { const res = await fetch(`${merchantOrigin}/.well-known/ucp`); if (!res.ok) throw new Error(res.status); const ucp = await res.json(); const shopping = ucp?.ucp?.services?.['dev.ucp.shopping']; const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp'); if (mcp?.endpoint) return mcp.endpoint; } catch (_) {} return `${merchantOrigin}/api/ucp/mcp`; } // Returns the checkout ID for use in subsequent calls. export async function createCheckout(token, variantId, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 3, params: { name: 'create_checkout', arguments: { checkout: { currency: 'USD', line_items: [{ quantity: 1, item: { id: variantId } }] }, meta: { 'ucp-agent': { profile: AGENT_PROFILE } } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`create_checkout failed: ${JSON.stringify(data)}`); const { id, totals } = data.result.content[0].text; const total = totals?.find(t => t.type === 'total')?.amount ?? 0; console.log('\n── 4. Create Checkout ─────────────────────────────\n'); console.log(` ID: ${id}`); console.log(` Total: $${(total / 100).toFixed(2)}`); return id; } ``` ##### {} MCP input reference ```json { "jsonrpc": "2.0", "method": "tools/call", "id": 3, "params": { "name": "create_checkout", "arguments": { "checkout": { "currency": "USD", "line_items": [ { "quantity": 1, "item": { "id": "" } } ] }, "meta": { "ucp-agent": { "profile": "https://agent.example/profiles/ucp-demo-agent.json" } } } } } ``` ##### {} Response ```json { "jsonrpc": "2.0", "id": 3, "result": { "content": [ { "type": "text", "text": { "id": "gid://shopify/Checkout/abc123?key=xyz789", "status": "requires_escalation", "messages": [ { "type": "error", "severity": "requires_buyer_input" } ], "currency": "USD", "line_items": [ { "id": "gid://shopify/CartLine/li_1?cart=abc123", "item": { "id": "gid://shopify/ProductVariant/11111111111", "title": "Organic Cotton Crewneck Sweater", "price": 8900 }, "quantity": 1, "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" }, { "type": "total", "amount": 8900, "display_text": "Total" } ] } ], "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" }, { "type": "total", "amount": 8900, "display_text": "Total" } ], "expires_at": "2026-02-20T15:17:07Z", "continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789" } } ] } } ``` *** ## Step 3: Update checkout After checkout is created, you may optionally want to attach buyer information to that checkout. The [`update_checkout`](https://shopify.dev/docs/agents/checkout/mcp#update_checkout) tool can add the buyer's email to the checkout session. Add the `updateCheckout` function to `checkout.js`: ##### checkout.js ```javascript const AGENT_PROFILE = 'https://agent.example/profiles/ucp-demo-agent.json'; async function getMcpEndpoint(merchantOrigin) { try { const res = await fetch(`${merchantOrigin}/.well-known/ucp`); if (!res.ok) throw new Error(res.status); const ucp = await res.json(); const shopping = ucp?.ucp?.services?.['dev.ucp.shopping']; const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp'); if (mcp?.endpoint) return mcp.endpoint; } catch (_) {} return `${merchantOrigin}/api/ucp/mcp`; } export async function createCheckout(token, variantId, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 3, params: { name: 'create_checkout', arguments: { checkout: { currency: 'USD', line_items: [{ quantity: 1, item: { id: variantId } }] }, meta: { 'ucp-agent': { profile: AGENT_PROFILE } } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`create_checkout failed: ${JSON.stringify(data)}`); const { id, totals } = data.result.content[0].text; const total = totals?.find(t => t.type === 'total')?.amount ?? 0; console.log('\n── 4. Create Checkout ─────────────────────────────\n'); console.log(` ID: ${id}`); console.log(` Total: $${(total / 100).toFixed(2)}`); return id; } export async function updateCheckout(token, checkoutId, variantId, email, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 4, params: { name: 'update_checkout', arguments: { id: checkoutId, checkout: { currency: 'USD', line_items: [{ quantity: 1, item: { id: variantId } }], buyer: { email } }, meta: { 'ucp-agent': { profile: AGENT_PROFILE } } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`update_checkout failed: ${JSON.stringify(data)}`); console.log('\n── 5. Update Checkout ─────────────────────────────\n'); return data.result.content[0].text.continue_url; } export async function cancelCheckout(token, checkoutId, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 5, params: { name: 'cancel_checkout', arguments: { id: checkoutId, meta: { 'ucp-agent': { profile: AGENT_PROFILE }, 'idempotency-key': crypto.randomUUID() } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`cancel_checkout failed: ${JSON.stringify(data)}`); const status = data.result.structuredContent?.status; if (status !== 'canceled') throw new Error(`Unexpected status: ${status}`); console.log('\n── 6. Cancel Checkout ─────────────────────────────\n'); console.log(` Status: ${status}`); console.log(' Checkout has been successfully cancelled.'); console.log(' Demo complete.\n'); } ``` ##### {} MCP input reference ```json { "jsonrpc": "2.0", "method": "tools/call", "id": 4, "params": { "name": "update_checkout", "arguments": { "id": "", "checkout": { "currency": "USD", "line_items": [ { "quantity": 1, "item": { "id": "" } } ], "buyer": { "email": "buyer@example.com" } }, "meta": { "ucp-agent": { "profile": "https://agent.example/profiles/ucp-demo-agent.json" } } } } } ``` ##### {} Response ```json { "jsonrpc": "2.0", "id": 4, "result": { "content": [ { "type": "text", "text": { "id": "gid://shopify/Checkout/abc123?key=xyz789", "status": "requires_escalation", "messages": [ { "type": "error", "severity": "requires_buyer_input" } ], "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 }, "quantity": 1 } ], "totals": [ { "type": "subtotal", "amount": 8900, "display_text": "Subtotal" }, { "type": "total", "amount": 8900, "display_text": "Total" } ], "expires_at": "2026-02-20T15:17:07Z", "continue_url": "https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789" } } ] } } ``` *** ## Step 4: Finish checkout Update `ucp_demo.js` to import `checkout.js`. The final script will create a checkout using the selected product variant, prompt the buyer to add their email to that checkout, and then append attribution to the final checkout URL (`utm_source` and `ucp_demo_app`) so the merchant can recognize your agent's influence on the completed sale. ## ucp\_demo.js ```javascript import { prompt } from './utils.js'; import { getAccessToken } from './auth.js'; import { searchProducts, displayOffers } from './search.js'; import { selectProduct } from './product.js'; import { createCheckout, updateCheckout, cancelCheckout } from './checkout.js'; async function main() { // 1. Authentication const token = await getAccessToken(); // 2 & 3. Search and select a variant let variant = null; while (!variant) { const searchResults = await searchProducts(token, { include_secondhand: true, min_price: 50, max_price: 200, ships_to: 'US', }); if (!searchResults?.offers?.length) return; displayOffers(searchResults.offers); variant = await selectProduct(token, searchResults.offers); } const { variantId, checkoutUrl } = variant; // 4. Create checkout const checkoutId = await createCheckout(token, variantId, checkoutUrl); // 5. Update checkout — add buyer email const email = await prompt('\n\x1b[1m Enter your email address:\x1b[0m '); const continueUrl = await updateCheckout(token, checkoutId, variantId, email, checkoutUrl); // Append UTM attribution so the referral source is tracked on the merchant's analytics. const attributedUrl = new URL(continueUrl); attributedUrl.searchParams.set('utm_source', 'ucp_demo_app'); console.log(` Refer your buyer to finish checkout at:\n\n ${attributedUrl}\n`); // 6. Cancel checkout await prompt('\x1b[1m Are you finished with the demo? Press Enter to cancel the checkout and exit.\x1b[0m '); await cancelCheckout(token, checkoutId, checkoutUrl); } main().catch(err => console.error('Request failed:', err)); ``` Run the full demo with `node ucp_demo.js`. Click the final link to view the checkout URL on the merchant storefront prepared for referral. When finished, press **Enter** to cancel the checkout and exit the demo. ## Output ── 1. Authentication ───────────────────────── Scopes: read\_global\_api\_catalog\_search Expires: 6:02:46 PM ── 2. Search the Catalog ───────────────────────── Catalog ID: {your\_catalog\_id} Hello! What are you looking for today? \> I need a crewneck sweater ── Results ──────────────────────────────────────── \[1] Organic Cotton Crewneck Sweater | $89.00 | Size: S, M, L | Color: Oatmeal, Forest Green \[2] Recycled Wool Blend Crewneck | $115.00 | Size: S, M, L, XL | Color: Charcoal, Navy \[3] Hemp Cotton Crew Pullover | $72.00 | Size: XS, S, M, L Lookup details on a result \[1-3]: 1 ── 3. Product Details ───────────────────────────── Organic Cotton Crewneck Sweater - M / Oatmeal $89.00 · EcoWear A soft crewneck sweater crafted from 100% organic cotton with a relaxed fit for everyday comfort. · 100% organic cotton for breathable comfort · Relaxed fit with ribbed cuffs and hem · GOTS certified sustainable production · Pre-washed for softness · Classic crewneck design Options: Size: \[1] ○ S \[2] ● M \[3] ○ L Color: \[4] ● Oatmeal \[5] ○ Forest Green Selected: M / Oatmeal \[s] Select this variant \[number] Pick an option \[b] Back to results \> s Checkout: https://ecowear-example.myshopify.com/cart/11111111111:1?\_gsid=example123 ── 4. Create Checkout ───────────────────────────── ID: gid://shopify/Checkout/abc123?key=xyz789 Total: $89.00 ── 5. Update Checkout ───────────────────────────── Enter your email address: buyer\@example.com Refer your buyer to finish checkout at: https://ecowear-example.myshopify.com/cart/c/abc123?key=xyz789\&utm\_source=ucp\_demo\_app Are you finished with the demo? Press Enter to cancel the checkout and exit. ── 6. Cancel Checkout ───────────────────────────── Status: canceled Checkout has been successfully cancelled. Demo complete. Notice that this script leverages another function called `cancelCheckout`. Canceling checkout is optional, but it's included here to clean up the tutorial. See the [section below](#step-5-optional-cancel-checkout) for more details. *** ## Step 5: (Optional) Cancel checkout When a buyer abandons your agentic experience before completing checkout, you can call the [`cancel_checkout`](https://shopify.dev/docs/agents/checkout/mcp#cancel_checkout) tool to cancel the active session. Add a `cancelCheckout` function that calls that tool to the same `checkout.js` script: ##### checkout.js ```javascript const AGENT_PROFILE = 'https://agent.example/profiles/ucp-demo-agent.json'; async function getMcpEndpoint(merchantOrigin) { try { const res = await fetch(`${merchantOrigin}/.well-known/ucp`); if (!res.ok) throw new Error(res.status); const ucp = await res.json(); const shopping = ucp?.ucp?.services?.['dev.ucp.shopping']; const mcp = Array.isArray(shopping) && shopping.find(s => s.transport === 'mcp'); if (mcp?.endpoint) return mcp.endpoint; } catch (_) {} return `${merchantOrigin}/api/ucp/mcp`; } export async function createCheckout(token, variantId, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 3, params: { name: 'create_checkout', arguments: { checkout: { currency: 'USD', line_items: [{ quantity: 1, item: { id: variantId } }] }, meta: { 'ucp-agent': { profile: AGENT_PROFILE } } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`create_checkout failed: ${JSON.stringify(data)}`); const { id, totals } = data.result.content[0].text; const total = totals?.find(t => t.type === 'total')?.amount ?? 0; console.log('\n── 4. Create Checkout ─────────────────────────────\n'); console.log(` ID: ${id}`); console.log(` Total: $${(total / 100).toFixed(2)}`); return id; } export async function updateCheckout(token, checkoutId, variantId, email, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 4, params: { name: 'update_checkout', arguments: { id: checkoutId, checkout: { currency: 'USD', line_items: [{ quantity: 1, item: { id: variantId } }], buyer: { email } }, meta: { 'ucp-agent': { profile: AGENT_PROFILE } } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`update_checkout failed: ${JSON.stringify(data)}`); console.log('\n── 5. Update Checkout ─────────────────────────────\n'); return data.result.content[0].text.continue_url; } export async function cancelCheckout(token, checkoutId, checkoutUrl) { const origin = new URL(checkoutUrl).origin; const mcpEndpoint = await getMcpEndpoint(origin); const res = await fetch(mcpEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', id: 5, params: { name: 'cancel_checkout', arguments: { id: checkoutId, meta: { 'ucp-agent': { profile: AGENT_PROFILE }, 'idempotency-key': crypto.randomUUID() } } } }) }); const data = await res.json(); if (data?.result?.content?.[0]?.text) { data.result.content[0].text = JSON.parse(data.result.content[0].text); } if (!data.result) throw new Error(`cancel_checkout failed: ${JSON.stringify(data)}`); const status = data.result.structuredContent?.status; if (status !== 'canceled') throw new Error(`Unexpected status: ${status}`); console.log('\n── 6. Cancel Checkout ─────────────────────────────\n'); console.log(` Status: ${status}`); console.log(' Checkout has been successfully cancelled.'); console.log(' Demo complete.\n'); } ``` ##### {} MCP input reference ```json { "jsonrpc": "2.0", "method": "tools/call", "id": 5, "params": { "name": "cancel_checkout", "arguments": { "id": "", "meta": { "ucp-agent": { "profile": "https://agent.example/profiles/ucp-demo-agent.json" }, "idempotency-key": "" } } } } ``` ##### {} Response ```json { "jsonrpc": "2.0", "id": 5, "result": { "structuredContent": { "id": "gid://shopify/Checkout/abc123?key=xyz789", "status": "canceled" } } } ``` *** ## 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 checkout works with Checkout MCP and cart permalinks. * [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. ***