╔══════════════════════════════════════════════════════════════╗ ║ EXPERIMENT: Capture Pattern ║ ║ KEY METRIC: 0 OAuth providers → same functionality ║ ║ workway.co ║ ╚══════════════════════════════════════════════════════════════╝
The Capture Pattern
Let people share what they find instead of scraping everything they might want.
What we were doing
We built a YouTube to Notion workflow. It watched a playlist and saved new videos automatically.
The setup
- 1. Connect YouTube
- 2. Connect Notion
- 3. Pick a playlist
- 4. We poll it every 15 minutes
- 5. New videos get saved with transcripts
Where it broke down
- App review: YouTube wants us reviewed before users can connect
- Token expiry: OAuth tokens expire, refresh logic breaks
- Wasted polls: Most checks find nothing new
- Playlist friction: People don't naturally add videos to playlists
- Silent failures: When something breaks, nobody knows
The idea
What if the user just told us what to save? One click on the video they're watching. No playlist, no polling, no YouTube OAuth.
This works if
- We can get transcripts without YouTube API access
- Desktop and mobile both work
- It's faster than the old way
- People actually use it
How it works now
User sends us the URL. We scrape the transcript. Done.
Desktop
Browser extension
⌘⇧S
Fallback
Bookmarklet
Click in bookmarks bar
Mobile
Share sheet
Share → WORKWAY
The flow
- 1. Install workflow, connect Notion
- 2. Get the extension or bookmarklet
- 3. Watch a video, press ⌘⇧S
- 4. We scrape the transcript, save to Notion
- 5. Shows up in your dashboard
Why this works
Transcripts are in the page HTML. Anyone can see them. We just need the URL to fetch them ourselves. No API key, no OAuth, no rate limits.
What we built
The pieces
| Component | Purpose | LOC |
|---|---|---|
| /share endpoint | URL detection, transcript scraping, Notion save | ~400 |
| /share page | Landing page for shared URLs | ~350 |
| Browser extension | Toolbar button, context menu, keyboard shortcut | ~200 |
| useBookmarklet hook | Shared bookmarklet code generation | ~25 |
| CaptureToolsModal | Post-install capture tools display | ~130 |
Transcript Extraction
// No API key needed - public page scraping
const html = await fetch(`https://youtube.com/watch?v=${videoId}`);
const captionTracks = html.match(/"captionTracks":(\[.*?\])/);
const trackUrl = JSON.parse(captionTracks)[0].baseUrl;
const transcript = await fetch(trackUrl); // XML formatBefore and after
Before
- • Connect YouTube and Notion
- • Poll every 15 minutes
- • Handle token refresh
- • Wait for app review
- • Failures happen silently
After
- • Connect Notion only
- • User triggers when ready
- • No tokens to manage
- • No review needed
- • Errors show immediately
Numbers
| Metric | Before | After |
|---|---|---|
| OAuth providers | 2 | 1 |
| Setup time | ~5 min | ~1 min |
| User action required | None (passive) | 1 click per capture |
| Failure visibility | Background logs | Immediate UI |
| Intent signal | Noisy (all playlist adds) | Clear (explicit action) |
What we learned
This showed us
- • You don't need OAuth for public data
- • When someone clicks, they meant to — that's a clearer signal than automation
- • Extension plus bookmarklet plus mobile share covers everyone
- • Less code means fewer things that break
We don't know yet
- • Whether scraping stays reliable (YouTube could change their HTML)
- • Whether people prefer clicking over "set and forget" automation
- • How this holds up at scale
- • What happens when content isn't public
Trade-offs we made
- • User does more: One click per video instead of automatic
- • Fragile scraping: If YouTube changes their page, we break
- • Extension friction: Desktop users need to install something
When this makes sense
Use it when
- • The data is public anyway
- • Intent matters more than volume
- • OAuth would be a pain to set up
- • It doesn't need to run in the background
Skip it when
- • You need private data
- • It's bulk processing
- • Background sync is the point
- • Users won't install an extension
If you want to do this
The files
workway-platform/
├── apps/api/src/routes/share.ts # API endpoint
├── apps/web/src/routes/share.tsx # Landing page
├── apps/web/src/routes/extension.tsx # Install instructions
├── apps/web/src/lib/hooks/useBookmarklet.ts
├── apps/web/src/components/
│ ├── CaptureToolsModal.tsx
│ └── ShareToolsSection.tsx
└── apps/extension/ # Chrome extension
├── manifest.json
├── background.js
├── popup.html
└── popup.jsThe steps
- 1. Build a /share endpoint that takes URLs
- 2. Add scraping for your content type (YouTube, etc.)
- 3. Make a landing page that shows progress
- 4. Build a browser extension
- 5. Add share_target to your PWA manifest
- 6. Add a bookmarklet for people who skip extensions
The result
We removed YouTube OAuth. Setup went from five minutes to one. Errors are visible instead of silent. The workflow does the same thing with half the complexity.
The transcript scraping works. The extension works. People can use it without us getting reviewed by YouTube.
Still figuring out
- Long-term reliability: Does scraping hold up when YouTube changes things?
- User preference: Do people actually prefer clicking over automation?
- Extension installs: How many people bother to install it?