Chapter 10 · Event Prospecting
Subchapter 10.1
references/event-platforms.mdMarkdown8 KBView on GitHub
__NEXT_DATA__ — Stripe Sessions classrecon.mjs probes the event URL once via browse open + browse eval, then chooses the first matching platform from this list:
__NEXT_DATA__ — document.getElementById('__NEXT_DATA__') returns a <script> tag.<meta name="generator"> content matches /sessionize/i.location.hostname matches /lu\.ma/.<meta property="og:site_name"> matches /eventbrite/i.<script type="application/ld+json"> block has @type === 'Event'.browse get markdown and parses speaker blocks heuristically.The order matters: Next.js sites often also embed JSON-LD, so probing __NEXT_DATA__ first gives us the structured data path before falling back to JSON-LD heuristics.
Detection signature
!!document.getElementById('__NEXT_DATA__')This script tag is emitted by every getServerSideProps / getStaticProps Next.js page. Its textContent is a JSON blob containing every prop hydrated into the React tree at build/request time — including the speaker list for an event microsite.
Extraction strategy — next-data-eval
recon.mjs walks the parsed JSON looking for arrays whose elements are objects with both a name-ish key AND a linkedin substring somewhere. It records the JSON path of every such array (e.g. .props.pageProps.featuredSpeakers.speakers.items) into recon.nextDataPaths. The Phase B extractor then runs ONE browse eval to harvest those arrays and union them into a single people list.
Sample output shape (Stripe Sessions 2026)
{
"platform": "next-data",
"strategy": "next-data-eval",
"nextDataPaths": [
".props.pageProps.featuredSpeakers.speakers.items",
".props.pageProps.moreSpeakers.speakers.items"
]
}A typical speaker object inside one of those arrays:
{
"name": "Patrick Collison",
"title": "CEO and Co-founder",
"companyName": "Stripe",
"linkedInProfile": "https://www.linkedin.com/in/patrickcollison/",
"bio": "..."
}Known gotchas
talks[N].speakers arrays (a denormalized re-listing of the same speakers per session). recon.mjs filters those out via regex so we don’t double-count.browse wait timeout is usually enough; if a site fails extraction, bump the wait to 5s before declaring it a different platform.companyName vs company vs org, linkedInProfile vs linkedinUrl). The Phase B extractor normalizes via fallback chains.Detection signature
<meta name="generator" content="Sessionize.com">Extraction strategy — sessionize-api (stub in v0.1; full implementation in a future phase)
Sessionize exposes a public read-only JSON API at https://sessionize.com/api/v2/{event_id}/view/Speakers. The event ID is in the page URL or embedded JS. v0.1 of recon.mjs only sets strategy: "sessionize-api" and emits the URL — no API discovery yet.
Sample output shape
{
"platform": "sessionize",
"strategy": "sessionize-api"
}Known gotchas
sessionize.com/api/v2/... URLs and pull the ID from there.Detection signature
/lu\.ma/.test(location.hostname)Lu.ma always serves on lu.ma (or rarely a custom CNAME); the hostname check is decisive.
Extraction strategy — json-ld (stub in v0.1)
Lu.ma embeds an Event JSON-LD block with attendee/speaker info, but the volume of structured data is event-dependent. v0.1 sets strategy: "json-ld" and defers actual extraction to Phase B+. The fallback markdown extractor handles Lu.ma pages reasonably well in the meantime.
Sample output shape
{
"platform": "luma",
"strategy": "json-ld"
}Known gotchas
recon.mjs should NOT crash if __NEXT_DATA__ isn’t present and JSON-LD is empty — it falls through to the markdown strategy.Detection signature
<meta property="og:site_name" content="Eventbrite">Extraction strategy — json-ld (stub in v0.1)
Eventbrite emits standard Event JSON-LD on every public event page. Speakers are usually only in the prose body, not the structured data — Eventbrite is more of an RSVP platform than a speaker-directory platform. We may need to combine JSON-LD parsing with markdown extraction of the description body.
Sample output shape
{
"platform": "eventbrite",
"strategy": "json-ld"
}Known gotchas
browse cloud fetch fails or returns thin content, use browse get markdown instead.Detection signature
None of the above match. recon.mjs sets platform: "custom" and strategy: "markdown".
Extraction strategy — markdown
The Phase B extractor calls browse get markdown, splits the output on heading boundaries (####, ###, ##), and treats each block as a candidate speaker:
linkedin.com/in/{handle}This is a best-effort fallback. Coverage is typically 60-80% of the actual speaker list; field accuracy is lower than the structured paths.
Sample output shape
{
"platform": "custom",
"strategy": "markdown"
}Known gotchas
## Speakers, ## Schedule) get parsed as candidate speakers and need to be filtered downstream by checking for the LinkedIn pattern.people.jsonl.recon.mjs probe() after the existing ones. Pick a cheap signal (a meta tag, hostname, or a specific script tag) that’s distinctive.sessionize-api or json-ld-events.extract_event.mjs that handles the new strategy.scripts/__fixtures__/{platform}-snapshot.json capturing the expected recon.json shape so future refactors don’t silently break extraction.Keep the detection branches small. If a platform needs more than ~20 lines of detection logic, factor it out into scripts/detectors/{platform}.mjs and import.