Used Newie, our AI search tool in the docs? Take a 2-minute survey to rate your experience!

Adding webview customizations to the cart

Prev Next

Webviews embedded in the cart of Associate App receive a live snapshot of the cart from the app and can post messages back to trigger native app actions. Use them to surface information next to the items the associate is selling — for example a loyalty enrollment prompt, available coupons from an external promotion engine, warranty validation of scanned serial numbers, or brand-specific purchase advisories.

Available from Associate App v1.83.0

Cart webview slots and the meta.allowExpansion option are available from Associate App version 1.83.0. The cart.customer object is available from version 1.85.0.

Further reading

Slots

Two slots are available in the cart. Configure which slot the webview appears via the Customization Configuration API.

Slot ID

Position

cart_top

Top of the cart, below the customer and address cards, above the item list

cart_footer

Bottom of the cart, above the totals summary

One slot can host multiple customizations, and they are rendered in configuration order.

Note

Cart slots only render when the cart contains at least one item. An empty cart shows the standard empty-cart screen without any customizations. When the cart has items, an enabled inline webview always renders and reserves its configured meta.height. Design your page to display a sensible empty or collapsed state when it has nothing to display.

Configuring a cart webview

Send a PUT request to the Customization Configuration API. The customization_id in the path is provided by you, to generate a UUID.

{
  "enabled": true,
  "type": "webview",
  "slot": "cart_top",
  "label": "Loyalty",
  "title": "Loyalty",
  "caption": "Open",
  "url": "https://your-webview-url.com/loyalty",
  "meta": {
    "inline": true,
    "scrollEnabled": false,
    "height": 150
  }
}

Display modes

Webviews can be configured as:

  • Full-screen (default) — Opens as a new screen when the associate taps a button in the cart

  • Inline — Rendered directly within the cart. Set meta.inline: true in the configuration, along with meta.height (pixels), and optionally meta.scrollEnabled, meta.scrollXEnabled, meta.scrollYEnabled.

Available from Associate App v1.83.0

An inline webview displays a header with the configured title and a button (labeled with caption) that opens the same page in full-screen mode. To hide that button and keep the webview inline-only, set meta.allowExpansion: false. The allowExpansion property defaults to true. Only an explicit false value hides the button.

For the full display mode reference and code examples, see Adding webview customizations to the product detail page.

{
  "meta": {
    "inline": true,
    "height": 180,
    "allowExpansion": false
  }
}

Delivering context

When the app renders the webview, it adds a base64-encoded JSON payload to the URL as a fragment (#):

https://your-webview-url.com/page#<base64-encoded-JSON>

Decode and parse it on the client side:

const raw = decodeURIComponent(window.location.hash.slice(1))
const context = JSON.parse(atob(raw))

The context object has the following payload:

{
  contextProps: {
    formData: {
      // --- Cart snapshot ---
      cart: {
        items: Array<{
          productId: string
          itemIds: string[]             // Cart line item IDs — use with the Cart API
          quantity: number
          unitPrice: number
          itemDiscount: number
          externalIdentifiers: Array<{
            serialNumber: string | null
            epc: string | null
          }>
        }>
        grandTotal: number
        subtotal: number
        discountTotal: number
        taxTotal: number
        remainingAmount: number
        currency: string | null
        coupons: string[]               // Applied coupon codes
        customerId: string | null       // Customer assigned to the cart, if any

        // Present only when a customer is attached and their profile has loaded
        customer?: {
          id: string                    // Same value as customerId
          displayId: string | null      // Human-readable customer number, e.g. "C-001"
          extendedAttributes: {         // Profile extended attributes, flattened
            [name: string]: string      // e.g. { "loyalty_id": "L-123" }
          }
        }
      }

      // --- Session fields (always present) ---
      associateId: string               // ID of the logged-in associate
      storeId: string                   // ID of the current store
      cartId: string                    // Active cart ID
    }
  }

  auth: {
    accessToken: string                 // NewStore access token — use to call NewStore APIs
  }

  externalIdentities: Array<{
    identityProvider: string
    identity: string
  }>

  theme: object                         // App color, spacing, and typography tokens
  dimensions: { top: number, bottom: number, left: number, right: number }
  enums: { DocumentType: object }       // Print document type constants
  securityToken: string                 // One-time token generated per webview session
}

A page that only needs the customer's identity should use customerId. It is available from the first render and is never absent when a customer is on the cart. The customer object is for pages that need the profile's extended attributes, and is only present once the customer's profile has loaded.

A customer has two identifiers. id is the internal identifier NewStore APIs expect, and it is the same value as customerId on the cart. displayId is the short, human-readable customer number the app shows to associates, for example C-001. Use id for API calls and displayId when you want to show the customer their own reference.

extendedAttributes contains the customer profile's extended attributes as a flat map of name to value. Retailers typically use these to carry integration keys such as a loyalty membership number. Reading them here saves your page a separate call to the customer profile API.

Privacy

The payload contains no customer contact details: no name, email address, phone number, or postal address. The customer object is limited to identifiers and the profile's extended attributes. If you need anything beyond that, use customer.id together with auth.accessToken to fetch the customer profile from NewStore APIs.

Timing

customer is populated from the customer profile, which the app may still be loading when your page first renders. When that happens, the object is absent from the initial fragment and arrives moments later in a hashchange update. Drive your rendering from the newest fragment rather than reading the hash only once. customerId is available immediately in both cases.

Reacting to cart changes

The app re-serializes the payload whenever the cart changes, such as when the associate scans another item or applies a discount, and when the attached customer's profile finishes loading. Read the fragment at load time and listen for the hashchange event:

function render() {
  const context = getWebViewContext()
  if (!context) return
  const { cart } = context.contextProps.formData
  // update your UI from the fresh snapshot
}

window.addEventListener('hashchange', render)
render()

Whether an update arrives as a soft navigation or a page reload is platform-dependent. Handle both by keeping the render() property idempotent.

Making a call to NewStore APIs

Use auth.accessToken to make authenticated calls to NewStore APIs. For example, attaching a warranty add-on to an item already in the cart via the Cart Line Item API:

const { auth, contextProps: { formData } } = context
const baseUrl = `https://<tenant>.p.newstore.net/v0/d`

await fetch(`${baseUrl}/checkout/carts/${formData.cartId}/items/${formData.cart.items[0].itemIds[0]}/add-ons`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${auth.accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    product_id: '<warranty-product-id>',
    fulfillment: 'IN_STORE_HANDOVER',
    price: { source: 'INTERNAL', source_id: '<pricebook-id>' },
  }),
})

After the webview modifies the cart, trigger Associate App to reload the cart view so the associate sees the change immediately:

window.open(`com.newstore.associate-one://cart/load?cartId=${formData.cartId}`)

Posting messages back to the app

The webview can trigger native app actions via window.ReactNativeWebView.postMessage. The same messages are available as on the product detail page. See the full postMessage reference:

// Close a full-screen webview
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'close' }))

// Open an https:// URL in the system browser
window.ReactNativeWebView.postMessage(JSON.stringify({
  type: 'openExternalUrl',
  message: { url: 'https://...' }
}))

// Print a document
window.ReactNativeWebView.postMessage(JSON.stringify({
  type: 'print',
  message: { resources: [{ documentType: context.enums.DocumentType.salesReceipt, url: 'https://...' }] }
}))

What cart webviews cannot do

A cart webview is informational and additive: it can display content and call NewStore APIs, but it cannot block the checkout flow or prevent the associate from completing a sale. If your use case requires enforcement, such as for legally binding quantity restrictions per brand, you must implement it in backend order validation in addition to the in-cart advisory.

Inline webviews require network connectivity and do not render meaningful content when the device is offline.

Example: loyalty enrollment banner

A minimal cart_top page that shows how many points the current purchase would earn:

function getWebViewContext() {
  const hash = window.location.hash.slice(1)
  if (!hash) return null
  try {
    return JSON.parse(atob(decodeURIComponent(hash)))
  } catch {
    return null
  }
}

function render() {
  const context = getWebViewContext()
  if (!context) return

  const { cart } = context.contextProps.formData
  const points = Math.round((cart.grandTotal || 0) * 10)
  const loyaltyId = cart.customer?.extendedAttributes?.loyalty_id

  document.getElementById('root').textContent = loyaltyId
    ? `Member ${loyaltyId} earns ${points} points on this purchase.`
    : `Add the customer to the cart to earn ${points} points on this purchase.`
}

window.addEventListener('hashchange', render)
render()

The optional chaining on cart.customer keeps the page safe during the window before the customer's profile has loaded.