---
title: "Support context for travel brands | Snowplow Signals"
description: "Snowplow Signals tells your assistant and support desk the route, dates, fare and error from a traveler's session, so the first reply is the fix."
source: https://signals.snowplow.io/solutions/travel/support
---

[Travel](https://signals.snowplow.io/solutions/travel) / Support context 

# Support that knows which trip the traveler means. 

Signals tells your assistant and support desk which route and dates this traveler was booking, which fare they picked and what failed before they opened chat, so the first reply can start from the trip instead of asking for it.

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

Shorter handle times

## Cut handle time by opening the chat on the trip the traveler was booking. 

Handle time moves when the assistant or agent does not have to ask which trip, which dates and what went wrong. Signals gives your assistant and support desk the route and dates from this session, the fare the traveler picked, the error they hit and the pages they read before asking, current to the last event. Your assistant or agent decides what to say with it.

Four ways travel brands use it

01 A traveler whose payment failed Card refused on the payment step, chat open a minute later. Start with the fix. 02 A traveler who searched one route six times Six searches, same route, no fare picked. Open with the route. 03 A member who could not change a booking Two failed date changes on manage booking, now in chat. Show the agent the change. 04 A traveler asking about bags Bag policy read twice, basic fare picked. Answer for that fare. 

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

### Open the chat on the booking that failed

A traveler got through the booking flow for Denver to Cancun, two passengers, next month, and the card was refused on the payment step. A minute later they are in chat. The assistant's first question asks which booking and what happened, and the traveler types out the route, the dates and the message they saw, then waits while it is looked up. The error code, the route, the dates and the fare were recorded when the payment failed. The chat can open with the booking on screen and the reason the card was refused, and move straight to retrying the payment.

What Signals knows

`last_payment_error` 

The error code from the most recent payment failure this visit

`route_session` 

The route of the booking they were on last this visit

`travel_dates_session` 

The departure date of that booking

`fare_family_session` 

The fare family they picked, such as basic or flex

What your assistant and support desk do

Opens the chat with the route, dates and fare on screen and the payment error named, and offers the payment retry as the first move when a payment has failed this visit. The retry runs through your payment provider and the assistant decides how to word it.

Define it · PythonUse it · TypeScript

support.ts · 15 lines Copy Copied 

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

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

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

route_session = Attribute(
    name="route_session",
    type="string",
    events=[booking_step],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="booking_step", major_version=1, path="route"),
)

travel_dates_session = Attribute(
    name="travel_dates_session",
    type="string",
    events=[booking_step],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="booking_step", major_version=1, path="depart_date"),
)

fare_family_session = Attribute(
    name="fare_family_session",
    type="string",
    events=[booking_step],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="booking_step", major_version=1, path="fare_family"),
)

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

```
// In the handler that runs when the chat widget opens
const traveler = await signals.getServiceAttributes({
  name: "trip_support_payment",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { last_payment_error: "card_refused", route_session: "DEN-CUN", travel_dates_session: "2026-11-14", fare_family_session: "basic" }

if (traveler.last_payment_error) {
  assistant.open({
    intent: "retry_payment",
    booking: { route: traveler.route_session, departs: traveler.travel_dates_session, fare: traveler.fare_family_session },
    error: traveler.last_payment_error,
  });
}
```

### Open with the route they keep searching

A traveler has searched Chicago to Dublin six times in twenty minutes, moving the dates and the airports, and has not picked a fare. Now they open chat. The usual opener asks how it can help, and the traveler explains the route, the dates they have tried and what they are hoping for, which is the same thing the last six searches said. An agent who opens with that route and those dates skips the explanation and gets to the question, which is usually whether a cheaper day exists.

What Signals knows

`searches_session` 

Flight searches this visit

`top_route_session` 

The route they searched most this visit

`dates_tried_session` 

The departure dates they searched this visit

What your assistant and support desk do

Puts the route and the dates tried into the agent's opener, and skips the question about what the traveler is looking for, when a traveler has run several searches on one route this visit without picking a fare. The agent decides which fares to suggest.

Define it · PythonUse it · TypeScript

support.ts · 14 lines Copy Copied 

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

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

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

top_route_session = Attribute(
    name="top_route_session",
    type="string",
    events=[flight_search],
    aggregation="most_frequent",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="route"),
)

dates_tried_session = Attribute(
    name="dates_tried_session",
    type="string_list",
    events=[flight_search],
    aggregation="unique_list",
    property=EventProperty(vendor="com.example", name="flight_search", major_version=1, path="depart_date"),
)

trip_support_route = StreamAttributeGroup(
    name="trip_support_route",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[searches_session, top_route_session, dates_tried_session],
)
```

```
// In the handler that builds the agent's opener when a chat starts
const traveler = await signals.getServiceAttributes({
  name: "trip_support_route",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { searches_session: 6, top_route_session: "ORD-DUB", dates_tried_session: ["2026-11-03", "2026-11-05", "2026-11-10"] }

if (traveler.searches_session >= 4) {
  desk.setOpener({
    ticket,
    text: `Looks like you are pricing ${traveler.top_route_session} around ${traveler.dates_tried_session.join(", ")}. Want me to check those dates?`,
  });
}
```

### Show the agent the change the member tried to make

A loyalty member is on the manage-booking page trying to move an outbound flight two days later. The change has failed twice, and they open chat from that page. The agent picks up a new conversation with the member's name on it and asks which booking and what they would like to do, and the member explains the booking reference, the new date and the error, which was recorded both times it happened. The agent can have the booking open and the failed change in front of them before the member finishes typing hello.

What Signals knows

`change_attempts_1h` 

Booking changes that failed in the last hour

`last_change_booking` 

The booking reference of the change that failed last

`last_change_reason` 

Why that change failed, as your booking engine reported it

What your assistant and support desk do

Routes the chat to a person with the booking open and the failed change attached, including the new date and the reason your booking engine gave, when a member has had two or more changes fail in the last hour. Your routing rules pick the queue and the agent makes the change.

Define it · PythonUse it · TypeScript

support.ts · 16 lines Copy Copied 

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

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

change_attempts_1h = Attribute(
    name="change_attempts_1h",
    type="int32",
    events=[booking_change_failed],
    aggregation="counter",
    period=timedelta(hours=1),
    default_value=0,
)

last_change_booking = Attribute(
    name="last_change_booking",
    type="string",
    events=[booking_change_failed],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="booking_change_failed", major_version=1, path="booking_ref"),
)

last_change_reason = Attribute(
    name="last_change_reason",
    type="string",
    events=[booking_change_failed],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="booking_change_failed", major_version=1, path="reason"),
)

# The booking engine sends booking_change_failed from the server, and the member is logged in, so the user id is the key.
trip_support_change = StreamAttributeGroup(
    name="trip_support_change",
    version=1,
    attribute_key=user_id,
    owner="you@example.com",
    attributes=[change_attempts_1h, last_change_booking, last_change_reason],
)
```

```
// In the handler that routes a new chat from a logged-in member
const member = await signals.getServiceAttributes({
  name: "trip_support_change",
  attribute_key: "user_id",
  identifier: userId,
});
// { change_attempts_1h: 2, last_change_booking: "K7Q2PD", last_change_reason: "fare_rule_no_change" }

if (member.change_attempts_1h >= 2) {
  desk.route({
    to: "agent",
    skipBot: true,
    booking: await bookings.get(member.last_change_booking),
    note: `Change failed twice: ${member.last_change_reason}`,
  });
}
```

### Answer the bag question for the fare they picked

A traveler has a basic fare picked for a week in Lisbon and has opened the baggage policy page twice in the last few minutes. Then they ask the assistant what they can bring. The policy page lists every fare, and an assistant that answers from it gives the whole table back, so the traveler asks again for their fare. The fare they picked is in this session. The answer can be the allowance for that fare, in one line, with the upgrade that adds a checked bag beside it.

What Signals knows

`policy_page_views_session` 

Times the baggage policy page was opened this visit

`fare_family_session` 

The fare family they picked, such as basic or flex

What your assistant and support desk do

Answers with the allowance for the fare the traveler picked, and names the fare that adds a checked bag, when they ask about bags after reading the policy page this visit. The allowance comes from your fare rules and the assistant writes the reply.

Define it · PythonUse it · TypeScript

support.ts · 15 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")
booking_step = Event(name="booking_step", vendor="com.example", version="1-0-0")

policy_page_views_session = Attribute(
    name="policy_page_views_session",
    type="int32",
    events=[page_view],
    aggregation="counter",
    criteria=Criteria(all=[
        Criterion.like(AtomicProperty(name="page_url"), "%/help/baggage%"),
    ]),
    default_value=0,
)

fare_family_session = Attribute(
    name="fare_family_session",
    type="string",
    events=[booking_step],
    aggregation="last",
    property=EventProperty(vendor="com.example", name="booking_step", major_version=1, path="fare_family"),
)

trip_support_baggage = StreamAttributeGroup(
    name="trip_support_baggage",
    version=1,
    attribute_key=domain_sessionid,
    owner="you@example.com",
    attributes=[policy_page_views_session, fare_family_session],
)
```

```
// In the handler that builds the assistant's context for a new question
const traveler = await signals.getServiceAttributes({
  name: "trip_support_baggage",
  attribute_key: "domain_sessionid",
  identifier: sessionId,
});
// { policy_page_views_session: 2, fare_family_session: "basic" }

if (traveler.policy_page_views_session > 0 && traveler.fare_family_session) {
  assistant.addContext({
    fare: traveler.fare_family_session,
    allowance: fareRules.baggage(traveler.fare_family_session),
    upgrade: fareRules.nextWithCheckedBag(traveler.fare_family_session),
  });
}
```

## 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)
