Skip to main content
This page is for partners who render OrderProtection inside Shopify checkout through the checkout-widget UI extension and want each checkout to resolve a specific A/B cohort. It builds on the Partner Integration Quickstart — register your OAuth app and create your variants there first, then use this page to wire the extension to them.
The checkout extension calls the path-based partner endpoints (/v2/.../partner/:applicationId), not the header-based public read with cohort headers. The three block settings below are what bind a checkout to your cohort.

Prerequisites

  • An approved OAuth app — see Creating an app. Note your Application.id (looks like cmpn5in6p000484flmfk9ns4d).
  • A partner context created for the merchant’s store — see Quickstart, Step 3.
  • A price set (Step 4) and a widget config (Step 5) created for each cohort, keyed by your variantKey.
  • The checkout-widget extension installed on the store — see below.

How the extension gets installed

The OrderProtection Shopify app is not listed in the Shopify App Store. It’s distributed as a custom app per store: OrderProtection creates the app in its Shopify Partners account, deploys the extensions to it, and generates a custom-distribution install link scoped to that store (or, for Shopify Plus, that organization’s stores). After install, the checkout-widget app block is added to the checkout in the Shopify checkout editor, which is where the block settings below live. For merchant stores this is part of OrderProtection onboarding. For partner development stores, contact your OrderProtection engineering contact to have the app provisioned and the install link sent over — there is no self-serve install path.

Configure the block settings

When the checkout-widget extension is added to a store’s checkout, configure three fields under checkout-widget → Block settings. A partner (e.g. PDQ) sets these at install time.
Shopify checkout editor showing the checkout-widget Block settings: Application ID, Price Set Variant Key, and Widget Config Variant Key, with the OrderProtection — Cohort A widget rendering in the checkout preview at $9.80.
The block settings state selects one of three modes:

Runtime cohort binding (cart attributes)

Static block settings pin every session on a checkout to one cohort. Partners whose experimentation assigns cohorts during the session (for example, after the buyer enters their email and address) bind at runtime instead, using two cart attributes: Values are the same partner-chosen cohort strings used when creating price sets and widget configs through the API (^[a-zA-Z0-9_.:-]{1,64}$) — not ids OrderProtection generates, and not Shopify product variant ids. Your extension writes them with Shopify’s Attributes API (applyAttributeChange, type: "updateAttribute"). Cart attributes are shared checkout state across apps, which is what makes them the cross-extension channel.

Session lifecycle

  1. Setup, once per store: the checkout-widget block is added with Application ID set and both variant-key fields empty. One checkout-widget block — no per-variant blocks, and no checkout-editor changes per test.
  2. Per test, API only: create a price set and a CHECKOUT widget config per variantKey. A control arm is a variantKey whose price set has noOp: true.
  3. Per session: the widget renders nothing while it waits, and makes no cohort pricing or widget-config requests for a surface until that surface’s key exists. When your extension allocates, it writes both attributes — the widget stays hidden until both exist.
  4. The widget fetches that cohort’s pricing and widget config from the partner endpoints below, renders, and applies the cohort’s auto-add behavior. A noOp cohort renders nothing and removes any protection line.
  5. Re-allocation mid-session is supported — the widget refetches and, on toggle-layout cohorts, swaps the protection line (tile-layout cohorts keep the existing line until the buyer interacts) — but it never re-runs the auto-add decision (a buyer’s explicit choice is not overridden). Don’t clear the attributes after allocating, and never express the control arm by not writing them: always allocate explicitly, so “not allocated yet” and “control” stay distinguishable.
  6. Cart attributes become note attributes on the resulting Shopify order (standard Shopify behavior), so the cohort keys are available in order payloads and webhooks for your per-order test attribution.

Rules and edge cases

  • Write both attributes at allocation time. One without the other keeps the widget hidden.
  • Don’t half-fill the block settings. Application ID with only one variant-key field filled leaves the other surface waiting for an attribute that may never come, so the widget stays hidden. Fill both fields (static mode) or neither (runtime mode).
  • Invalid values are treated as absent (client-side validation against the key pattern), falling back to the block-setting value if one exists, otherwise back to the hold.
  • Attributes override filled block settings when both are present. But leave the fields empty on runtime checkouts: a filled field pins a cohort that renders immediately on page load and then swaps when the allocation lands — the hold only engages when the fields are empty. Filled fields are useful for debugging a cohort statically, not for live tests.
  • Missing cohort data falls back visibly, per surface: a key with a widget config but no SHIPPING price set renders the cohort’s appearance with the store’s merchant fallback pricing (the pricing fetch 404s); a key with a price set but no CHECKOUT widget config renders the merchant’s appearance with cohort pricing. If either half looks wrong during setup, check both resources exist for that exact variantKey.
  • Wallet checkouts (Apple Pay, Google Pay) reject extension cart changes — attribute writes and protection line-adds alike — so protection can’t be sold in those sessions by any binding mode; in runtime mode they simply stay unallocated and hidden. Shop Pay is expected to work — it’s the full checkout surface, and buyer details arriving prefilled means allocation can fire immediately — but it hasn’t been verified end-to-end yet.
  • Unallocated sessions stay hidden indefinitely by design. The partner owns allocation reliability; there is no timeout fallback to a default widget.

Impression signal (write-back)

On runtime-allocated sessions, the widget reports back through two more cart attributes so your extension can verify — in-session — that the impression actually landed: Reason values: no_op (the cohort is a noOp control arm — expected behavior), out_of_range (cart subtotal outside the cohort’s price rules), country_excluded (destination or geo exclusion), not_eligible (pickup, B2B, hidden shipping method, nothing shippable), error (the pricing fetch failed). Read it as a tri-state after writing your allocation attributes:
  • _op_rendered present — the impression happened. Treat this as authoritative even if a stale reason briefly coexists (the retraction is best-effort).
  • Reason present, no _op_rendered — the widget ran and declined; the value says why. no_op is your control arm working correctly, not a failure.
  • Neither appears — the widget never ran: missing block, misconfigured checkout, broken extension, or a wallet flow. This is the setup-problem signal, and it works precisely because it’s an absence — a missing renderer can’t self-report.
Two guarantees behind the signal: reasons are only emitted for definitive decisions (a country or subtotal that simply hasn’t resolved yet never produces a false decline), and neither attribute is ever written on merchant checkouts or statically pinned cohorts — they exist only when both cohort keys arrived via runtime attributes. Like the allocation attributes, they persist onto the order.
Channel longevity: applyAttributeChange is deprecated in Shopify’s 2026+ checkout API versions in favor of cart metafields (it remains fully supported on the API versions the extensions currently pin). When pinned versions sunset, this channel migrates to a shared cart metafield with the same contract — coordinate the swap with OrderProtection before bumping your extension’s api_version.

What the extension fetches

Once configured, on each checkout render the extension calls the partner-scoped endpoints below. Both are public (no OAuth token) and resolve the cohort from the path applicationId + key query param.

Pricing — GET /v2/quote/insurance/partner/:applicationId

The response is the same shaped quote payload as the public widget read, computed from the cohort’s SHIPPING price set. Responses are cached for ~1 hour per (store_url, country, applicationId, key) (plus the A/B session token when one is present). Errors
  • 400 Bad Requestkey is missing, or applicationId/key don’t match the partner key format.
  • 404 Not Found — the store isn’t found, the app isn’t installed, or the cohort has no SHIPPING pricing rules.

Widget config — GET /v2/widget/config/partner/:applicationId

config and customizationValues always hold the same value; customizationValues is the legacy column name. Responses are cached for ~1 hour per (store_url, applicationId, key, type). Errors
  • 400 Bad Requestkey is missing, or applicationId/key don’t match the partner key format.
  • 404 Not Found — the store isn’t found, the app has no active context on that store, or no widget config exists for that (variantKey, type).
These endpoints throw 400/404 rather than silently falling back. Treat any non-200 as “no partner cohort for this checkout” and render the store’s default widget — a partner cohort must never break checkout.

Default vs. cohort render

The explainer below walks the whole flow — authenticate, create variants, configure the extension, and render per shopper:
With no partner keys set, the extension renders the store’s default widget:
Default Order Protection checkout widget: shield icon, 'Order Protection — Protection for Damage, Loss, Theft & More!', priced at $10.00, toggled on.
With Application ID + variant key test-a, the same checkout resolves the cohort’s price set and widget config — for example OrderProtection — Cohort A at $9.80 with flat-rate protection, as shown in the checkout preview above.

Next steps

Partner Integration Quickstart

Register your app and create your cohorts’ pricing and widget configs — the setup this page depends on.

Partner endpoints reference

Full schema for every /v2 partner endpoint, including the contexts, pricing, and widget-config writes.

Creating an app

Register an OAuth app and capture your Application.id, client ID, and secret.

Scopes

Scopes your app needs to create contexts, price sets, and widget configs.