Embedded Checkout Protocol
The Embedded Checkout Protocol (ECP) enables your application to embed a merchant's checkout UI, receive events as the buyer interacts with checkout, and delegate key actions like address and payment selection to your native experience.
Anchor to How it worksHow it works
ECP is a technical bridge that defines how your host application communicates with the embedded checkout:
- Load the checkout in a web view using the
continue_urlfrom the UCP checkout response — returned bycreate_checkoutand later checkout calls wheneverstatusisrequires_escalation— with ECP parameters appended. - Receive events as the buyer progresses through checkout via JSON-RPC 2.0 messages.
- Delegate actions like payment instrument selection and address changes to your native UI.
- Complete the purchase when checkout signals completion.
ECP is inspired by the W3C Payment Request API.
For the full protocol specification, see the ECP specification.
Anchor to Construct the embedded URLConstruct the embedded URL
When you receive a requires_escalation status from a UCP checkout response (via REST, MCP, or other transport), add ECP query parameters to the continue_url to enable embedded checkout.
See the UCP spec for more details.
Anchor to URL ParametersURL Parameters
The UCP version for this session. Must match the UCP version from the checkout response, for example 2026-08-25.
Authentication token in merchant-defined format. Optional if not required by the merchant.
Comma-delimited list of delegations the host wants to handle natively. See Delegations.
Embedded URL construction
Example URL
Anchor to Web view integrationWeb view integration
Load the embedded checkout URL in a web view. Communication between your Host and the Embedded Checkout happens through two JavaScript globals:
EmbeddedCheckoutProtocolConsumer: Created by your app. Checkout callspostMessage()to send events to your application.EmbeddedCheckoutProtocol: Created by Shopify Checkout. Your app callspostMessage()to respond to events.
All messages use JSON-RPC 2.0 format. Messages with an id field are requests that require a response. Messages without id are notifications that don't expect a response.
iOS
Android
Anchor to DelegationsDelegations
Delegations declare which operations your Host handles natively instead of in the Embedded Checkout UI. Each delegation maps to a corresponding _request message:
ec_delegate value | Corresponding message | Description |
|---|---|---|
fulfillment.address_change | ec.fulfillment.address_change_request | Host presents address selection UI |
payment.credential | ec.payment.credential_request | Host authenticates and provides payment credentials |
payment.instruments_change | ec.payment.instruments_change_request | Host presents payment method selection UI |
window.open | ec.window.open_request | Host validates and opens external links (policies, help pages, payment app handoffs) itself instead of the web view navigating away |
When you accept a delegation:
- Embedded checkout fires the
_requestmessage when that action is triggered, and waits for your response. - Your host must respond to every request with a
resultcarrying theucpenvelope (statusand the negotiatedversion) — including when the buyer cancels, which is aresultwithucp.status: "error"and amessagesentry, not a JSON-RPCerror. - Embedded checkout doesn't show its own UI for delegated actions.
Anchor to Event referenceEvent reference
All events use JSON-RPC 2.0 format. Events are either:
- Requests (include
id): Require a response from your host. - Notifications (no
id): Informational only, no response expected.
This reference covers the events a host has to handle to run a checkout. The ECP specification lists the full set, including the informational change notifications (ec.buyer.change, ec.fulfillment.change, ec.line_items.change, ec.messages.change, ec.payment.change, ec.totals.change), ec.auth, ec.error, and ec.window.open_request.
Anchor to [object Object]ec.ready
ec.readyBroadcast when Embedded Checkout is ready. This initializes the communication channel and indicates which delegations were accepted. Checkout doesn't render until your host responds, so answer it promptly — at minimum with the ucp envelope.
Direction: Embedded Checkout → Host
Type: Request (requires response)
Payload:
delegate: Array of accepted delegation identifiers (subset of what you requested viaec_delegate)
Response:
ucp(required): The UCP envelope,{ "status": "success", "version": "2026-08-25" }checkout(optional): Additional display-only state, such aspayment.instrumentsupgrade(optional): AMessagePortto upgrade the communication channel
ec.ready
Request
{
"jsonrpc": "2.0",
"id": "ready_1",
"method": "ec.ready",
"params": {
"delegate": [
"fulfillment.address_change",
"payment.instruments_change",
"payment.credential"
]
}
}{} Response
{
"jsonrpc": "2.0",
"id": "ready_1",
"result": {
"ucp": { "status": "success", "version": "2026-08-25" },
"checkout": {
"payment": {
"instruments": [
{
"id": "pm_1234567890abc",
"handler_id": "gpay",
"type": "card",
"brand": "visa",
"last_digits": "4242"
}
],
"selected_instrument_id": "pm_1234567890abc"
}
}
}
}Anchor to [object Object]ec.start
ec.startSignals that checkout is visible and ready for buyer interaction.
Direction: Embedded Checkout → Host
Type: Notification (no response)
Payload:
checkout: The current state of the checkout
ec.start
Anchor to [object Object]ec.fulfillment.address_change_request
ec.fulfillment.address_change_requestRequests the host to present address selection UI for shipping.
Direction: Embedded Checkout → Host
Type: Request (requires response)
Payload:
checkout: Current checkout state including fulfillment details
Response:
checkout.fulfillment.methods: Updated methods withselected_destination_idanddestinations
ec.fulfillment.address_change_request
Request
{
"jsonrpc": "2.0",
"id": "address_1",
"method": "ec.fulfillment.address_change_request",
"params": {
"checkout": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"fulfillment": {
"methods": [...]
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": "address_1",
"result": {
"ucp": { "status": "success", "version": "2026-08-25" },
"checkout": {
"fulfillment": {
"methods": [
{
"id": "method_1",
"type": "shipping",
"selected_destination_id": "gid://host/Address/789",
"destinations": [
{
"id": "gid://host/Address/789",
"street_address": "123 Main Street",
"address_locality": "Brooklyn",
"address_region": "NY",
"postal_code": "11201",
"address_country": "US",
"first_name": "Jane",
"last_name": "Smith"
}
]
}
]
}
}
}
}Anchor to [object Object]ec.payment.instruments_change_request
ec.payment.instruments_change_requestRequests the Host to present payment instrument selection UI.
Direction: Embedded Checkout → Host
Type: Request (requires response)
Payload:
checkout: Current checkout state including payment details
Response:
checkout.payment.instruments: Updated array of payment instrumentscheckout.payment.selected_instrument_id: ID of the selected instrument
ec.payment.instruments_change_request
Request
{
"jsonrpc": "2.0",
"id": "instruments_1",
"method": "ec.payment.instruments_change_request",
"params": {
"checkout": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"payment": {...}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": "instruments_1",
"result": {
"ucp": { "status": "success", "version": "2026-08-25" },
"checkout": {
"payment": {
"instruments": [
{
"id": "pm_1234567890abc",
"handler_id": "gpay",
"type": "card",
"brand": "visa",
"last_digits": "4242",
"billing_address": {
"street_address": "123 Main Street",
"address_locality": "Charleston",
"address_region": "SC",
"postal_code": "29401",
"address_country": "US"
}
}
],
"selected_instrument_id": "pm_1234567890abc"
}
}
}
}Anchor to [object Object]ec.payment.credential_request
ec.payment.credential_requestRequests a payment credential for the selected instrument during checkout submission. This typically involves user authentication (biometric/PIN).
Direction: Embedded Checkout → Host
Type: Request (requires response)
Payload:
checkout: Current checkout state including payment details
Response:
checkout.payment.instruments: Updated instrument array withcredentialfield added to the selected instrument
ec.payment.credential_request
Request
{
"jsonrpc": "2.0",
"id": "credential_1",
"method": "ec.payment.credential_request",
"params": {
"checkout": {
"id": "gid://shopify/Checkout/abc123?key=xyz789",
"payment": {
"selected_instrument_id": "pm_1234567890abc"
}
}
}
}{} Response
{
"jsonrpc": "2.0",
"id": "credential_1",
"result": {
"ucp": { "status": "success", "version": "2026-08-25" },
"checkout": {
"payment": {
"instruments": [
{
"id": "pm_1234567890abc",
"handler_id": "gpay",
"type": "card",
"brand": "visa",
"last_digits": "4242",
"credential": {
"type": "payment_gateway",
"token": "examplePaymentMethodToken"
}
}
]
}
}
}
}Anchor to [object Object]ec.complete
ec.completeIndicates successful checkout completion.
Direction: Embedded Checkout → Host
Type: Notification (no response)
Payload:
checkout: Final checkout state, including the resultingorder
ec.complete
Anchor to [object Object]ec.window.open_request
ec.window.open_requestSent when checkout wants to open an external link — a policy page, help content, or a payment app handoff — and you accepted the window.open delegation. The request doesn't guarantee anything about the URL: validate it yourself before opening it — require https:, and ideally allow only origins you expect — then open it in your own browser surface and respond with a UCP envelope. Reject anything else, including custom-scheme links.
Direction: Embedded Checkout → Host
Type: Request (requires response)
Payload:
url: The URL checkout wants opened. Treat it as untrusted input
Response:
ucp:{ "status": "success", "version": "2026-08-25" }when you opened it, or{ "status": "error", "version": "2026-08-25" }plus amessagesentry withcode: "window_open_rejected_error"when you didn't
Anchor to Error responsesError responses
When the buyer cancels a delegated action or your host can't fulfil it, respond with a result whose ucp.status is error and describe why in messages. Reserve JSON-RPC error objects for malformed requests.