FIFA World Cup 2026 Schedule & Lowest Ticket Prices (US & Canada)
Purpose
Return the full FIFA World Cup 2026 match schedule from SeatGeek, filtered to the US and Canada host cities only, as a JSON array where each object is { matchup, stage, date, time, venue, city, lowest_price }, sorted by lowest_price ascending. The entire 104-match dataset (teams, venue, city, country, kickoff datetime, and a live lowest ticket price per match) is server-rendered into the landing page's __NEXT_DATA__ blob, so this is a single HTTP fetch + JSON parse — no clicking, scrolling, or per-event navigation. Read-only; never adds to cart or checks out.
When to Use
- Building a World Cup 2026 price tracker / "cheapest matches" leaderboard for US + Canada venues.
- Pulling the complete fixture list (matchup, stage, date/time, venue, city) for the 91 non-Mexico matches in one shot.
- Monitoring lowest_price drift per match over time (the value is live SeatGeek inventory).
- Anywhere you'd otherwise scrape SeatGeek's infinite-scroll match list — the embedded JSON is faster, complete, and avoids the DataDome-protected UI entirely.
Workflow
Recommended method: fetch. One GET of the landing page returns all 104 matches inside __NEXT_DATA__. A residential proxy is required (SeatGeek fronts everything with DataDome — see Gotchas), but no browser, JS execution, or session state is needed.
-
Fetch the landing page through a residential proxy:
GET https://seatgeek.com/fifa-world-cup-ticketsWith the
browseCLI:browse cloud fetch "https://seatgeek.com/fifa-world-cup-tickets" --proxies(returns HTTP 200 with the full ~4MB HTML; a bare/un-proxied request gets a DataDome 403). -
Extract the embedded JSON. Pull the
__NEXT_DATA__script tag and parse it:<script id="__NEXT_DATA__" type="application/json">{ ... }</script>The full schedule lives at
props.pageProps.worldCupAllEvents2026— an array of 104 SeatGeek event objects. (props.pageProps.allEventsis only the first paginated page of 25 — do not use it; useworldCupAllEvents2026for the complete set.) -
Map each event to the output shape:
- matchup — the two
performers[]entries withtype === "international_soccer"whosenamedoes not start with"World Cup"; join theirshort_name(e.g."Cape Verde","Saudi Arabia") with" vs ". Knockout-round events carry no team performers — instead pull the bracket placeholder fromtitlevia regex-\s*([0-9A-Z]+ vs [0-9A-Z]+)\s*-(e.g."2E vs 2I","W97 vs W98"). Third Place and the Final have no placeholder at all → emit"TBD". - stage — the
performers[]entry whose name matchesWorld Cup (Group Stage|Round of N|Quarterfinals|Semifinals|Third Place|Final), with the leading"World Cup "stripped →"Group Stage","Round of 32","Final", etc. - date / time —
datetime_local(local-to-venue), split intoYYYY-MM-DD(chars 0–10) andHH:MM(chars 11–16). - venue —
venue.name. city —venue.city(the precise municipality, always present, e.g."Inglewood","East Rutherford"). - lowest_price —
stats.lowest_price(a plain number in USD; equals the UI "From $X" price).
- matchup — the two
-
Filter to
venue.country∈{"US", "Canada"}(drops the 13 Mexico matches). Sort bylowest_priceascending. Expect 91 rows (78 US + 13 Canada).
Browser fallback
If the proxied fetch is ever challenged, the identical JSON is reachable through a browser session — it's only ~30× more expensive, with the same parse:
browse open https://seatgeek.com/fifa-world-cup-tickets --remoteusing a stealth session created with--proxies --verified. The page itself loads without a DataDome interstitial (browse get title→"World Cup Tickets 2026 ... SeatGeek").browse wait loadthenbrowse wait timeout 3000.- Read the blob directly — do not use
browse snapshot(returns 0 a11y refs on this page) and do not usebrowse get value #__NEXT_DATA__(a<script>has novalue→ returns""):browse eval "document.getElementById('__NEXT_DATA__').textContent" - Parse and map exactly as in steps 3–4 above.
Site-Specific Gotchas
- DataDome on every entry point. The pre-run homepage probe returned
403 / datadome. A residential proxy (browse cloud fetch --proxies, or a--proxies --verifiedremote session) is mandatory; bare requests get a DataDome 403. The FIFA landing page itself does not present a captcha/interstitial once you're proxied — it renders straight to HTML with all data inline. - Use
worldCupAllEvents2026, notallEvents.pageProps.allEventsis only the first 25 (paginated UI page);pageProps.worldCupAllEvents2026is the complete 104.pageProps.totalEventsis104and is a good sanity check. countryis the literal string"US"(not"USA"/"United States"); Canada is"Canada", Mexico is"Mexico". Filtering on{"US","Canada"}yields exactly 91 of the 104 matches.stats.lowest_priceis live and is the displayed "From $X". It matches the UI to the dollar at fetch time and drifts a few dollars between fetches as inventory turns over (observed e.g. $223 vs $221, $774 vs $804 minutes apart). It can benullif a match momentarily has zero listings — treat null as "no price" and sort it last. All 91 had a price during testing (range ~$171 group-stage to ~$7,869 Final).- city vs host-city label.
venue.cityis the precise municipality (Inglewood, East Rutherford, Foxborough, Santa Clara, Arlington, Miami Gardens…). The FIFA marketing host-city name (Los Angeles, New York, Boston, San Francisco, Dallas, Miami…) is invenue.marquee_city, but that field is null for roughly half the venues (Toronto, Vancouver, Houston, Philadelphia, Atlanta, Seattle…), so prefervenue.cityfor a field that's always populated. - Knockout matchups are placeholders, not teams. Round-of-32 through Semifinals encode bracket slots in the title (
"2E vs 2I","W97 vs W98"); the Third-Place match and the Final have no placeholder. These are genuinely TBD until the bracket fills — emit the placeholder where present, else"TBD". - Don't drive the visible list. The on-page match list is an infinite-scroll React component; scraping it row-by-row needs scrolling + reflows and gives strictly less data than the JSON.
browse snapshotreturns 0 refs on this page. Always read__NEXT_DATA__. - Lighter JSON endpoint exists but isn't standalone-stable.
GET /_next/data/{buildId}/performer-tickets.json?slug=fifa-world-cupreturns the samepagePropsas a 2MB JSON document (no HTML). Confirmed working, butbuildIdrotates on every SeatGeek deploy and must first be read from a live page's__NEXT_DATA__(buildIdfield), so it's only a useful shortcut once you already hold a fresh buildId — otherwise just parse the page HTML.
Expected Output
A JSON array sorted by lowest_price ascending. 91 objects (78 US + 13 Canada). Shapes:
[
{
"matchup": "Cape Verde vs Saudi Arabia",
"stage": "Group Stage",
"date": "2026-06-26",
"time": "19:00",
"venue": "Reliant Stadium",
"city": "Houston",
"lowest_price": 171
},
{
"matchup": "2E vs 2I",
"stage": "Round of 32",
"date": "2026-06-30",
"time": "12:00",
"venue": "AT&T Stadium",
"city": "Arlington",
"lowest_price": 552
},
{
"matchup": "W97 vs W98",
"stage": "Semifinals",
"date": "2026-07-14",
"time": "15:00",
"venue": "AT&T Stadium",
"city": "Arlington",
"lowest_price": 2510
},
{
"matchup": "TBD",
"stage": "Final",
"date": "2026-07-19",
"time": "15:00",
"venue": "MetLife Stadium",
"city": "East Rutherford",
"lowest_price": 7869
}
]
Field notes:
matchup—"<Team A> vs <Team B>"for group stage (short names); bracket placeholder ("2E vs 2I","W97 vs W98") for Round of 32 → Semifinals;"TBD"for Third Place and Final.stage— one of"Group Stage","Round of 32","Round of 16","Quarterfinals","Semifinals","Third Place","Final".dateYYYY-MM-DD,timeHH:MM(24h, local to venue).lowest_price— number (USD); may benullif a match has no active listings (sort last).