---
title: "Support context for retailers | Snowplow Signals"
description: "Snowplow Signals tells your support desk and chatbot the order, the cart and the pages a shopper viewed before they asked, so the reply starts from there."
source: https://signals.snowplow.io/solutions/retail/support
---

[Retail and e-commerce](https://signals.snowplow.io/solutions/retail) / Support context 

# Support that knows what the shopper was doing. 

Signals tells your support desk and chatbot which pages this shopper viewed, what failed and what they searched for before they opened chat, so the first reply can start from there instead of asking.

[Start free](https://signals.snowplow.io/start) [Talk to us](https://signals.snowplow.io/start?intent=talk) 

Shorter handle times

## Cut handle time by starting the reply from what the shopper did before asking. 

Handle time moves when the agent or bot does not have to ask what happened. Signals gives your support desk the pages this shopper viewed, the error they hit, the search they ran and how often they have been in touch, current to the last event. Your desk or bot decides what to say with it.

Four ways retailers use it

01 A shopper refreshing order tracking The tracking page, four times in ten minutes. Open with the order status. 02 A shopper whose payment failed Card declined at checkout, chat opened a minute later. Skip the screenshot. 03 A shopper reading the returns policy Searched returns, read the policy, now in chat. Start the return. 04 A customer who has been in touch twice this month Third contact this month, same order. Route to a person, with the history. 

[Read the docs →](https://docs.snowplow.io/docs/signals/introduction/) 

### Open the chat with the order status

A shopper has reloaded the tracking page for one order four times in ten minutes, and now they open chat. The bot's usual opener asks how it can help, and the shopper types out what the tracking page showed them, then waits while the bot looks the order up. The refreshing was the question. The chat can open with the answer and the order number in it.

What Signals knows

`tracking_views_session` 

Times the order tracking page was opened this visit

`last_tracking_url_session` 

The tracking page they opened last, which carries the order number

What your support desk does

Opens the chat with the status of that order and the carrier's latest scan when the shopper has reloaded the tracking page three or more times this visit. The order number comes from the page they were on and the status comes from your order system.

Define it · PythonUse it · TypeScript

support.ts · 15 lines Copy Copied 

```
from snowplow_signals import (
    Attribute, AtomicProperty, Criteria, Criterion, Event, StreamAttributeGroup, domain_sessionid,
)

page_view = Event(name="page_view", vendor="com.snowplowanalytics.snowplow", version="1-0-0")
on_tracking = Criteria(all=[
    Criterion.like(AtomicProperty(name="page_url"), "%/orders/%/track%"),
])

tracking_views_session = Attribute(
    name="tracking_views_session",
    type="int32",
    events=[page_view],
    aggregation="counter",
    criteria=on_tracking,
    default_value=0,
)

last_tracking_url_session = Attribute(
    name="last_tracking_url_session",
    type="string",
    events=[page_view],
    aggregation="last",
    property=AtomicProperty(name="page_url"),
    criteria=on_tracking,
)

# Keyed on the visit, so the chat widget asks about this session the moment it opens.
shop_support_tracking = StreamAttributeGroup(
    name="shop_support_tracking",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[tracking_views_session, last_tracking_url_session],
)
```

```
// In the handler that runs when the chat widget opens
const shopper = await signals.getServiceAttributes({
  name: "shop_support_tracking",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { tracking_views_session: 4, last_tracking_url_session: "https://shop.example/orders/48213/track" }

if (shopper.tracking_views_session >= 3) {
  const orderId = orderIdFromUrl(shopper.last_tracking_url_session);
  bot.open({
    intent: "order_status",
    order: await orders.get(orderId),
  });
}
```

### Show the agent the error before the shopper describes it

A shopper reached the payment step, the card was declined, and a minute later they are in chat. The agent sees a new conversation with no context, so the first exchange is a request for a screenshot and a description of what the shopper clicked. The error code and the step it happened on were recorded when it happened. They can be on the ticket before the agent says hello.

What Signals knows

`last_checkout_error` 

The error code from the most recent checkout failure this visit

`checkout_errors_session` 

Checkout failures this visit

`minutes_since_error` 

Minutes since the last checkout failure

What your support desk does

Attaches the error code, the checkout step and how long ago it happened to the conversation before the agent opens it, and pulls up the macro your team keeps for that code. The agent decides what to say.

Define it · PythonUse it · TypeScript

support.ts · 15 lines Copy Copied 

```
from snowplow_signals import (
    Attribute, Event, EventProperty, StreamAttributeGroup, domain_sessionid,
)

checkout_error = Event(name="checkout_error", vendor="com.example", version="1-0-0")

last_checkout_error = Attribute(
    name="last_checkout_error",
    type="string",
    events=[checkout_error],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="checkout_error", major_version=1, path="code"),
)

checkout_errors_session = Attribute(
    name="checkout_errors_session",
    type="int32",
    events=[checkout_error],
    aggregation="counter",
    default_value=0,
)

minutes_since_error = Attribute(
    name="minutes_since_error",
    type="double",
    events=[checkout_error],
    aggregation="time_since_last",
    time_unit="min",
)

shop_support_checkout = StreamAttributeGroup(
    name="shop_support_checkout",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[last_checkout_error, checkout_errors_session, minutes_since_error],
)
```

```
// In the handler that creates the ticket when a chat starts
const shopper = await signals.getServiceAttributes({
  name: "shop_support_checkout",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { last_checkout_error: "card_declined", checkout_errors_session: 1, minutes_since_error: 1.3 }

if (shopper.checkout_errors_session > 0) {
  desk.annotate({
    ticket,
    note: `Checkout failed with ${shopper.last_checkout_error}, ${Math.round(shopper.minutes_since_error)} min ago`,
    macro: macros.forError(shopper.last_checkout_error),
  });
}
```

### Start the return instead of asking what the question is

A shopper searched for returns, opened the policy page, read it and then opened chat. The bot's opening question asks what they need, and the shopper explains that they want to return something, which the last two pages said. A chat that opens on the return flow, with the recent orders listed, saves that exchange and the one after it.

What Signals knows

`returns_pages_session` 

Returns and refund pages opened this visit

`return_searches_session` 

Searches this visit that mention returns

What your support desk does

Opens the chat on the return flow, with the shopper's recent orders listed, when they have read a returns page or searched for returns this visit. The order list comes from your order system.

Define it · PythonUse it · TypeScript

support.ts · 14 lines Copy Copied 

```
from snowplow_signals import (
    Attribute, AtomicProperty, Criteria, Criterion, Event, EventProperty, StreamAttributeGroup, domain_sessionid,
)

page_view = Event(name="page_view", vendor="com.snowplowanalytics.snowplow", version="1-0-0")
search = Event(name="search", vendor="com.example", version="1-0-0")

returns_pages_session = Attribute(
    name="returns_pages_session",
    type="int32",
    events=[page_view],
    aggregation="counter",
    criteria=Criteria(any=[
        Criterion.like(AtomicProperty(name="page_url"), "%/help/returns%"),
        Criterion.like(AtomicProperty(name="page_url"), "%/help/refunds%"),
    ]),
    default_value=0,
)

return_searches_session = Attribute(
    name="return_searches_session",
    type="int32",
    events=[search],
    aggregation="counter",
    criteria=Criteria(all=[
        Criterion.like(EventProperty(vendor="com.example", name="search", major_version=1, path="query"), "%return%"),
    ]),
    default_value=0,
)

shop_support_returns = StreamAttributeGroup(
    name="shop_support_returns",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[returns_pages_session, return_searches_session],
)
```

```
// In the handler that runs when the chat widget opens
const shopper = await signals.getServiceAttributes({
  name: "shop_support_returns",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { returns_pages_session: 2, return_searches_session: 1 }

if (shopper.returns_pages_session > 0 || shopper.return_searches_session > 0) {
  bot.open({
    flow: "start_return",
    orders: await orders.recentForSession(sessionId),
  });
}
```

### Route a repeat contact to a person who can see the history

A logged-in customer opens chat for the third time this month, and the first two were about the same order. The bot greets them as a new conversation and starts the same script, and the agent who eventually picks it up reads the earlier threads while the customer waits. The third contact about one order is the moment for a person, with both earlier conversations open before they say hello.

What Signals knows

`support_contacts_30d` 

Support conversations in the last 30 days

`contact_orders_30d` 

The orders those conversations were about

`last_contact_topic_30d` 

What the most recent conversation was about

What your support desk does

Skips the bot and routes to a person when a customer has contacted support twice in the last 30 days about the same order, and opens the earlier conversations beside the new one. Your routing rules pick the queue.

Define it · PythonUse it · TypeScript

support.ts · 15 lines Copy Copied 

```
from datetime import timedelta
from snowplow_signals import Attribute, Event, EventProperty, StreamAttributeGroup, user_id

support_contact = Event(name="support_contact", vendor="com.example", version="1-0-0")

support_contacts_30d = Attribute(
    name="support_contacts_30d",
    type="int32",
    events=[support_contact],
    aggregation="counter",
    period=timedelta(days=30),
    default_value=0,
)

contact_orders_30d = Attribute(
    name="contact_orders_30d",
    type="string_list",
    events=[support_contact],
    aggregation="unique_list",
    property=EventProperty(vendor="com.example", name="support_contact", major_version=1, path="order_id"),
    period=timedelta(days=30),
)

last_contact_topic_30d = Attribute(
    name="last_contact_topic_30d",
    type="string",
    events=[support_contact],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="support_contact", major_version=1, path="topic"),
    period=timedelta(days=30),
)

# The help desk sends support_contact from the server when a conversation closes, so the user id is the key.
shop_support_repeat = StreamAttributeGroup(
    name="shop_support_repeat",
    version=1,
    attribute_key=user_id,
    owner="you@example.com",
    attributes=[support_contacts_30d, contact_orders_30d, last_contact_topic_30d],
)
```

```
// In the handler that routes a new chat from a logged-in customer
const customer = await signals.getServiceAttributes({
  name: "shop_support_repeat",
  attribute_key: "user_id",
  identifier: userId,
});
// { support_contacts_30d: 2, contact_orders_30d: ["48213"], last_contact_topic_30d: "missing_item" }

if (customer.support_contacts_30d >= 2 && customer.contact_orders_30d.length === 1) {
  desk.route({
    to: "agent",
    skipBot: true,
    context: await desk.conversations({ userId, order: customer.contact_orders_30d[0] }),
  });
}
```

## Try it on your own site. 

Add the SDK, define one thing you want to know about a visitor, and watch it update while you click around. The free tier runs on the real engine: no card, no sales call.

[Start free](https://signals.snowplow.io/start) [Talk to an engineer](https://signals.snowplow.io/start?intent=engineer)
