thisago's blog ============== ------------------------------------------------------------------------ __________________________________________________ HOW I CANCELLED SEAT SELECTION IN GOL AIRLINES DAMN WEBSITE __________________________________________________ <2026-03-29 Sun> I got stuck analyzing the Gol brazilian airlines check-in page[1] during the whole last hour because I selected a seat. And only after selection I realized: there's no UI button to remove the selection. Even selecting a economic seat in the 48h period before the flight has a cost[2], and yes, this kind of seat is what I bought previously. Anyway, if I extend my personal complaining about websites of airline companies, this content would be enough to fill a book LOL I could just give up of this online check-in and do it from the airport, but the NoScript temporary trust was already conceded, I had to make it until the end. Analyzing ========= The first step was analyze the network requests the website does. The seat selection was *extremely* slow in my qube without GPU acceleration, but this friction didn't feared me to keep devtools open. With the intention to aggregate useful identifiers about my order/user, I copied the curl representation of some of the main requests: - [Check-in data retrieval] (https://codeberg.org/thisago/restclient/src/commit/83159b7e059edee230769b011659dc2516893857/voegol.com.br/b2c/info.http#L9-L27) - Personal data validation. A POST that confirms phone, CPF (government ID) and email (`POST https://g2s-checkin-api.voegol.com.br/api/passenger/update') - [Seat selection] (https://codeberg.org/thisago/restclient/src/commit/ca487637250bb719acd412eb37ae804809ee072c/voegol.com.br/b2c/seatSelection.http#L31-L48) Then, I tried to infer the design of the API to bet the deletion method/parameters. It didn't worked, so what I did was to analyze the JavaScript minified bundle files of the webpage. With +Firefox+ Librewolf devtools global search in debugger tab (`C-F') I managed to find 2 key files: Found with the keyword included in the selection request body, this file exposes a class with these key functions: - `select(body: Record)' - `cancel(body: Record)' Both call a method from `this.seatsClient.(select|cancel)'. Interesting. I configured a network override for this file in devtools and duplicated the implementation of `cancel(body)' into `select(body)', with the hope to get a cancellation when selecting a seat. Didn't worked, but I realized the request wasn't `POST' anymore, but `DELETE'. I had to analyze further. Reading it again, I saw the both functions defines the body this way: ,---- | // ... | select(e) { // `e` is the `body` | let r = { seatSelectRequest: l(S({}, e), { returnSession: !0 }) }, | // ... `---- I assumed the function `S' is doing a deep copy of `e' properties into a new object, a kind of a `Object.assign()'. So it means the meaningful part of the body is built by the caller. The caller. Found this by searching which files calls the `cancel(body)' method. It exposes a class named `o' with the method `onSubmitRemoveSeatMap()', which calls the `cancel(body)' function: ,---- | onSubmitRemoveSeatMap() { | // ... | this.seatsClientFacade | .cancel({ passengerFlightIds: [this.getFlightIdFromPassenger()] }) | // ... | } `---- Bingo. This function is strangely not used across the bundles, but luckly it was left there. The `select(body)' payload is known because devtools network tab logged it. Comparing both, it differ. Here's for setting: [payload for setting the seat] (https://codeberg.org/thisago/restclient/src/commit/ca487637250bb719acd412eb37ae804809ee072c/voegol.com.br/b2c/seatSelection.http#L31-L48): ,---- | { | "seatSelectRequest": { | "passengerFlightId": "", | "seatNumber": "", | "returnSession": true | } | } `---- And the DTO for deleting which this file reveals: ,---- | { | "seatsCancelRequest": { | "passengerFlightIds": [""], | "returnSession": true | } | } `---- Now I have the: - Method, URL and headers to call the `DELETE' route. - Body built by a unused caller. The final query =============== Finally I was able to call the `DELETE' to unset my selection. Because it receives a (damn) Google ReCaptcha token and a retating bearer token, what I did was patch the `select(body)' method to build the body from itself, so I can call `DELETE' when selecting a seat. The following diff is all I did in [chunk-WC72Z7VP.js] (https://b2c.voegol.com.br/check-in/chunk-WC72Z7VP.js) bundle: ,---- | diff /tmp/chunk-WC72Z7VP.js /tmp/chunk-WC72Z7VP.new.js `---- ,---- | 95c95,100 | < let r = { seatSelectRequest: l(S({}, e), { returnSession: !0 }) }, | --- | > let r = { | > seatsCancelRequest: l( | > S({}, { passengerFlightIds: [""] }), | > { returnSession: !0 }, | > ), | > }, | 99c104 | < .select( | --- | > .cancel( | 111c116 | < reservation: i.result.response.seatSelectResponse.reservation, | --- | > reservation: i.result.response.seatCancelResponse.reservation, `---- Once it worked, I reproduced it in `restclient.el' for clearance. I ran with my current bearer and ReCaptcha and worked as well: ,---- | DELETE https://g2s-checkin-api.voegol.com.br/api/seats/cancel | flow=Default | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0 | Accept: application/json | sessionId: :session-id | Content-Type: application/json | Authorization: Bearer :bearer | Origin: https://b2c.voegol.com.br | | { | "seatsCancelRequest": { | "passengerFlightIds": [ | ":flight-id" | ], | "returnSession": true | } | } `---- You can see it in my public restclient repo: Outro ===== In the next time I'll take a increased care before clicking in any selection, these websites offer no transparency and it's pretty frustrating the user experience, forcing you to either finish the check-in paying the seat selection, or having to arrive sooner in the airport to do the check-in from there, hoping it won't take much time. Keep aware, modern internet is dangerous :P Footnotes _________ [1] [2] Which [seems wrong as far as I understood about] (https://old.reddit.com/r/viagens/comments/1kp3nho/troca_de_assento_gol/). ------------------------------------------------------------------------ [HTML version] (/index.html) Clone the [Git repo] (https://codeberg.org/thisago/blog)! [Privacy Policy] (/meta/privacyPolicy.txt) [About Site] (/meta/aboutSite.txt) [Contact] (/meta/contact.txt) [Sitemap] (/sitemap.txt) Generated by Emacs