Background
V1 of Expensify Travel launched in February of this year, and we've seen month over month growth of customers adopting it and using Expensify Travel to get shit done. We use Spotnana's webhook api to achieve this functionality. We want travel to be as deeply integrated into the app as possible.
Problem
Travel is messy and lots of things happen other than just booking flights. Flights get cancelled, refunded, seats get changed, people get bumped up in class (ex: economy to business), flight schedules change last minute, etcetera. We want our app to be as deeply integrated with travel as possible and to have all relevant information about a trip in the trip chat. Spotnana has a range of "operations" that the webhook API can produce and we handle only couple. So for example, if I book a flight from Los Angeles to Seattle, and that flight gets moved by an hour, there is no notification in the Expensify app to signify that change, even though Spotnana is sending us the data.
Solution
Have Concierge add a muted comment in the trip room chat when we have relevant updates about flights, hotels, cars, limos, and rail bookings. We'll add approximately 20 new types of operations that are provided by the Spotnana webhook API. When we receive a payload from Spotnana, if the payload has an operation for which we want to create an update message, we'll create a report action with the type TRAVEL_TRIP_ROOM_UPDATE. Each report action will have a originalMessage property, which will be a JSON object, and on the json object will be the attribute "operation", which will be the trip operation, and the rest of the attributes will be the reservation object data that we can use to make the message.
Example Message

Example reportAction object for a flight pnr
{
"accountEmail": "__FAKE__",
"accountID": "0",
"action": "TRAVEL_TRIP_ROOM_UPDATE",
"created": "2025-05-09 08:02:04.976",
"message": "",
"originalMessage": {
"arrivalGate": {
"gate": "",
"terminal": "F"
},
"company": {
"longName": "American Airlines",
"phone": "",
"shortName": "AA"
},
"confirmations": [
{
"name": "Confirmation Number",
"value": "MYYJPB"
}
],
"departureGate": {
"gate": "",
"terminal": ""
},
"duration": 9600,
"end": {
"cityName": "Philadelphia, PA, United States",
"date": "2025-03-01T09:40:00",
"longName": "Philadelphia International Airport",
"shortName": "PHL",
"timezoneOffset": ""
},
"isNewDot": false,
"lastModified": "2025-05-09 08:02:04.976",
"operation": "BOOKING_TICKETED",
"route": {
"airlineCode": "AA5115",
"class": "ECONOMY",
"number": "5115"
},
"seatNumber": "",
"start": {
"cityName": "New Orleans, LA, United States",
"date": "2025-03-01T06:00:00",
"longName": "Louis Armstrong New Orleans International Airport",
"shortName": "MSY",
"timezoneOffset": ""
},
"travelerPersonalInfo": {
"email": "employee@travelconsumercorp.com",
"name": "Falon Abram"
},
"type": "flight"
},
"reportActionID": "5755913763092787521",
"reportID": 651638413310008
}
Example reportAction object for a rail pnr
{
"accountEmail": "__FAKE__",
"accountID": "0",
"action": "TRAVEL_TRIP_ROOM_UPDATE",
"created": "2025-05-02 20:50:28.378",
"message": "",
"originalMessage": {
"coachNumber": "152",
"company": {
"longName": "Amtrak",
"phone": "",
"shortName": "Northeast Regional"
},
"confirmations": [
{
"name": "Ticket Number",
"value": "3230884002346"
}
],
"end": {
"cityName": "Boston",
"date": "2024-11-23T16:28:00-05:00",
"longName": "South Station",
"shortName": "BOS",
"timezoneOffset": ""
},
"operation": "EXCHANGE",
"fareType": "Business Class Seat",
"route": {
"class": "BUSINESS",
"name": "Northeast Regional 152"
},
"seatNumber": "9A",
"start": {
"cityName": "New York",
"date": "2024-11-23T12:14:00-05:00",
"longName": "New York Penn Station",
"shortName": "NYP",
"timezoneOffset": ""
},
"travelerPersonalInfo": {
"email": "bstites+travel12@expensifail.com",
"name": "brandon sasdf"
},
"type": "train",
},
"reportActionID": "8882719064301787065",
"reportID": 3158835779047053
}
Example reportAction object for a hotel pnr
{
"accountEmail": "__FAKE__",
"accountID": "0",
"action": "TRAVEL_TRIP_ROOM_UPDATE",
"created": "2025-05-02 20:50:28.378",
"message": "",
"originalMessage": {
"cancellationDeadline": "2024-04-19T23:59:00-07:00",
"cancellationPolicy": "FREE_CANCELLATION_UNTIL",
"confirmations": [
{
"name": "Itinerary Number",
"value": "74694882"
}
],
"end": {
"address": "\"480 SUTTER STREET\" 94108, San Francisco, CA, US",
"date": "2024-04-24T11:00:00",
"longName": "MARRIOTT SAN FRANCISCO UNION SQ"
},
"operation": "BOOKING_REBOOKED",
"longName": "MARRIOTT SAN FRANCISCO UNION SQ",
"paymentType": "PREPAID",
"roomClass": "STANDARD ROOM",
"start": {
"address": "\"480 SUTTER STREET\" 94108, San Francisco, CA, US",
"date": "2024-04-23T16:00:00",
"longName": "MARRIOTT SAN FRANCISCO UNION SQ"
},
"travelerPersonalInfo": {
"email": "soniya@spotnana.com",
"name": "Soniya Goyal"
},
"type": "hotel"
},
"reportActionID": "8882719064301787065",
"reportID": 3158835779047053
}
Example reportAction object for a car pnr
{
"accountEmail": "__FAKE__",
"accountID": "0",
"action": "TRAVEL_TRIP_ROOM_UPDATE",
"created": "2025-05-02 20:50:28.378",
"message": "",
"originalMessage": {
"cancellationDeadline": "2024-04-19T16:00:00",
"cancellationPolicy": "FREE_CANCELLATION_UNTIL",
"carInfo": {
"engine": "UNKNOWN_ENGINE",
"name": "Compact"
},
"end": {
"date": "2024-05-01T14:00:00",
"location": "BASEMENT 1 305 ALEXANDRA ROAD"
},
"operation": "BOOKING_REBOOKED",
"paymentType": "PREPAID",
"reservationID": "K812F742527",
"start": {
"date": "2024-04-19T13:00:00",
"location": "BASEMENT 1 305 ALEXANDRA ROAD"
},
"travelerPersonalInfo": {
"email": "spotnanauser@spotnanauser.com",
"name": "Brandon Stites"
},
"type": "car",
"vendor": "HERTZ"
},
"reportActionID": "8882719064301787065",
"reportID": 3158835779047053
}
You can see that each object follows a similar, but not exact, pattern. Each one will always have a type attribute that you can use to distinguish between air, hotel, rail, and car pnrs, and each will always have an operation attribute to tell you what kind of message we want.
Each report action should cause the UI to display a custom message, as defined in this spreadsheet. The "Message displayed to user" column will be the English copy we'll use. The variables in the messages will be passed to the frontend as part of the report action.
It is difficult to test all of these new report actions because our sandbox environment isn't able to replicate all of them. So for now lets assume that the reservation object stored inside the report action will be similar to the examples listed above.
Contributor will need to:
This will be a tricky thing to test since it is difficult/impossible to test a lot of these flows directly in staging and dev. @blimpich will work closely with the contributor working on this to test how the messages look in the final product.
Upwork Automation - Do Not Edit
- Upwork Job URL: https://www.upwork.com/jobs/~021922333355980760454
- Upwork Job ID: 1922333355980760454
- Last Price Increase: 2025-05-13
Issue Owner
Current Issue Owner: @
Issue Owner
Current Issue Owner: @shubham1206agra
Background
V1 of Expensify Travel launched in February of this year, and we've seen month over month growth of customers adopting it and using Expensify Travel to get shit done. We use Spotnana's webhook api to achieve this functionality. We want travel to be as deeply integrated into the app as possible.
Problem
Travel is messy and lots of things happen other than just booking flights. Flights get cancelled, refunded, seats get changed, people get bumped up in class (ex: economy to business), flight schedules change last minute, etcetera. We want our app to be as deeply integrated with travel as possible and to have all relevant information about a trip in the trip chat. Spotnana has a range of "operations" that the webhook API can produce and we handle only couple. So for example, if I book a flight from Los Angeles to Seattle, and that flight gets moved by an hour, there is no notification in the Expensify app to signify that change, even though Spotnana is sending us the data.
Solution
Have Concierge add a muted comment in the trip room chat when we have relevant updates about flights, hotels, cars, limos, and rail bookings. We'll add approximately 20 new types of operations that are provided by the Spotnana webhook API. When we receive a payload from Spotnana, if the payload has an operation for which we want to create an update message, we'll create a report action with the type
TRAVEL_TRIP_ROOM_UPDATE. Each report action will have aoriginalMessageproperty, which will be a JSON object, and on the json object will be the attribute"operation", which will be the trip operation, and the rest of the attributes will be the reservation object data that we can use to make the message.Example Message

Example
reportActionobject for a flight pnr{ "accountEmail": "__FAKE__", "accountID": "0", "action": "TRAVEL_TRIP_ROOM_UPDATE", "created": "2025-05-09 08:02:04.976", "message": "", "originalMessage": { "arrivalGate": { "gate": "", "terminal": "F" }, "company": { "longName": "American Airlines", "phone": "", "shortName": "AA" }, "confirmations": [ { "name": "Confirmation Number", "value": "MYYJPB" } ], "departureGate": { "gate": "", "terminal": "" }, "duration": 9600, "end": { "cityName": "Philadelphia, PA, United States", "date": "2025-03-01T09:40:00", "longName": "Philadelphia International Airport", "shortName": "PHL", "timezoneOffset": "" }, "isNewDot": false, "lastModified": "2025-05-09 08:02:04.976", "operation": "BOOKING_TICKETED", "route": { "airlineCode": "AA5115", "class": "ECONOMY", "number": "5115" }, "seatNumber": "", "start": { "cityName": "New Orleans, LA, United States", "date": "2025-03-01T06:00:00", "longName": "Louis Armstrong New Orleans International Airport", "shortName": "MSY", "timezoneOffset": "" }, "travelerPersonalInfo": { "email": "employee@travelconsumercorp.com", "name": "Falon Abram" }, "type": "flight" }, "reportActionID": "5755913763092787521", "reportID": 651638413310008 }Example
reportActionobject for a rail pnr{ "accountEmail": "__FAKE__", "accountID": "0", "action": "TRAVEL_TRIP_ROOM_UPDATE", "created": "2025-05-02 20:50:28.378", "message": "", "originalMessage": { "coachNumber": "152", "company": { "longName": "Amtrak", "phone": "", "shortName": "Northeast Regional" }, "confirmations": [ { "name": "Ticket Number", "value": "3230884002346" } ], "end": { "cityName": "Boston", "date": "2024-11-23T16:28:00-05:00", "longName": "South Station", "shortName": "BOS", "timezoneOffset": "" }, "operation": "EXCHANGE", "fareType": "Business Class Seat", "route": { "class": "BUSINESS", "name": "Northeast Regional 152" }, "seatNumber": "9A", "start": { "cityName": "New York", "date": "2024-11-23T12:14:00-05:00", "longName": "New York Penn Station", "shortName": "NYP", "timezoneOffset": "" }, "travelerPersonalInfo": { "email": "bstites+travel12@expensifail.com", "name": "brandon sasdf" }, "type": "train", }, "reportActionID": "8882719064301787065", "reportID": 3158835779047053 }Example
reportActionobject for a hotel pnr{ "accountEmail": "__FAKE__", "accountID": "0", "action": "TRAVEL_TRIP_ROOM_UPDATE", "created": "2025-05-02 20:50:28.378", "message": "", "originalMessage": { "cancellationDeadline": "2024-04-19T23:59:00-07:00", "cancellationPolicy": "FREE_CANCELLATION_UNTIL", "confirmations": [ { "name": "Itinerary Number", "value": "74694882" } ], "end": { "address": "\"480 SUTTER STREET\" 94108, San Francisco, CA, US", "date": "2024-04-24T11:00:00", "longName": "MARRIOTT SAN FRANCISCO UNION SQ" }, "operation": "BOOKING_REBOOKED", "longName": "MARRIOTT SAN FRANCISCO UNION SQ", "paymentType": "PREPAID", "roomClass": "STANDARD ROOM", "start": { "address": "\"480 SUTTER STREET\" 94108, San Francisco, CA, US", "date": "2024-04-23T16:00:00", "longName": "MARRIOTT SAN FRANCISCO UNION SQ" }, "travelerPersonalInfo": { "email": "soniya@spotnana.com", "name": "Soniya Goyal" }, "type": "hotel" }, "reportActionID": "8882719064301787065", "reportID": 3158835779047053 }Example
reportActionobject for a car pnr{ "accountEmail": "__FAKE__", "accountID": "0", "action": "TRAVEL_TRIP_ROOM_UPDATE", "created": "2025-05-02 20:50:28.378", "message": "", "originalMessage": { "cancellationDeadline": "2024-04-19T16:00:00", "cancellationPolicy": "FREE_CANCELLATION_UNTIL", "carInfo": { "engine": "UNKNOWN_ENGINE", "name": "Compact" }, "end": { "date": "2024-05-01T14:00:00", "location": "BASEMENT 1 305 ALEXANDRA ROAD" }, "operation": "BOOKING_REBOOKED", "paymentType": "PREPAID", "reservationID": "K812F742527", "start": { "date": "2024-04-19T13:00:00", "location": "BASEMENT 1 305 ALEXANDRA ROAD" }, "travelerPersonalInfo": { "email": "spotnanauser@spotnanauser.com", "name": "Brandon Stites" }, "type": "car", "vendor": "HERTZ" }, "reportActionID": "8882719064301787065", "reportID": 3158835779047053 }You can see that each object follows a similar, but not exact, pattern. Each one will always have a
typeattribute that you can use to distinguish between air, hotel, rail, and car pnrs, and each will always have anoperationattribute to tell you what kind of message we want.Each report action should cause the UI to display a custom message, as defined in this spreadsheet. The "Message displayed to user" column will be the English copy we'll use. The variables in the messages will be passed to the frontend as part of the report action.
It is difficult to test all of these new report actions because our sandbox environment isn't able to replicate all of them. So for now lets assume that the reservation object stored inside the report action will be similar to the examples listed above.
Contributor will need to:
This will be a tricky thing to test since it is difficult/impossible to test a lot of these flows directly in staging and dev. @blimpich will work closely with the contributor working on this to test how the messages look in the final product.
Upwork Automation - Do Not Edit
Issue Owner
Current Issue Owner: @Issue Owner
Current Issue Owner: @shubham1206agra