Skip to content
Gauge

ax-check.com

Storylane

storylane.io

ax-check.com

Checked 29 September 2026

B

84/100

Agents need your login

Storylane's quickstart and pricing are clear and easy to find fast.

Docs give a 3-step record/edit/share guide and pricing lists all five tiers with exact dollar amounts, seats, and limits upfront.

2 fixes to start with

Concrete changes, backed by what we found.

Onboarding

Give agents a clear login handoff and a way to resume setup.

Agents cannot progress through onboarding without human help. Provide an agent-accessible path through authentication and setup.

Docs

Return Markdown when clients request text/markdown.

Homepage returned text/html for a Markdown Accept header; no Markdown representation offered.

Recorded coding sessions

Same task, 3 models, no storylane.io account. Recorded 29 September 2026 on Pi.

3 recorded · 6m 29s

6m 29s · recorded

Session findings

Select a finding to see it in context.

piv0.84.2
modelQwen 3.8 Maxcwd/sandbox/repo
Help me build a simple example using Storylane. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
Thinking…

I need to look into Storylane's API and understand how their pricing model works, then figure out what I can actually do with it from a sandbox environment without credentials.

$ pwd; ls -la; cat README.md 2>/dev/null | head -50; echo ---; env | grep -i -E "storylane|api" | sed 's/=.*/=<set>/'{"content":[{"text":"/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 29 13:24 ..\ndrwxr-xr-x 8 root root 40…ctrl+o
{"content":[{"text":"/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 29 13:24 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root  408 Sep 18 08:00 .gitignore\n---\nPI_GATEWAY_API_KEY=<set>\n","type":"text"}]}
$ ls /usr/local/lib/node_modules/@earendil-works/pi-coding-agent/docs 2>/dev/null{"content":[{"text":"compaction.md\ncontainerization.md\ncustom-provider.md\ndevelopment.md\ndocs.json\nenvironment-variables.md\nextensions.md\nimages\nindex.m…ctrl+o
{"content":[{"text":"compaction.md\ncontainerization.md\ncustom-provider.md\ndevelopment.md\ndocs.json\nenvironment-variables.md\nextensions.md\nimages\nindex.md\njson.md\nkeybindings.md\nllama-cpp.md\nmodels.md\npackages.md\nprompt-templates.md\nproviders.md\nquickstart.md\nrpc.md\nsdk.md\nsecurity.md\nsession-format.md\nsessions.md\nsettings.md\nshell-aliases.md\nskills.md\nterminal-setup.md\ntermux.md\nthemes.md\ntmux.md\ntui.md\nusage.md\nwindows.md\n","type":"text"}]}
Thinking…

I'm starting with an empty repository and no Storylane credentials available. Storylane is an interactive product demo platform that offers embeds through an HTML/JS snippet, a HubSpot-like REST API for managing leads and captures, and a JS library for tracking events.

I need to check if I can access their documentation and API endpoints to understand the capabilities better, though network access might be limited in this environment.

$ cd /tmp && timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://js.storylane.io/js/v1/storylane.js ; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://api.storylane.io/api/v1/ ; tim…{"content":[{"text":"200\n404\n200\n","type":"text"}]}ctrl+o
cd /tmp && timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://js.storylane.io/js/v1/storylane.js ; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://api.storylane.io/api/v1/ ; timeout 15 curl -sS -o /dev/null -w "%{http_code}\n" https://www.google.com

Thinking…

I'm checking if the network connection is working, then I need to examine the Storylane JavaScript library and look up their API documentation.

$ cd /tmp && timeout 20 curl -sS https://js.storylane.io/js/v1/storylane.js -o sl.js; wc -c sl.js; head -c 3000 sl.js{"content":[{"text":"17598 sl.js\nvar storylane1732113157820=function(){\"use strict\";var ie=Object.defineProperty;var F=Object.getOwnPropertySymbols;var le=Ob…ctrl+o
{"content":[{"text":"17598 sl.js\nvar storylane1732113157820=function(){\"use strict\";var ie=Object.defineProperty;var F=Object.getOwnPropertySymbols;var le=Object.prototype.hasOwnProperty,ae=Object.prototype.propertyIsEnumerable;var R=(w,m,u)=>m in w?ie(w,m,{enumerable:!0,configurable:!0,writable:!0,value:u}):w[m]=u,_=(w,m)=>{for(var u in m||(m={}))le.call(m,u)&&R(w,u,m[u]);if(F)for(var u of F(m))ae.call(m,u)&&R(w,u,m[u]);return w};const w=\"storylane-demo-event\",m=\"storylane-embed-demo\",u=\"storylane-token-submit\",H=\"storylane-tracking\",$=\"storylane-host-info\",T=\"storylane-demo-host\",I=\"hubspotutk\",O=\"_mkto_trk\",D=/visitor_id\\d+/,E={CloseModal:\"sl-close\"},N=`.sl-close-btn {\n  background: #ffffff;\n  color: #1a1348;\n  border: 2px solid #fffffe;\n  border-radius: 20px;\n  line-height: 18px;\n  box-shadow: 0px 0px 5px #444;\n  cursor: pointer;\n  font-family: Helvetica;\n  font-size: 14px;\n  font-weight: bold;\n  height: 24px;\n  width: 24px;\n  padding: 0px;\n}\n\n.sl-close-btn:hover {\n  box-shadow: 0px 0px 6px rgba(255, 255, 100, 1);\n}\n`;class j extends HTMLElement{constructor(){super();const e=this.attachShadow({mode:\"open\"}),o=document.createElement(\"button\");o.classList.add(\"sl-close-btn\"),o.innerHTML='<svg viewBox=\"0 0 24 24\" class=\"__sl-close-btn\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\"><path fill=\"#9392A3\" d=\"m17.674 15.738-9.5-9.5A1.11 1.11 0 0 0 6.6 6.235l-.274.274a1.11 1.11 0 0 0 0 1.573l9.5 9.5a1.114 1.114 0 0 0 1.574 0l.273-.274a1.11 1.11 0 0 0 0-1.573\"></path><path fill=\"#9392A3\" d=\"m17.675 6.51-.275-.275a1.113 1.113 0 0 0-1.573 0l-9.5 9.5a1.113 1.113 0 0 0 0 1.573l.272.274a1.11 1.11 0 0 0 1.574 0l9.5-9.5a1.113 1.113 0 0 0 0-1.573\"></path><path fill=\"#F3F5F7\" d=\"M12 0a12 12 0 1 1 0 24 12 12 0 0 1 0-24\"></path><path fill=\"#9392A3\" d=\"M16.215 14.964 13.251 12l2.964-2.964a.883.883 0 0 0 0-1.25.884.884 0 0 0-1.25 0L12 10.748 9.036 7.785a.885.885 0 0 0-1.251 1.251L10.749 12l-2.964 2.964a.883.883 0 1 0 1.25 1.251L12 13.251l2.964 2.964a.884.884 0 0 0 1.251-1.25\"></path></svg>';const n=document.createElement(\"style\");n.textContent=N,e.appendChild(n),e.appendChild(o),this.addCloseEventListener(o)}addCloseEventListener(e){const o=()=>{this.dispatchEvent(new Event(E.CloseModal))};e.addEventListener(\"click\",o),this.addEventListener(\"disconnected\",()=>{e.removeEventListener(\"click\",o)})}}customElements.define(\"sl-close-button\",j);class U{constructor(){this.storylane_style=\"\",this.embed_style=\"\",this.demo_html=\"\"}Play(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;switch(this.params=e,this.params.type){case\"popup\":this.Player(e);break;case\"preview_embed\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:.9};this.params=_(_({},o),e);const n=this.params.demo_url;if(!n)return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;console.log(\"creating storylane demo with preview\");const i=this.params.element,l=i.closest(\".sl-embed-container\");if(!l){console.error(\"Unable t","type":"text"}]}
$ cd /tmp && for u in "https://api.storylane.io/api/v1/docs" "https://docs.storylane.io" "https://apidocs.storylane.io" "https://www.storylane.io/pricing"; do echo "== $u"; timeout 15 curl -sS -L -o /de…{"content":[{"text":"== https://api.storylane.io/api/v1/docs\n404 https://api.storylane.io/api/v1/docs\n== https://docs.storylane.io\n200 https://docs.storylane…ctrl+o
cd /tmp && for u in "https://api.storylane.io/api/v1/docs" "https://docs.storylane.io" "https://apidocs.storylane.io" "https://www.storylane.io/pricing"; do echo "== $u"; timeout 15 curl -sS -L -o /dev/null -w "%{http_code} %{url_effective}\n" "$u"; done

{"content":[{"text":"== https://api.storylane.io/api/v1/docs\n404 https://api.storylane.io/api/v1/docs\n== https://docs.storylane.io\n200 https://docs.storylane.io/\n== https://apidocs.storylane.io\n200 https://app.storylane.io/\n== https://www.storylane.io/pricing\n200 https://www.storylane.io/plans\n","type":"text"}]}
Thinking…

I need to explore the JS SDK documentation and API docs to understand what's available. Let me fetch the docs.storylane.io sitemap to locate the relevant sections on the JS library, API reference, and pricing information.

$ cd /tmp && timeout 20 curl -sS https://docs.storylane.io/sitemap.xml -o sm.xml; wc -c sm.xml; grep -o '<loc>[^<]*</loc>' sm.xml | sed 's/<[^>]*>//g' | head -100{"content":[{"text":"696 sm.xml\nhttps://docs.storylane.io/sitemap-pages.xml\nhttps://docs.storylane.io/hubs-docs/sitemap-pages.xml\nhttps://docs.storylane.io/r…ctrl+o
{"content":[{"text":"696 sm.xml\nhttps://docs.storylane.io/sitemap-pages.xml\nhttps://docs.storylane.io/hubs-docs/sitemap-pages.xml\nhttps://docs.storylane.io/repx-chat/sitemap-pages.xml\nhttps://docs.storylane.io/changelog/sitemap-pages.xml\nhttps://docs.storylane.io/repx-updates/sitemap-pages.xml\nhttps://docs.storylane.io/storylane-playbooks/sitemap-pages.xml\n","type":"text"}]}
$ cd /tmp && timeout 20 curl -sS https://www.storylane.io/plans -o plans.html; wc -c plans.html; python3 - <<'EOF'{"content":[{"text":"1148174 plans.html\nStorylane Product Demo Software Plans: Startups, SMB & Enterprises\n","type":"text"}]}ctrl+o
cd /tmp && timeout 20 curl -sS https://www.storylane.io/plans -o plans.html; wc -c plans.html; python3 - <<'EOF'
import re,html
s=open('/tmp/plans.html').read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S)
s=re.sub(r'<style.*?</style>','',s,flags=re.S)
t=re.sub(r'<[^>]+>','\n',s)
t=html.unescape(t)
lines=[l.strip() for l in t.split('\n') if l.strip()]
print('\n'.join(lines[:300]))
EOF

Thinking…

The plans page relies heavily on JavaScript rendering, so I'll need to try fetching the sitemap to extract URLs and look for pricing data embedded in JSON format like Next.js data structures. I should also pull the docs sitemap for the pages.

$ cd /tmp && timeout 20 curl -sS https://docs.storylane.io/sitemap-pages.xml -o sm2.xml; grep -o '<loc>[^<]*</loc>' sm2.xml | sed 's/<[^>]*>//g' | grep -i -E "api|js|embed|integrat|develop"{"content":[{"text":"https://docs.storylane.io/sharing-demos/embed-on-website-or-product\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-dat…ctrl+o
{"content":[{"text":"https://docs.storylane.io/sharing-demos/embed-on-website-or-product\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/hubspot-lead-forms\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/hubspot-integration-features\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/hubspot-website-embeds\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/storylane-app-for-hubspot\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/matching-hubspot-tokens-to-storylane-tokens\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/how-to-build-workflows-with-demo-engagement-data\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/marketo\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/marketo/marketo-lead-forms\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/pardot\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/pardot/pardot-form\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/salesforce\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/salesforce/storylane-app-for-salesforce\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/mcp\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/slack\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/freshsales\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/dynamics-365\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/zapier\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/gong\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/outreach\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/chili-piper\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/calendly\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/google-tag-manager\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/google-analytics\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/gmail\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/segment\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/intercom\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/clearbit\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/6sense\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/sourceforge\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/seismic\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/g2\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/webhooks\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/cross-frame-events\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/external-api\nhttps://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/highspot\nhttps://docs.storylane.io/tips-and-tricks/how-to-add-embed-a-storylane-demo-in-a-zendesk-article\n","type":"text"}]}
$ cd /tmp && grep -o '<loc>[^<]*</loc>' sm2.xml | sed 's/<[^>]*>//g' | wc -l; grep -o '<loc>[^<]*</loc>' sm2.xml | sed 's/<[^>]*>//g' | head -60{"content":[{"text":"grep: sm2.xml: No such file or directory\n0\ngrep: sm2.xml: No such file or directory\n","type":"text"}]}
Thinking…

The parallel calls ran into a race condition with the second one. I can see there's an MCP integration doc and an external API doc available, so I should grab those along with the embed doc. The MCP integration is particularly promising since it suggests a developer workflow. Let me fetch those pages now.

$ cd /tmp && cat > strip.py <<'EOF'{"content":[{"text":"=== integrations-demo-suite/integrations-and-data-flow/external-api (1811218 bytes)\nExternal API | Storylane Docs\nMeet RepX: AI sales age…ctrl+o
cd /tmp && cat > strip.py <<'EOF'
import re,html,sys
s=open(sys.argv[1]).read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S)
s=re.sub(r'<style.*?</style>','',s,flags=re.S)
s=re.sub(r'<svg.*?</svg>','',s,flags=re.S)
t=re.sub(r'<[^>]+>','\n',s)
t=html.unescape(t)
lines=[l.strip() for l in t.split('\n') if l.strip()]
out=[]
for l in lines:
    if out and out[-1]==l: continue
    out.append(l)
print('\n'.join(out))
EOF
for p in integrations-demo-suite/integrations-and-data-flow/external-api integrations-demo-suite/integrations-and-data-flow/mcp sharing-demos/embed-on-website-or-product; do f=$(echo $p|tr '/' '_'); timeout 20 curl -sSL "https://docs.storylane.io/$p" -o "$f.html"; echo "=== $p ($(wc -c < $f.html) bytes)"; python3 strip.py "$f.html" | head -120; done

{"content":[{"text":"=== integrations-demo-suite/integrations-and-data-flow/external-api (1811218 bytes)\nExternal API | Storylane Docs\nMeet RepX: AI sales agents to demo, qualify, and convert prospects\nLearn more\nStorylane Docs\n⌘\nCtrl\nk\nAsk\nLogin\nStart free\nMore\nDemo Suite\nRepX\nProduct Updates\nPlaybooks & Customer Showcase\nGitBook Assistant\nI'm here to help you with the docs.\nWhat is this page about?\nWhat should I read next?\nCan you give an example?\n⌘\nCtrl\ni\nAI\nBased on your context\nSend\nStorylane Docs\n👋\nWelcome to Storylane\n🚀\nQuick Start Guide\n⭐\nBest Practices\n📸\nRECORDING DEMOS\nRecording Screenshot Demos\nRecording HTML Demos\nOther Recording Options\n💻\nEDITING DEMOS\nEditing Screenshot Screens\nEditing HTML Screens\nAdding and Editing Guided Steps\nAI Demo Creation\nPersonalizing Demos\nChapters & Convert CTA\nDemo Settings\nTranslations\nReview and commenting\n🎊\nSHARING DEMOS\nOverview\nShare via Link\nEmbed on Website or Product\nShare via Email\nUse in CRM & Sales Tools\nShare on Social\nPresent Mode\nConference Mode\nExports & Downloads\nDemo Protection & Security\n🖱️\nSandbox Demo\nSandbox Configuration\n📈\nANALYTICS & PERFORMANCE\nCapture Leads\nLead Attribution\nTracking and Analyzing\nAccount-Level Insights\nViewer Data Enrichment\nA/B Testing\nDeal intelligence\n🔗\nIntegrations demo suite\nIntegrations & Data Flow\nHubSpot\nMarketo\nPardot\nSalesforce\nMCP\nSlack\nFreshSales\nDynamics 365\nZapier\nGong\nOutreach\nChili Piper\nCalendly\nGoogle Tag Manager\nGoogle Analytics\nGmail\nSegment\nIntercom\nClearbit\n6Sense\nSourceForge\nSeismic\nG2\nWebhooks\nCross-Frame Events\nExternal API\nHighspot\n🤓\nAccount\nAlerts\nUser activity\nManaging Demos\nManaging Team\nBranding\n⚡\nTips and Tricks\nHow to Switch Your Editor Theme\nHow to create interactive demos that convert?\nHow to Add/Embed a Storylane Demo in a Zendesk Article\nHow do I send data to 6Sense?\nHow to record a video avatar?\nIs it possible to connect a LinkedIn tracking pixel to Storylane?\nHow to edit the background of a screen with a pop-up?\nHow to copy a chapter to a demo?\n=== integrations-demo-suite/integrations-and-data-flow/mcp (2182964 bytes)\nMCP | Storylane Docs\nMeet RepX: AI sales agents to demo, qualify, and convert prospects\nLearn more\nStorylane Docs\n⌘\nCtrl\nk\nAsk\nLogin\nStart free\nMore\nDemo Suite\nRepX\nProduct Updates\nPlaybooks & Customer Showcase\nGitBook Assistant\nI'm here to help you with the docs.\nWhat is this page about?\nWhat should I read next?\nCan you give an example?\n⌘\nCtrl\ni\nAI\nBased on your context\nSend\nStorylane Docs\n👋\nWelcome to Storylane\n🚀\nQuick Start Guide\n⭐\nBest Practices\n📸\nRECORDING DEMOS\nRecording Screenshot Demos\nRecording HTML Demos\nOther Recording Options\n💻\nEDITING DEMOS\nEditing Screenshot Screens\nEditing HTML Screens\nAdding and Editing Guided Steps\nAI Demo Creation\nPersonalizing Demos\nChapters & Convert CTA\nDemo Settings\nTranslations\nReview and commenting\n🎊\nSHARING DEMOS\nOverview\nShare via Link\nEmbed on Website or Product\nShare via Email\nUse in CRM & Sales Tools\nShare on Social\nPresent Mode\nConference Mode\nExports & Downloads\nDemo Protection & Security\n🖱️\nSandbox Demo\nSandbox Configuration\n📈\nANALYTICS & PERFORMANCE\nCapture Leads\nLead Attribution\nTracking and Analyzing\nAccount-Level Insights\nViewer Data Enrichment\nA/B Testing\nDeal intelligence\n🔗\nIntegrations demo suite\nIntegrations & Data Flow\nHubSpot\nMarketo\nPardot\nSalesforce\nMCP\nSlack\nFreshSales\nDynamics 365\nZapier\nGong\nOutreach\nChili Piper\nCalendly\nGoogle Tag Manager\nGoogle Analytics\nGmail\nSegment\nIntercom\nClearbit\n6Sense\nSourceForge\nSeismic\nG2\nWebhooks\nCross-Frame Events\nExternal API\nHighspot\n🤓\nAccount\nAlerts\nUser activity\nManaging Demos\nManaging Team\nBranding\n⚡\nTips and Tricks\nHow to Switch Your Editor Theme\nHow to create interactive demos that convert?\nHow to Add/Embed a Storylane Demo in a Zendesk Article\nHow do I send data to 6Sense?\nHow to record a video avatar?\nIs it possible to connect a LinkedIn tracking pixel to Storylane?\nHow to edit the background of a screen with a pop-up?\nHow to copy a chapter to a demo?\n=== sharing-demos/embed-on-website-or-product (1056756 bytes)\nEmbed on Website or Product | Storylane Docs\nMeet RepX: AI sales agents to demo, qualify, and convert prospects\nLearn more\nStorylane Docs\n⌘\nCtrl\nk\nAsk\nLogin\nStart free\nMore\nDemo Suite\nRepX\nProduct Updates\nPlaybooks & Customer Showcase\nGitBook Assistant\nI'm here to help you with the docs.\nWhat is this page about?\nWhat should I read next?\nCan you give an example?\n⌘\nCtrl\ni\nAI\nBased on your context\nSend\nStorylane Docs\n👋\nWelcome to Storylane\n🚀\nQuick Start Guide\n⭐\nBest Practices\n📸\nRECORDING DEMOS\nRecording Screenshot Demos\nRecording HTML Demos\nOther Recording Options\n💻\nEDITING DEMOS\nEditing Screenshot Screens\nEditing HTML Screens\nAdding and Editing Guided Steps\nAI Demo Creation\nPersonalizing Demos\nChapters & Convert CTA\nDemo Settings\nTranslations\nReview and commenting\n🎊\nSHARING DEMOS\nOverview\nShare via Link\nEmbed on Website or Product\nShare via Email\nUse in CRM & Sales Tools\nShare on Social\nPresent Mode\nConference Mode\nExports & Downloads\nDemo Protection & Security\n🖱️\nSandbox Demo\nSandbox Configuration\n📈\nANALYTICS & PERFORMANCE\nCapture Leads\nLead Attribution\nTracking and Analyzing\nAccount-Level Insights\nViewer Data Enrichment\nA/B Testing\nDeal intelligence\n🔗\nIntegrations demo suite\nIntegrations & Data Flow\n🤓\nAccount\nAlerts\nUser activity\nManaging Demos\nManaging Team\nBranding\n⚡\nTips and Tricks\nHow to Switch Your Editor Theme\nHow to create interactive demos that convert?\nHow to Add/Embed a Storylane Demo in a Zendesk Article\nHow do I send data to 6Sense?\nHow to record a video avatar?\nIs it possible to connect a LinkedIn tracking pixel to Storylane?\nHow to edit the background of a screen with a pop-up?\nHow to copy a chapter to a demo?\nHow to combine Desktop and Mobile captures?\nCustomizing and Tracking Demo URL Links\nHow to add pop-up message for non-clickable elements?\nI'm running paid ads to my demo. How do I report conversions in Google Ads and LinkedIn?\n🔒\nTrust and Security\nGDPR Compliance\nSSO\nFAQs\nPowered by GitBook\nOn this page\nFor the complete documentation index, see\nllms.txt\n. This page is also available as\nMarkdown\n.\nAsk\nOn this page\nDemo Suite\nInteractive Demos\n🎊\nSHARING DEMOS\nEmbed on Website or Product\nEmbedding interactive demos are a proven way to engage website visitors and drive conversions.\nStorylane supports a couple of embed formats:\nInline Embed\nPopup Embed\n","type":"text"}]}
Thinking…

I can use GitBook's markdown export feature by appending .md to URLs or checking the llms.txt file for documentation.

$ cd /tmp && for p in integrations-demo-suite/integrations-and-data-flow/external-api integrations-demo-suite/integrations-and-data-flow/mcp sharing-demos/embed-on-website-or-product integrations-demo-s…{"content":[{"text":"integrations-demo-suite/integrations-and-data-flow/external-api -> 31310\nintegrations-demo-suite/integrations-and-data-flow/mcp -> 16931\n…ctrl+o
cd /tmp && for p in integrations-demo-suite/integrations-and-data-flow/external-api integrations-demo-suite/integrations-and-data-flow/mcp sharing-demos/embed-on-website-or-product integrations-demo-suite/integrations-and-data-flow/cross-frame-events integrations-demo-suite/integrations-and-data-flow/webhooks; do f=$(echo $p|tr '/' '_'); timeout 20 curl -sSL "https://docs.storylane.io/$p.md" -o "$f.md"; echo "$p -> $(wc -c < $f.md)"; done

{"content":[{"text":"integrations-demo-suite/integrations-and-data-flow/external-api -> 31310\nintegrations-demo-suite/integrations-and-data-flow/mcp -> 16931\nsharing-demos/embed-on-website-or-product -> 8687\nintegrations-demo-suite/integrations-and-data-flow/cross-frame-events -> 6275\nintegrations-demo-suite/integrations-and-data-flow/webhooks -> 7969\n","type":"text"}]}
Thinking…

Markdown is working, so now I'm reviewing the external API documentation, MCP setup, and embed docs to understand the full context.

read /tmp/integrations-demo-suite_integrations-and-data-flow_external-api.md{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are av…ctrl+o
{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/external-api.md).\n\n# External API\n\nUse Storylane's External API to connect your workspace with internal tools, applications, and automated workflows.\n\n{% hint style=\"info\" %}\nThe API is available to users on all paid plans. If you need help, contact us at <support@storylane.io>.\n{% endhint %}\n\nWith the API, you can:\n\n* List and publish demos, and retrieve their content and engagement stats\n* List workspace hubs\n* Create and manage demo and hub links, including passcodes, expiration dates, and other access controls\n* Create email tokens for demo and hub links\n* Retrieve demo sessions and demo hub sessions, along with aggregate stats for both\n* List and retrieve lead accounts and leads\n\n### Getting started\n\nYou can generate and manage API keys directly from your Storylane workspace.\n\n#### Step 1: Generate an API key\n\n<figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FwDSPj0tagKgPfl3xo9yC%2FScreenshot%202026-08-27%20at%2014.35.34.png?alt=media&amp;token=a79b6f86-cb2e-4119-92a4-497153179b38\" alt=\"\"><figcaption></figcaption></figure>\n\n1. Sign in to your Storylane workspace.\n2. Go to **Settings → Integrations**.\n3. Find **API keys** and click **View key**.\n4. Copy the **Workspace ID** displayed in the modal.\n5. Click **Generate new key** to create an API key.\n6. Give the key a descriptive name, such as `Production automation`, `Analytics sync`, or `Staging`.\n7. Copy the generated API key and store it securely.\n\n{% hint style=\"warning\" %}\n&#x20;We are not able to access `access_token` again if you lose it. We don’t store it on our side and have no access to it after we send it to you. Therefore, please store it securely.&#x20;\n{% endhint %}\n\nWe recommend creating a separate API key for each integration or environment. This makes it easier to rotate or revoke access without affecting your other integrations.\n\n#### Step 2: Configure the Postman collection\n\nOpen the [Storylane Public Postman Collection](https://www.postman.com/team-storylane/storylane-public/collection/2zkg7jc/storylane-connect?action=share\\&creator=17698894).\n\nIn the collection variables, replace:\n\n* **`ACCESS_TOKEN`** with the API key you generated in Storylane. It will be used as the Bearer authentication token.\n* **`WORKSPACE_ID`** with your Storylane workspace ID, if required by the endpoint.\n\nThen save the updated variables.\n\n#### Step 3: Test the connection\n\nRun the API endpoints in Postman and verify that you are successfully retrieving your company’s demo data.\n\n<figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FUZHkcRqMjFF5q1wmgHMD%2Fimage.png?alt=media&amp;token=b1e7f1e8-e1dd-4c94-979e-b615ba8aa463\" alt=\"\" width=\"375\"><figcaption></figcaption></figure>\n\nHere is an interactive demo demonstrating how to set up using Postman 👇\n\n{% @storylane/embed subdomain=\"app\" linkValue=\"jkzyb2hwbamc\" url=\"<https://app.storylane.io/share/jkzyb2hwbamc>\" %}\n\nA successful response will return data from your Storylane workspace. If the request fails, verify that:\n\n* The API key was copied correctly\n* The key is active\n* The request uses Bearer authentication\n* The workspace ID is correct, where required\n* Your workspace has access to the External API\n\n### Managing API keys\n\nYou can return to **Settings → Integrations → API keys** at any time to view active keys or revoke keys that are no longer needed.\n\nTo rotate a key safely:\n\n1. Generate a new API key.\n2. Update your integration to use the new key.\n3. Confirm that the integration is working.\n4. Revoke the old key.\n\nIf you believe a key has been exposed, generate a replacement and revoke the affected key immediately.\n\n### **API Responses**\n\n#### 1. All Workspace Demos\n\n`GET /api/connect/v1/workspaces/:workspace_id/demos` Use the endpoint to list all demos in your workspace. A sample response:\n\n```json\n{\n  \"demos\": [\n    {\n      \"id\": \"96528ce0-a85d-46be-a878-5cd0242534c3\",\n      \"archived_at\": null,\n      \"convert_cta\": { \"text\": \"optio\", \"url\": \"http://rempel-gerlach.test/rex.oberbrunner\", \"enabled\": true },\n      \"created_at\": \"2026-09-10T05:15:39.812-07:00\",\n      \"edited_at\": \"2026-09-10T04:15:39.811-07:00\",\n      \"folder\": null,\n      \"kind\": \"html\",\n      \"links_count\": 0,\n      \"name\": \"Bogan, Bauch and Becker\",\n      \"published_at\": null,\n      \"status\": \"published\",\n      \"tags\": [],\n      \"team_id\": \"527cf57d-68cd-4e36-90c8-d041571e5302\",\n      \"user\": {\n        \"id\": \"3ebfa7a1-5e86-4609-88a7-c19ea9d5e712\",\n        \"email\": \"carlene_hagenes@schinner.test\",\n        \"first_name\": \"Gilma\",\n        \"last_name\": \"Kozey\"\n      }\n    }\n  ]\n}\n```\n\n#### 2. Get Details on a Single Demo\n\n`GET /api/connect/v1/workspaces/:workspace_id/demos/:id` Use the endpoint to fetch detailed information about a specific demo. A sample response:\n\n```json\n{\n  \"demo\": {\n    \"id\": \"5d641afb-e83c-449a-b31d-81e5686ba1d2\",\n    \"archived_at\": null,\n    \"chapter_menu\": {\n      \"id\": \"159dae1e-d425-47bf-94a6-da3ac3d736b2\",\n      \"button_text\": \"Chapters\",\n      \"description\": \"Pick what you are interested about below\",\n      \"items\": [\n        { \"id\": \"e39bf4b7-5630-4e56-b7b5-c16b25e59d9b\", \"description\": \"Recusandae porro voluptas reiciendis.\", \"name\": \"accusantium\", \"target_kind\": \"default\", \"target_value\": null }\n      ],\n      \"title\": \"Experience Chapters\"\n    },\n    \"chapters\": [\n      {\n        \"id\": \"bb46b495-5575-4312-94d6-c59950eb7d4d\",\n        \"name\": \"natus\",\n        \"steps\": [\n          {\n            \"id\": \"be16ddbd-6b60-4514-8ec4-d7fa556cef47\",\n            \"audio_url\": \"https://storylane-dev-uploads.s3.us-east-2.amazonaws.com/.../id.mov\",\n            \"content\": \"<div class=\\\"ut\\\">Quisquam accusantium reiciendis voluptas.</div>\",\n            \"cta\": \"<div>Continue</div>\",\n            \"kind\": \"modal\",\n            \"secondary\": null,\n            \"transcription\": \"Est non qui qui.\"\n          }\n        ]\n      }\n    ],\n    \"convert_cta\": { \"text\": \"Click here to learn more\", \"url\": \"\", \"enabled\": true },\n    \"created_at\": \"2026-09-10T05:15:39.548-07:00\",\n    \"edited_at\": \"2026-09-10T04:15:39.548-07:00\",\n    \"folder\": \"Iowa monkeys\",\n    \"kind\": \"html\",\n    \"links_count\": 0,\n    \"name\": \"Von, Cronin and Quitzon\",\n    \"published_at\": null,\n    \"status\": \"draft\",\n    \"tags\": [],\n    \"team\": { \"id\": \"ddacb917-0849-46c9-bdb5-9ae4d6c2eab5\", \"name\": \"Rhode Island people 575\" },\n    \"user\": {\n      \"id\": \"1da47Sde-6d51-4513-b388-aa7ab5cde161\",\n      \"email\": \"jamaal_kerluke@pagac-watsica.test\",\n      \"first_name\": \"Raul\",\n      \"last_name\": \"Kuvalis\"\n    }\n  }\n}\n```\n\n#### 3. Publish a Demo\n\n`POST /api/connect/v1/workspaces/:workspace_id/demos/:id/publish` Use the endpoint to publish a demo. A sample response:\n\n```json\n{\n  \"demo\": {\n    \"id\": \"daa67ec3-8e05-4360-a9ef-1ffedd4688ab\",\n    \"archived_at\": null,\n    \"chapter_menu\": null,\n    \"chapters\": [],\n    \"convert_cta\": { \"text\": \"Book a Demo\", \"url\": \"\", \"enabled\": false },\n    \"created_at\": \"2026-09-10T05:15:38.976-07:00\",\n    \"edited_at\": \"2026-09-10T05:15:39.067-07:00\",\n    \"folder\": null,\n    \"kind\": \"html\",\n    \"links_count\": 0,\n    \"name\": \"Ritchie, Kub and Johnson\",\n    \"published_at\": \"2026-09-10T05:15:39.067-07:00\",\n    \"status\": \"published\",\n    \"tags\": [],\n    \"team\": { \"id\": \"5d7ac791-8a8f-40d0-bb28-900aa8b7607f\", \"name\": \"Illinois lions 569\" },\n    \"user\": {\n      \"id\": \"2d40bae1-15f3-44f3-b0d4-890b89029a6d\",\n      \"email\": \"kermit_moen@haag-kuhn.example\",\n      \"first_name\": \"Tameka\",\n      \"last_name\": \"Wilkinson\"\n    }\n  },\n  \"link\": {\n    \"id\": \"96fb719a-e16d-46aa-8884-ca606a5a466f\",\n    \"created_at\": \"2026-09-10T05:15:39.046-07:00\",\n    \"default_link\": true,\n    \"email_confirmation_required\": false,\n    \"email_required\": false,\n    \"expires_at\": null,\n    \"name\": \"Default\",\n    \"passcode\": null,\n    \"recipient_email\": null,\n    \"tokens\": [],\n    \"updated_at\": \"2026-09-10T05:15:39.046-07:00\",\n    \"url\": \"https://staging.storylane.us/share/6dzry3zezs70\",\n    \"user\": {\n      \"id\": \"2d40bae1-15f3-44f3-b0d4-890b89029a6d\",\n      \"email\": \"kermit_moen@haag-kuhn.example\",\n      \"first_name\": \"Tameka\",\n      \"last_name\": \"Wilkinson\"\n    },\n    \"whitelist_email_domains\": []\n  }\n}\n```\n\n#### 4. Paginated Stats for All Workspace Demos\n\n`GET /api/connect/v1/workspaces/:workspace_id/demos/stats` Use the endpoint to fetch paginated engagement stats across all workspace demos. A sample response:\n\n```json\n{\n  \"demos\": [\n    {\n      \"id\": \"dd319440-5642-467d-a2db-cb38b1a4f2ea\",\n      \"name\": \"Larkin-Kiehn\",\n      \"intent_level\": \"unknown\",\n      \"archived_at\": null,\n      \"current\": {\n        \"total_sessions\": 0,\n        \"unique_sessions\": 0,\n        \"engaged_sessions\": 0,\n        \"cta_opened_sessions\": 0,\n        \"leads_count\": 0,\n        \"time_spent\": 0,\n        \"total_steps_count\": 0,\n        \"viewed_steps_count\": 0,\n        \"avg_time_spent\": 0\n      }\n    }\n  ],\n  \"page\": 1,\n  \"per_page\": 20,\n  \"next_page\": null\n}\n```\n\n#### 5. All Demo Sessions\n\n`GET /api/connect/v1/workspaces/:workspace_id/demo_sessions` Use the endpoint to list demo sessions across the workspace. A sample response:\n\n```json\n{\n  \"page\": 1,\n  \"per_page\": 40,\n  \"next_page\": null,\n  \"demo_sessions\": [\n    {\n      \"id\": \"b22e61c2-a0d0-489e-a109-7eac2630fc36\",\n      \"browser\": null,\n      \"clearbit_company\": {\n        \"id\": \"c7297878-73cb-44a0-884e-9c787321bd16\",\n        \"annual_revenue\": \"$1M-$16M\",\n        \"domain_name\": \"mcdermott-klocko.example\",\n        \"employees\": \"51-250\",\n        \"industry\": \"Information Technology\",\n        \"logo_url\": \"http://buckridge.example/stephan.kunde\",\n        \"name\": \"Bartoletti, Champlin and Spinka\"\n      },\n      \"clearbit_confidence_score\": null,\n      \"cookie_id\": \"316c2802-a63a-4adc-8e26-ed9a8862a463\",\n      \"cta_opened\": false,\n      \"cta_opened_options\": null,\n      \"engaged\": false,\n      \"first_accessed_at\": \"2026-09-10T05:16:02.885-07:00\",\n      \"host_url\": \"\",\n      \"intent_level\": \"unknown\",\n      \"internal\": false,\n      \"ipdata_location\": { \"city\": \"New Erin\", \"country\": \"Luxembourg\", \"state\": null },\n      \"last_accessed_at\": \"2026-09-10T05:08:19.963-07:00\",\n      \"lead\": {\n        \"id\": \"edcf442f-a04a-4826-a786-553cb2e98789\",\n        \"client_source\": \"none\",\n        \"client_tracking_id\": null,\n        \"company_name\": \"Rohan, Kassulke and Rau\",\n        \"created_at\": \"2026-09-10T05:16:02.876-07:00\",\n        \"email\": \"jammie@bechtelar.test\",\n        \"external_entities\": {},\n        \"external_user_id\": null,\n        \"first_name\": \"Raeann\",\n        \"last_name\": \"Champlin\",\n        \"phone\": \"(301) 517-3612\",\n        \"sync_error\": null\n      },\n      \"lead_company_id\": null,\n      \"link\": {\n        \"id\": \"16123265-525e-488f-b224-c18cade3881e\",\n        \"alias_value\": \"\",\n        \"base_link\": false,\n        \"name\": \"modi reprehenderit\",\n        \"original_value\": \"bvrmgixl66iu\",\n        \"value\": \"bvrmgixl66iu\"\n      },\n      \"mobile\": false,\n      \"platform\": null,\n      \"project\": {\n        \"id\": \"3fbb2541-4476-4367-a752-cf6df6f2f2ed\",\n        \"archived_at\": null,\n        \"flows_count\": 0,\n        \"kind\": \"html\",\n        \"name\": \"Schmidt-Tillman\",\n        \"presenter_mode\": false,\n        \"widgets_count\": 0\n      },\n      \"referer\": \"https://staging.storylane.us/demo/bvrmgixl66iu\",\n      \"remote_ip\": \"104.112.51.1\",\n      \"tagged_email\": null,\n      \"time_spent\": -462,\n      \"token_data\": {},\n      \"total_steps_count\": 0,\n      \"utm_params\": {},\n      \"view_options\": { \"embed\": false, \"hide_forms\": false, \"hide_steps\": false },\n      \"viewed_steps_count\": 0,\n      \"viewed_steps_percent\": 0\n    }\n  ]\n}\n```\n\n#### 6. Demo Sessions Stats\n\n`GET /api/connect/v1/workspaces/:workspace_id/demo_sessions/stats` Use the endpoint to fetch aggregate demo session stats. A sample response:\n\n```json\n{\n  \"stats\": {\n    \"total_sessions\": 2,\n    \"engaged_sessions\": 0,\n    \"cta_opened_sessions\": 0,\n    \"high_intent_level\": 0,\n    \"leads_count\": 0,\n    \"time_spent\": 0\n  }\n}\n```\n\n#### 7. Get Detailed Demo Session\n\n`GET /api/connect/v1/workspaces/:workspace_id/demo_sessions/:id` Use the endpoint to fetch detailed information about a specific demo session. A sample response:\n\n```json\n{\n  \"demo_session\": {\n    \"id\": \"75d7780b-e17e-43d9-ae53-e72ac7c7f2a5\",\n    \"browser\": null,\n    \"clearbit_company\": null,\n    \"clearbit_confidence_score\": null,\n    \"cookie_id\": \"3567c24e-1304-46f1-82ea-56f8ea659e70\",\n    \"cta_opened\": false,\n    \"cta_opened_options\": null,\n    \"demo_events\": [\n      { \"id\": \"f12db591-94e4-489e-b17a-53557fd4e86f\", \"kind\": \"opened\", \"occurred_at\": \"2026-09-10T05:13:02.727-07:00\", \"options\": {}, \"target_id\": null, \"target_type\": null },\n      { \"id\": \"1be08427-aa6f-42e7-a1ca-546acafa88f0\", \"kind\": \"widget_viewed\", \"occurred_at\": \"2026-09-10T05:14:02.762-07:00\", \"options\": {}, \"target_id\": \"0236b65e-7377-4651-a9e0-5681c0e6106c\", \"target_type\": \"Widget\" }\n    ],\n    \"engaged\": false,\n    \"first_accessed_at\": \"2026-09-10T04:42:50.453-07:00\",\n    \"host_url\": \"\",\n    \"intent_level\": \"unknown\",\n    \"internal\": false,\n    \"ipdata_location\": null,\n    \"last_accessed_at\": \"2026-09-10T05:08:37.080-07:00\",\n    \"lead\": null,\n    \"lead_company_id\": null,\n    \"link\": {\n      \"id\": \"2fc95a99-d3ca-4de7-9ce7-96a1a64a55a2\",\n      \"alias_value\": \"\",\n      \"base_link\": false,\n      \"name\": \"consequatur voluptas\",\n      \"original_value\": \"uhpqtckng8i0\",\n      \"value\": \"uhpqtckng8i0\"\n    },\n    \"mobile\": false,\n    \"platform\": null,\n    \"project\": {\n      \"id\": \"30f20a07-68e7-4c22-b092-a8bddcb96ca7\",\n      \"archived_at\": null,\n      \"flows_count\": 1,\n      \"kind\": \"html\",\n      \"name\": \"Hudson, Corwin and Schmidt\",\n      \"presenter_mode\": false,\n      \"widgets_count\": 1\n    },\n    \"project_body\": {},\n    \"referer\": \"https://staging.storylane.us/demo/uhpqtckng8i0\",\n    \"remote_ip\": \"103.44.102.221\",\n    \"tagged_email\": null,\n    \"time_spent\": 1546,\n    \"token_data\": {},\n    \"total_steps_count\": 0,\n    \"utm_params\": {},\n    \"view_options\": { \"embed\": false, \"hide_forms\": false, \"hide_steps\": false },\n    \"viewed_steps_count\": 0,\n    \"viewed_steps_percent\": 0\n  }\n}\n```\n\n#### 8. List Demo Links\n\n`GET /api/connect/v1/workspaces/:workspace_id/demos/:demo_id/links` Use the endpoint to list all links for a given demo. A sample response:\n\n```json\n{\n  \"links\": [\n    {\n      \"id\": \"7578653e-e35d-45aa-b449-8f1aac070247\",\n      \"created_at\": \"2026-09-10T05:15:49.328-07:00\",\n      \"default_link\": false,\n      \"email_confirmation_required\": false,\n      \"email_required\": false,\n      \"expires_at\": null,\n      \"name\": \"illum voluptas\",\n      \"passcode\": null,\n      \"recipient_email\": \"edmundo@frami.test\",\n      \"tokens\": [],\n      \"updated_at\": \"2026-09-10T05:15:49.328-07:00\",\n      \"url\": \"https://staging.storylane.us/share/7vr8fkijmv3d\",\n      \"user\": {\n        \"id\": \"cd065129-5137-4f9a-8c46-26660bfe4587\",\n        \"email\": \"felipa_vonrueden@muller.example\",\n        \"first_name\": \"Arlie\",\n        \"last_name\": \"Runolfsdottir\"\n      },\n      \"whitelist_email_domains\": []\n    }\n  ]\n}\n```\n\n#### 9. Create a New Demo Link\n\n`POST /api/connect/v1/workspaces/:workspace_id/demos/:demo_id/links` Use the endpoint to create a new link for a demo. A sample response:\n\n```json\n{\n  \"link\": {\n    \"id\": \"01d9daec-ebbe-4105-9880-addbbe060d6d\",\n    \"created_at\": \"2026-09-10T05:15:48.326-07:00\",\n    \"default_link\": false,\n    \"email_confirmation_required\": false,\n    \"email_required\": false,\n    \"expires_at\": null,\n    \"name\": \"enim\",\n    \"passcode\": null,\n    \"recipient_email\": null,\n    \"tokens\": [],\n    \"updated_at\": \"2026-09-10T05:15:48.326-07:00\",\n    \"url\": \"https://staging.storylane.us/share/lh49uvcwtlaq\",\n    \"user\": {\n      \"id\": \"742ab3e1-7b57-4fed-bca8-6d2c95fb954c\",\n      \"email\": \"rodrick@yost-harris.example\",\n      \"first_name\": \"Travis\",\n      \"last_name\": \"Roberts\"\n    },\n    \"whitelist_email_domains\": []\n  }\n}\n```\n\n#### 10. Update an Existing Demo Link\n\n`PATCH /api/connect/v1/workspaces/:workspace_id/demos/:demo_id/links/:id` Use the endpoint to update an existing demo link. A sample response:\n\n```json\n{\n  \"link\": {\n    \"id\": \"3185e384-528d-4846-8d08-3c090aea186d\",\n    \"created_at\": \"2026-09-10T05:15:48.667-07:00\",\n    \"default_link\": false,\n    \"email_confirmation_required\": false,\n    \"email_required\": false,\n    \"expires_at\": null,\n    \"name\": \"iste\",\n    \"passcode\": null,\n    \"recipient_email\": null,\n    \"tokens\": [],\n    \"updated_at\": \"2026-09-10T05:15:48.702-07:00\",\n    \"url\": \"https://staging.storylane.us/share/vckynuinuvkw\",\n    \"user\": {\n      \"id\": \"d38b7ad3-7d8c-44f2-b0bd-69430f113936\",\n      \"email\": \"king@nader-legros.test\",\n      \"first_name\": \"Renea\",\n      \"last_name\": \"Yost\"\n    },\n    \"whitelist_email_domains\": []\n  }\n}\n```\n\n#### 11. Deactivate a Demo Link\n\n`DELETE /api/connect/v1/workspaces/:workspace_id/demos/:demo_id/links/:id` Use the endpoint to deactivate an existing demo link. Returns a `204 No Content` response with no body on success.\n\n#### 12. Create a New Email Token for a Demo Link\n\n`POST /api/connect/v1/workspaces/:workspace_id/demos/:demo_id/links/:link_id/tokens` Use the endpoint to create a new email token for a demo link. A sample response:\n\n```json\n{\n  \"token\": {\n    \"id\": \"5685c781-0954-4651-926b-5b535afc2227\",\n    \"email\": \"hunter_schumm@kihn-vonrueden.test\",\n    \"link_url\": \"https://staging.storylane.us/share/vxs1bfygmdgo?sl_token=5685c781-0954-4651-926b-5b535afc2227\"\n  }\n}\n```\n\n#### 13. All Workspace Hubs\n\n`GET /api/connect/v1/workspaces/:workspace_id/hubs` Use the endpoint to list all hubs in your workspace. A sample response:\n\n```json\n{\n  \"hubs\": [\n    {\n      \"id\": \"24f85e4b-f0b4-4d1e-85a7-54263eac599a\",\n      \"created_at\": \"2026-09-10T05:15:08.738-07:00\",\n      \"edited_at\": \"2026-09-10T05:15:08.739-07:00\",\n      \"kind\": \"custom\",\n      \"links_count\": 0,\n      \"name\": \"vel\",\n      \"published_at\": null,\n      \"status\": \"published\",\n      \"team_id\": \"5966d10b-06b1-451d-bc08-6f0cc09caf16\",\n      \"user\": {\n        \"id\": \"5975ee7d-3ebb-491b-8a33-e0014bbeb1b3\",\n        \"email\": \"lorene_kub@heathcote.example\",\n        \"first_name\": \"Genoveva\",\n      …
read /tmp/sharing-demos_embed-on-website-or-product.md{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are av…ctrl+o
{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.storylane.io/sharing-demos/embed-on-website-or-product.md).\n\n# Embed on Website or Product\n\nEmbedding interactive demos are a proven way to engage website visitors and drive conversions.\n\nStorylane supports a couple of embed formats:\n\n* [Inline Embed](#inline-embed)\n* [Popup Embed](#popup-embed)\n* [Supported Platforms](#supported-platforms)\n* [Embed Support](#embed-support)\n* [Mobile-friendly Demos](#mobile-friendly-demos)\n\n{% hint style=\"info\" %}\n\n#### To embed your demo into a Hubspot website or landing page, please refer to [this article](/integrations-demo-suite/integrations-and-data-flow/hubspot/hubspot-website-embeds.md).\n\n{% endhint %}\n\n***\n\n### 👉 **Inline Embed**\n\n#### :new: LLM Friendly toggle\n\nWhen copying your embed code, you'll notice a new **LLM-Friendly** toggle in the embed panel. Turning this on makes your demo content readable by LLMs and search engines — so your product tour can be indexed and discovered organically.\n\nTo enable it, go to **Share → Embed** and toggle **LLM-Friendly** on before copying the code.\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FCPhUh3T1i5bCy38lcZ9J%2FScreenshot%202026-06-07%20at%207.01.17%E2%80%AFPM.png?alt=media&amp;token=f109ee77-d270-4405-a8f4-d3d9a6504591\" alt=\"\" width=\"563\"><figcaption></figcaption></figure></div>\n\n#### **Without Overlay**\n\nThe demo auto-starts as soon as the page loads.\\\nExample: [Clari](https://www.clari.com/product-tour/) | [Gong](https://www.gong.io/tour-confirmation/)\n\n{% hint style=\"info\" %}\nCopy the embed code ([see example](https://jsfiddle.net/storylane/h6gdwf4s/)) and you can add it to any website.&#x20;\n{% endhint %}\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FF5uUEfhAjwa5qRKCnPQW%2FScreenshot%202026-06-07%20at%206.13.27%E2%80%AFPM.png?alt=media&amp;token=ff1c9ae0-4061-498a-ab89-36963397ba9e\" alt=\"\" width=\"563\"><figcaption></figcaption></figure></div>\n\n**With Overlay**\n\nShow a demo preview with a clickable CTA button that loads the demo when clicked. 👆\\\nExample: [Storylane](https://www.storylane.io/) | [Reviewr](https://www.reviewr.com/)\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FnEn6NRRSIlCBVhZckXV1%2FScreenshot%202026-06-07%20at%206.13.52%E2%80%AFPM.png?alt=media&amp;token=0851b895-f8d9-48cc-ae10-1b8ac7262709\" alt=\"\" width=\"563\"><figcaption></figcaption></figure></div>\n\n{% hint style=\"info\" %}\nDemo preview is shown with the CTA button ([see example](https://jsfiddle.net/storylane/akprf9z3/)). Clicking CTA loads the demo.&#x20;\n{% endhint %}\n\n{% hint style=\"warning\" %}\nOverlay embeds have a blur slider for a cleaner, distraction-free background\n{% endhint %}\n\n***\n\n### 👉 **Popup Embed**\n\nAdd a CTA button on your page, and when clicked, voilà! A full-screen demo pops up for a fun, interactive experience! 🖱️✨ Example: [SentinelOne](https://www.sentinelone.com/lp/tour/)'\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FH5CIUAvjc1vOMyUmmVvl%2FScreenshot%202026-06-07%20at%206.17.42%E2%80%AFPM.png?alt=media&amp;token=5f64575c-7cf7-4de1-9bde-62c5dec39fa7\" alt=\"\" width=\"563\"><figcaption></figcaption></figure></div>\n\n{% hint style=\"info\" %}\nAdd the CTA button on your website ([see example](https://jsfiddle.net/storylane/w64kq9uh/)) and on click, it will open a popup window with the demo.\n{% endhint %}\n\n### 👉 Supported Platforms\n\nStorylane demos play nice with a variety of platforms right out of the box! 🎬 Check out the list below and dive into the interactive fun on each one! ✨\n\n<table data-header-hidden><thead><tr><th width=\"155\" align=\"center\"></th><th width=\"153\" align=\"center\"></th><th width=\"134\" align=\"center\"></th><th width=\"179\" align=\"center\"></th><th align=\"center\"></th></tr></thead><tbody><tr><td align=\"center\"><a href=\"https://app.storylane.io/share/pecbxgquhxu8\"><strong>Webflow</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/hg1wpf21zfyv\"><strong>Wordpress</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/j6pmqg2ltl63\"><strong>Notion</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/xwdankssm6fy\"><strong>Jira Confluence</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/19cbz1nbcttq\"><strong>Gitbook</strong></a></td></tr><tr><td align=\"center\"><a href=\"https://app.storylane.io/share/yiogycrgeig9\"><strong>Readme</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/ojxtcerxcxez\"><strong>Miro</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/p3xd8yxc63ga\"><strong>Guru</strong></a></td><td align=\"center\"><a href=\"https://app.storylane.io/share/4mxq1djdjovs\"><strong>Trainual</strong></a></td><td align=\"center\"><a href=\"broken://pages/LD4mpLhDi4E61mPEXfnD\"><strong>Zendesk</strong></a></td></tr></tbody></table>\n\n### 👉 Embed Support\n\nWant to add your Storylane demo anywhere? **oEmbed** has got your back! 🙌 Just grab the demo URL and pop it into platforms like **iFramely** or **Embedly**, and voilà – demo embedded! 🎉\n\nFeeling like a code wizard? 🧙‍♂️ You can also use the **oEmbed API** for a little manual magic:\\\n[API example here](https://api.storylane.io/oembed/meta?url=https://app.storylane.io/share/ha3eti5gqq2c) – just swap out the URL with your own demo link! 🔮\n\nReady to embed everywhere? Let's make some demo magic! ✨\n\n### 👉 Mobile-friendly demos\n\nIf your product doesn’t have a mobile app and isn’t mobile responsive:\n\nUsing Screenshots:\n\n* Open the[ demo](https://app.storylane.io/share/ub2niorbmx9q) on a mobile-sized screen. It should display correctly.\n* You can also switch to landscape mode for a better view.\n* Enable “Mobile View.” This will prompt users to rotate their device.\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2F0Evyf2m36zKDlOa0LujS%2FScreenshot%202026-02-16%20at%201.50.40%E2%80%AFPM.png?alt=media&amp;token=122b8012-aed2-4774-8d44-531b94260ff8\" alt=\"\" width=\"563\"><figcaption></figcaption></figure></div>\n\nUsing HTML:\n\n* Go to Settings > Auto-scale Demo.\n* Toggle the setting ON. This will adjust the demo for mobile viewing automatically.\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FOYukERTsAVou0gRyWbtw%2FScreenshot%202026-02-16%20at%201.53.09%E2%80%AFPM.png?alt=media&amp;token=7a26e619-1e4d-4519-b74c-93fef216524b\" alt=\"\" width=\"563\"><figcaption></figcaption></figure></div>\n\nWith these tips, you can easily capture mobile-friendly demos for your product.&#x20;\n\n{% hint style=\"info\" %}\nIf you need help or have any questions, feel free to contact us at <support@storylane.io>.\n{% endhint %}\n\n\n---\n\n# Agent Instructions\nThis documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.\n\n## Querying This Documentation\nIf you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.\n\nPerform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:\n\n```\nGET https://docs.storylane.io/sharing-demos/embed-on-website-or-product.md?ask=<question>&goal=<endgoal>\n```\n\n`ask` is the immediate question: it should be specific, self-contained, and written in natural language.\n`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.\n\nThe response will contain a direct answer to the question and relevant excerpts and sources from the documentation.\n\nUse this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.\n","type":"text"}]}
Thinking…

I need to review the MCP documentation and cross-frame events details, then look into the JavaScript library capabilities and find a public demo link from Storylane to work with—something like one of their app.storylane.io/share endpoints that I can use for testing.

read /tmp/integrations-demo-suite_integrations-and-data-flow_mcp.md{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are av…ctrl+o
{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/mcp.md).\n\n# MCP\n\nStorylane MCP connects Claude, ChatGPT, Microsoft Copilot, Cursor,  and other MCP-compatible AI clients to your account. Personalize, analyze, and publish demos without leaving your AI client.\n\n* [Storylane MCP overview](#storylane-mcp-overview)\n* [Setup and clients](#connect-your-ai-client)\n* [Available tools](#available-tools)\n* [AI workflows](#ai-workflows)\n* [Ready-to-use prompts](#ready-to-use-prompts)\n* [FAQ](#faq)\n\n***\n\n### Storylane MCP overview\n\nConnect Claude, ChatGPT, Microsoft Copilot other MCP-compatible AI clients to Storylane.\n\nUse natural-language prompts to manage your demo library. Build, personalize, analyze, and publish demos without leaving your AI client.\n\n{% hint style=\"info\" %}\nThe MCP server URL: <https://mcp.storylane.io/mcp>\n{% endhint %}\n\n{% @storylane/embed subdomain=\"app\" linkValue=\"a8s4jaoeaa0o\" url=\"<https://app.storylane.io/share/a8s4jaoeaa0o>\" %}\n\nWhat you can do:\n\n* **Analyze engagement.** Query demo performance, leads, and account activity.\n* **Create demos from media.** Convert screenshots or recordings into interactive demos.\n* **Personalize at scale.** Create tailored demo variants and trackable links.\n* **Manage your library.** Browse demos, workspaces, hubs, links, and voices.\n* **Publish and distribute.** Update demos, publish them, and manage share links.\n\n### Connect your AI client\n\nTwo ways to connect, depending on how you work.\n\n#### 1. Public LLM clients\n\nFor Claude, ChatGPT, and Microsoft Copilot. Uses OAuth — no API keys required.\n\n{% tabs %}\n{% tab title=\"Claude\" %}\n\n1. In Claude, open **Settings → Connectors**.\n2. Select **Add custom connector**.\n3. Paste the Storylane MCP server URL.\n4. Sign in to Storylane and approve access.\n5. In a conversation, select **+ → Connectors** and enable Storylane.\n6. Open connector settings and set **Tool permissions** to **Always allow** — this lets workflows run end to end without stopping to ask for confirmation on every action.\n\n{% hint style=\"info\" %}\n**For Personal Accounts:** \"Add Custom Connector\" is always available.\n\n**For Company Accounts:** \"Add Custom Connector\" is only available for account admins. A non-admin member cannot add a Custom Connector in a Company Account.\n{% endhint %}\n{% endtab %}\n\n{% tab title=\"ChatGPT\" %}\n\n1. For write actions, enable Developer Mode in **Workspace Settings → Permissions & Roles → Connected Data**.\n2. Open **Settings → Apps & Connectors** and create a connector.\n3. Set the name to `Storylane`.\n4. Select OAuth authentication and add the Storylane MCP server URL.\n5. Publish the connector, then authorize it from **Apps & Connectors**.\n\n{% hint style=\"info\" %}\n**Free accounts:** Custom Connector is not available.\n\n**Personal (paid) accounts and company accounts:** Custom Connector is available.\n{% endhint %}\n\n{% hint style=\"warning\" %}\nWrite actions require Developer Mode. It is currently in beta for Business and Enterprise plans. Read-only tools work on all plans.\n{% endhint %}\n{% endtab %}\n\n{% tab title=\"Microsoft Copilot\" %}\n\n1. In Microsoft Copilot Studio, open your agent and go to **Tools → Add tool → New tool**.\n2. Select **Model Context Protocol**.\n3. Paste the Storylane MCP server URL.\n4. Under **Connection**, select **Create new connection**, then sign in to Storylane and approve access.\n5. Save the tool, then publish or roll out the agent to yourself or your team.\n\n{% hint style=\"info\" %}\nUnlike Claude or ChatGPT, Microsoft Copilot doesn't have a direct \"Settings → Connectors\" toggle — MCP servers are added through Copilot Studio and made available via an agent your org publishes or rolls out to users.\n{% endhint %}\n{% endtab %}\n{% endtabs %}\n\n#### 2. M2M authentication\n\nFor Cursor, n8n, custom automations, and any tool that connects via client credentials. Use M2M when you need server-to-server access without a user session.\n\nGo to **Settings → Integrations → MCP** and click **Request credentials** to generate your `client_id` and `client_secret`. Credentials are unique to your account and workspace.\n\n{% hint style=\"warning\" %}\nStore your `client_secret` securely — it is only shown once. If exposed, generate new credentials from **Settings → Integrations → MCP** and revoke the old ones.\n{% endhint %}\n\n**Cursor and similar platforms**\n\nAdd the following to your `mcp.json` file:\n\n```json\n{\n  \"mcpServers\": {\n    \"storylane\": {\n      \"name\": \"Storylane\",\n      \"description\": \"Storylane MCP\",\n      \"url\": \"https://mcp.storylane.io/mcp\"\n    }\n  }\n}\n```\n\n{% hint style=\"info\" %}\nM2M credentials are scoped to your account and workspace. `switch_workspace` and `list_workspaces` are not available to M2M clients for security reasons.\n{% endhint %}\n\n### Available tools\n\nEvery tool is scoped to your workspace. Your AI client only accesses data you can access.\n\n#### Analytics\n\n* `get_demo_analytics` — Get views, unique visitors, session duration, CTA clicks, click-through rate, and completion rate for a demo.\n* `get_accounts` — List companies that engaged with demos, including firmographic and engagement data.\n* `get_leads` — Get leads captured through demo gating forms and their source demos.\n\n#### Demo library\n\n* `list_workspaces` — List workspaces available to the authenticated user.\n* `switch_workspace` — Change the active workspace for the current session.\n* `list_demos` — List demos in the active workspace. Supports filtering and search.\n* `get_demo` — Get a demo's status, last published data, default link, and any labels added to it.&#x20;\n* `get_demo_status` — Get a demo's publish and processing status.\n\n#### Demo creation and editing\n\n* `convert_images_to_demo` — Create an interactive demo from uploaded images or screenshots.\n* `convert_video_to_demo` — Create an interactive demo from a screen recording or video.\n* `add_step` — Add a text, hotspot, or tooltip step to an existing demo.\n* `personalise_demo` — Create an AI-personalized demo variant for a company, persona, or context.\n* `update_demo_settings` — Update a demo's Convert CTA configuration and nav bar capabilities&#x20;\n* `publish_demo` — Publish a draft or updated demo.\n\n#### Hubs\n\n* `list_hubs` — List Demo Hubs in the active workspace.\n* `get_hub` — Get a hub's demos, ordering, branding, and share URL.\n\n#### Links and distribution\n\n* `list_links` — List share links, tracking variables, creation dates, and view counts.\n* `create_link` — Create a trackable demo link with custom variables.\n* `update_link` — Update an existing link's variables, expiry, or access settings.\n\n#### Voices\n\n* `list_voices` — List available AI voices, languages, voice IDs, and preview URLs.\n\n### AI workflows\n\n{% hint style=\"info\" %}\n26 prompts, ready to paste. [Explore the full MCP library here](https://www.storylane.io/mcp).\n{% endhint %}\n\n#### Product marketing\n\n**Create a launch demo from media**\n\n1. Upload screenshots or a screen recording.\n2. Create the demo with `convert_images_to_demo` or `convert_video_to_demo`.\n3. Check completion with `get_demo_status`.\n4. Set the title, description, branding, and gating with `update_demo_settings`.\n5. Publish it with `publish_demo`.\n\nPair this workflow with Linear or Jira. Create demos from completed feature tickets.\n\n**Audit your demo library**\n\n1. List published demos with `list_demos`.\n2. Retrieve analytics for each demo with `get_demo_analytics`.\n3. Rank demos by engagement.\n4. Flag low-performing demos for updates, personalization, or retirement.\n\nTry this prompt:\n\n> List all published demos. Show demos below 40% completion that had no views in 30 days. Rank them worst to best. Recommend an action for each.\n\n#### Sales and account executives\n\n**Prepare a personalized demo before a call**\n\n1. Find relevant demos with `list_demos` and `get_demo`.\n2. Create an account-specific variant with `personalise_demo`.\n3. Publish it with `publish_demo`.\n4. Generate a trackable link with `create_link`.\n\nCombine this workflow with HubSpot or Salesforce. Log the generated link on the contact record.\n\n**Prioritize follow-ups from engagement**\n\n1. Get recent leads with `get_leads`.\n2. Review account activity with `get_accounts`.\n3. Rank accounts by engagement.\n4. Draft follow-up actions for hot, nurturing, and inactive accounts.\n\n#### Sales engineers\n\n**Convert a screen recording into a demo**\n\n1. Convert the recording with `convert_video_to_demo`.\n2. Add missing steps with `add_step`.\n3. Select narration options with `list_voices`.\n4. Publish the demo and create a prospect link.\n\n**Plan an enterprise deal hub**\n\n1. Find persona-relevant demos with `list_demos` and `get_demo`.\n2. Review existing hubs with `list_hubs` and `get_hub`.\n3. Personalize selected demos with `personalise_demo`.\n4. Create a recommended hub structure for the buying committee.\n\n#### Customer success\n\n**Refresh stale onboarding demos**\n\n1. List onboarding and help demos with `list_demos`.\n2. Find low-engagement demos with `get_demo_analytics`.\n3. Prioritize demos to update, recreate, or retire.\n\n**Identify customer expansion signals**\n\n1. Query customer engagement with `get_accounts`.\n2. Check demo-level activity with `get_demo_analytics`.\n3. Flag customers exploring advanced features for follow-up.\n\n#### RevOps and marketing operations\n\n**Send a personalized demo when a deal reaches Demo stage**\n\n1. Trigger the workflow from a CRM stage change.\n2. Find a relevant demo with `list_demos`.\n3. Personalize it using CRM data with `personalise_demo`.\n4. Publish the demo and create a tracked link.\n5. Write the link back to the CRM record.\n\nUse HubSpot, Salesforce, Zapier, or webhook workflows as the trigger.\n\n### Prompt library\n\n26 prompts, ready to paste. Pick one to see what it does and copy the prompt. Swap anything in \\[brackets]. Here are a few of our favorites. [Explore the full MCP library here](https://www.storylane.io/mcp).\n\n### Weekly report → Slack <a href=\"#dtitle\" id=\"dtitle\"></a>\n\nA ranked digest in the same shape each week, with movement called out — not a wall of metrics. Copy and paste the following:\n\n```\nUsing the Storylane MCP, build this week's demo performance report.\n\n1. Pull demo analytics for the last 7 days and compare against the previous 7. Exclude internal team views — I only want real prospect sessions.\n2. Take the top 5 demos by sessions. For each, capture sessions, engagement rate, CTA clicks, and the change vs last week.\n3. Also flag the single biggest mover, even if it sits outside the top 5.\n4. Use demo names, never IDs.\n\nFormat it exactly like this, every week:\n\nStorylane demos — week of [date]\nTop 5 by sessions\n1. [name] — [n] sessions ([+/-]%) · [x]% engaged · [n] CTA clicks\nBiggest mover: [name] [+/-]% — [what changed, one clause]\nWatch: [one demo with a drop or weak progression, one line]\n\nThen post it to [#marketing]. Ignore moves under 10% as noise.\n```\n\n#### Personalized leave-behind\n\nA personalized demo, tracked link, CTA set, and an opener ready to send. Copy and paste the following:\n\n```\nUsing the Storylane MCP, build a leave-behind demo after my call.\n\nCompany: [NAME] — [industry]\nThey cared about: [TOPICS]\nObjection raised: [OBJECTION]\nBase demo: [DEMO NAME]\n\n1. Work on a copy — never edit the original.\n2. Personalize it for this company: emphasize what they cared about, address the objection, keep the tone confident and concise. Tie it to something specific from the call, not just the company name.\n3. Poll until personalization completes.\n4. Publish the copy — it stays a draft with no shareable link until you do.\n5. Set the CTA to \"[Book a follow-up]\" pointing at [MY BOOKING LINK]. Copies inherit the base demo's CTA, so always set this explicitly.\n6. Create a tracked share link with the company name as a variable.\n\nGive me the link plus a two-sentence follow-up opener I can send as-is.\n```\n\n#### Demo attribution\n\nSourcing demos separated from assisting ones, with the gating caveat stated. Copy and paste the following:\n\n```\nUsing the Storylane MCP, show me which demos actually source pipeline.\n\n1. Pull leads from the last 90 days three ways: first-touch (Attributed), multi-touch (Nurtured), and Direct.\n2. Group them by the demo they came from. Use demo names, not IDs.\n3. Rank demos by first-touch leads, but show the full source mix for each.\n\nOutput a table: demo, first-touch, nurtured, direct, total. Then name the best sourcing demo and the best assister separately — they're different things.\n\nImportant: leads only exist where a demo or link is gated. Flag that ungated demos will under-count, rather than implying they underperform.\n```\n\n#### Compare demo performance\n\n> Which Storylane demos had the highest engagement rate over the past 30 days? Show views, completion rate, and CTA clicks for each.\n\nUses `list_demos` and `get_demo_analytics`.\n\n#### Find engaged accounts\n\n> Show accounts that engaged with our demos in the last two weeks. Rank them by total time spent and list the demos they viewed.\n\nUses `get_accounts` and `get_demo_analytics`.\n\n#### Personalize a demo for a prospect\n\n> Find the best demo for a \\[industry] company focused on \\[use case]. Personalize it for \\[Company Name]. Create a tracked link with their name and email.\n\nUses `list_demos`, `personalise_demo`, `publish_demo`, and `create_link`.\n\n#### Create a demo from a recording\n\n> Convert this screen recording into a Storylane demo. When it is ready, add a final step for \\[feature name], then publish it.\n\nUses `convert_video_to_demo`, `get_demo_status`, `add_step`, and `publish_demo`.\n\n#### Audit inactive demos\n\n> List published demos. Flag those with no views in 30 days or completion below 30%. Recommend whether to update, personalize, or retire each one.\n\nUses `list_demos` and `get_demo_analytics`.\n\n#### Review Demo Hubs\n\n> Show every Demo Hub and its demos. Identify hubs missing a demo for the \\[persona] persona. Suggest existing demos to add.\n\nUses `list_hubs`, `get_hub`, and `list_demos`.\n\n#### Follow up with captured leads\n\n> Who submitted a demo gating form this week? Group them by source demo. Draft a personalized follow-up for the top three leads.\n\nUses `get_leads` and `get_demo`.\n\n### FAQ\n\n#### 👉 Do I need technical skills?\n\nNo. After the connector is configured, use plain-language prompts.\n\n#### 👉 Which AI clients are supported?\n\nClaude, ChatGPT, Microsoft Copilot are supported.\n\n#### 👉 Can an agent access another workspace?\n\nNo. Operations use the active workspace. Use `switch_workspace` when you have access to multiple workspaces.\n\n#### 👉 Is my content secure?\n\nMCP access uses OAuth. Your AI client never receives your Storylane password.\n\nYou can revoke access from **Settings → Connectors**. Agents only access workspace data allowed by your permissions.\n\n#### 👉 How do `personalise_demo` and link variables differ?\n\n`personalise_demo` creates a new demo variant with AI-adapted content.\n\n`create_link` adds dynamic viewer variables to a tracked URL. It does not modify the demo.\n\n#### 👉 Can I automate MCP workflows?\n\nYes. Trigger workflows with CRM events, Zapier, or other MCP-connected tools.\n\n#### 👉 Does MCP support Hubs?\n\nYou can list hubs and read their structure. Hub creation and editing are coming soon.\n\n#### 👉 Which plan includes MCP?\n\nMCP is available on Starter plan and above. Check **Settings → Integrations → MCP** for access.\n\n\n---\n\n# Agent Instructions\nThis documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.\n\n## Querying This Documentation\nIf you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.\n\nPerform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:\n\n```\nGET https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/mcp.md?ask=<question>&goal=<endgoal>\n```\n\n`ask` is the immediate question: it should be specific, self-contained, and written in natural language.\n`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.\n\nThe response will contain a direct answer to the question and relevant excerpts and sources from the documentation.\n\nUse this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.\n","type":"text"}]}
read /tmp/integrations-demo-suite_integrations-and-data-flow_cross-frame-events.md{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are av…ctrl+o
{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/cross-frame-events.md).\n\n# Cross-Frame Events\n\nSend demo analytic events cross origin\n\nYou can now send Storylane demo analytics cross-frame to the hosting website. If you are embedding Storylane demo on your webpage, then real-time analytics events can now be subscribed on your webpage.  Some use cases can be:\n\n* Launching chat window based on user action in the demo\n* Displaying demo-booking calendar based on user action\n* Combine Storylane analytics with additional user info and send to your analytics stack\n\n## **Implementation**\n\nWhen Storylane demo is embedded on a webpage then each user interaction will trigger an event that will be sent through [`window.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). Below is an example of an event payload sent from Storylane demo iframe\n\n```json\n//Storylane sends these postMessage events from iframe to the parent window\nwindow.parent.postMessage(\n{\n  message: 'storylane-demo-event',\n  payload: {\n    event: 'step_view';\n    demo: {\n      id: string;\n      url: string;\n    };\n    step: {\n      id: string;\n      index: number; // starts with 1  \n    };\n    flow: {\n      id: string;\n      name: string;\n    };\n   }\n },\n'*'\n)\n\n```\n\nYou will subscribe to the above events like below on your webpage so it can be processed&#x20;\n\n```json\nwindow.addEventListener('message', (event) => {\n  if (event.data.message === 'storylane-demo-event') {\n    //do something here based on event type\n    // 1. send event to GA, Amplitude, mixpanel etc\n    // 2. open chat window\n    // 3. open meeting calendar ..or anything else\n  }\n})\n```\n\nBelow are different <mark style=\"color:blue;\">`event.data.payload`</mark>for different analytic events sent from the Storylane Demo\n\n```json\n// sent when demo loads first time \n{\n  event: 'demo_open';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n}\n\n// sent when a guided step (hotspot, walkthrough, or modal) is displayed\n{\n  event: 'step_view';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  step: {\n    id: string;\n    index: number; // starts with 1  \n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n}\n\n// sent when demo is finished and user went through all steps/flows\n{\n  event: 'demo_finished';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n}\n\n// sent when a lead is captured through form or URL params\n{\n  event: 'lead_identify';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  lead: {\n    email: string;\n    [key: string]: string; // e.g. first_name, last_name, company, etc...\n  }\n}\n\n// sent when the user clicks on the CTA in button to the external URL\n{\n  event: 'open_external_url';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  step: {\n    id: string;\n    index: number; // starts with 1  \n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n  target: {\n    url: string;\n    target: '_blank' | '_self';\n  }\n}\n\n// sent when an item in the checklist is clicked\n{\n  event: 'checklist_item_view';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  checklist_item: {\n    id: string;\n  }\n}\n\n// sent when flow started by user\n// You need to have multiple flows and a checklist configured for this\n{\n  event: 'flow_start';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n}\n\n// sent when the last step in flow was viewed \n{\n  event: 'flow_end';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n}\n\n// sent on every page change\n{\n  event: 'page_view';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  page: {\n    id: string;\n    name: string;\n  }\n}\n\n// sent when the user clicks a primary button in the guide\n{\n  event: 'primary_cta';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  step: {\n    id: string;\n    index: number; // starts with 1  \n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n}\n\n// sent when the user clicks a secondary button in the guide\n{\n  event: 'secondary_cta';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  step: {\n    id: string;\n    index: number; // starts with 1  \n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n}\n\n// sent when the user clicks a convert CTA button in the demo\n{\n  event: 'convert_cta';\n  demo: {\n    id: string;\n    url: string;\n    name: string;\n  };\n  step: {\n    id: string;\n    index: number; // starts with 1  \n  };\n  flow: {\n    id: string;\n    name: string;\n  };\n}\n\n\n```\n\n## **Example of sending events to GA**\n\n{% embed url=\"<https://jsfiddle.net/storylane/Lwotsvdr/>\" %}\n\n\n---\n\n# Agent Instructions\nThis documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.\n\n## Querying This Documentation\nIf you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.\n\nPerform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:\n\n```\nGET https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/cross-frame-events.md?ask=<question>&goal=<endgoal>\n```\n\n`ask` is the immediate question: it should be specific, self-contained, and written in natural language.\n`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.\n\nThe response will contain a direct answer to the question and relevant excerpts and sources from the documentation.\n\nUse this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.\n","type":"text"}]}
Thinking…

I'm going to test the oEmbed endpoint without authentication using the documented example URL, then fetch the Storylane JavaScript embed snippet from their jsfiddle and verify the API endpoints work as expected.

Now I need to extract the pricing information from their plans page — the HTML appears to be mostly empty due to JavaScript rendering, so I'll search the raw HTML for dollar amounts or pricing text.

$ cd /tmp && echo "== oembed"; timeout 20 curl -sS "https://api.storylane.io/oembed/meta?url=https://app.storylane.io/share/ha3eti5gqq2c" | head -c 1500; echo; echo "== api probe (no auth)"; timeout 20 …{"content":[{"text":"== oembed\n{\"type\":\"rich\",\"version\":\"1.0\",\"title\":\"Storylane No-code Editor\",\"provider_name\":\"Storylane\",\"provider_url\":\…ctrl+o
cd /tmp && echo "== oembed"; timeout 20 curl -sS "https://api.storylane.io/oembed/meta?url=https://app.storylane.io/share/ha3eti5gqq2c" | head -c 1500; echo; echo "== api probe (no auth)"; timeout 20 curl -sS -w "\n%{http_code}\n" "https://api.storylane.io/api/connect/v1/workspaces/demo/demos" | head -c 800

{"content":[{"text":"== oembed\n{\"type\":\"rich\",\"version\":\"1.0\",\"title\":\"Storylane No-code Editor\",\"provider_name\":\"Storylane\",\"provider_url\":\"https://www.storylane.io\",\"thumbnail_url\":\"https://app-pages.storylane.io/company/company_d3ab1eef-4a7e-4994-9234-64f19dd6b621/project/project_e4a21c52-c26d-45f2-9977-1770d8a4122b/preview.gif\",\"html\":\"\\u003cdiv\\u003e   \\u003cscript src=\\\"https://js.storylane.io/js/v1/storylane.js\\\"\\u003e\\u003c/script\\u003e   \\u003cdiv class=\\\"sl-embed\\\" style=\\\"position:relative;padding-bottom:calc(56.32% + 26px);width:100%;height:0;transform:scale(1)\\\"\\u003e     \\u003ciframe class=\\\"sl-demo\\\" src=\\\"https://app.storylane.io/demo/ha3eti5gqq2c\\\" allow=\\\"fullscreen\\\" style=\\\"position:absolute;top:0;left:0;width:100%;height:100%;border:none;\\\"\\u003e\\u003c/iframe\\u003e   \\u003c/div\\u003e \\u003c/div\\u003e \",\"width\":750,\"height\":448}\n== api probe (no auth)\n{\"error\":\"API Key is missing, invalid or expired. Please contact support to obtain a new API Key.\"}\n401\n","type":"text"}]}
$ cd /tmp && grep -o -E '(\$[0-9,]+[^<"]{0,30})' plans.html | sort | uniq -c | sort -rn | head -40; echo ---; grep -o -iE '(per (month|seat|user|year)|billed (annually|monthly)|/mo|Free plan|Starter|Gro…{"content":[{"text":" 5 $40\n 2 $50'\n 2 $100\n 2 $1.3M\n 2 $0\n 1 $625'\n 1 $625\n 1 $500'\n 1 $500\n 1 $50';…ctrl+o
cd /tmp && grep -o -E '(\$[0-9,]+[^<"]{0,30})' plans.html | sort | uniq -c | sort -rn | head -40; echo ---; grep -o -iE '(per (month|seat|user|year)|billed (annually|monthly)|/mo|Free plan|Starter|Growth|Premium|Enterprise)[^<>{}"]{0,60}' plans.html | sort | uniq -c | sort -rn | head -40

{"content":[{"text":"      5 $40\n      2 $50'\n      2 $100\n      2 $1.3M\n      2 $0\n      1 $625'\n      1 $625\n      1 $500'\n      1 $500\n      1 $50';\n      1 $50'))\n      1 $50\n      1 $40+ Add-on)\n      1 $40';\n      1 $40'))\n      1 $40'\n      1 $3000\n      1 $2000\n      1 $1500'\n      1 $1500\n      1 $125';\n      1 $125'))\n      1 $125'\n      1 $1200'\n      1 $1200\n      1 $100+ Add-on)\n      1 $100';\n      1 $100'))\n---\n      8 Enterprise\n      7 starter\n      7 Premium\n      7 Growth\n      6 Starter\n      6 Per month\n      5 growth\n      5 /mo\n      4 Enterprises\n      3 enterprise\n      2 starterSeat.forEach((item) =\n      2 premium\n      2 growthPremiumSeat.forEach((item) =\n      2 growth-and-demand-gen\n      2 Starter +\n      2 Premium +\n      2 Growth +\n      2 Growth &amp; Demand Gen\n      2 Free plan includes\n      2 Enterprise Grade Security\n      2 /month\n      1 starter_tab\n      1 starterSeat.textContent = '$50'\n      1 starterSeat = document.querySelectorAll('[starter-seat]')\n      1 premium is_repx\n      1 growthPremiumSeat.textContent = '$125'\n      1 growthPremiumSeat = document.querySelectorAll('[growth-premium-sea\n      1 growth is_repx\n      1 Starter.textContent = '$50'\n      1 Starter.textContent = '$40'\n      1 Starter, Demo Chapters, Account Reveal, HubSpot & Zapier Integratio\n      1 Starter = document.querySelector('[pricing-starter]')\n      1 Premium.textContent = '$1500'\n      1 Premium.textContent = '$1200'\n      1 Premium, plus\n      1 Premium, Salesforce Integration, SSO, Whitelabel Demo URL, Presente\n      1 Premium = document.querySelector('[pricing-premium]')\n      1 Growth.textContent = '$625'\n      1 Growth.textContent = '$500'\n      1 Growth, plus\n","type":"text"}]}
Thinking…

The user hasn't asked anything substantive yet — the last message was just the system instructions block establishing that I'm an expert software engineer helping the user solve problems. There's no actual task or question to work on. My response was simply an acknowledgment that I understood the instructions. There was no extended reasoning process to reproduce; I just confirmed I'd follow the guidance and waited for an actual request.

$ cd /tmp && grep -o -E 'Free plan includes.{0,600}' plans.html | head -2; echo ===; grep -o -E '(pricing-starter|pricing-growth|pricing-premium)[^;]{0,200}' plans.html | head; echo ===; grep -o -E 'tex…{"content":[{"text":"Free plan includes</div><div data-wf--pricing-pointer-with-tooltip--variant=\"dark\" class=\"pricing_pointer has-tooltip\"><div class=\"che…ctrl+o
cd /tmp && grep -o -E 'Free plan includes.{0,600}' plans.html | head -2; echo ===; grep -o -E '(pricing-starter|pricing-growth|pricing-premium)[^;]{0,200}' plans.html | head; echo ===; grep -o -E 'textContent = .\$[0-9]+.;[^;]{0,80}' plans.html | head -20; echo === ADDONS; grep -o -E '\$[0-9]+\+ Add-on\)[^<]{0,120}' plans.html | head

{"content":[{"text":"Free plan includes</div><div data-wf--pricing-pointer-with-tooltip--variant=\"dark\" class=\"pricing_pointer has-tooltip\"><div class=\"check_pointer_flex\"><div class=\"icon-1-25 is_pointer\"><img src=\"https://cdn.prod.website-files.com/60aacdcdcc32cf61b05512fb/68e0cd40f71391dc02bf4a3f_circle-check%2C%20check%20radio%2C%20circle%2C%20checkbox%2C%20check%2C%20checkmark%2C%20confirm.svg\" loading=\"lazy\" alt=\"\"/></div><div class=\"pricing_item_pointer_text text-16px is_625059 w-variant-4b2b5ef8-81c1-6388-a4cd-9ca2c7bf5439\">1 Seat</div></div></div><div data-wf--pricing-pointer-with-tooltip--variant=\"dark\" class=\"pricing_poi\nFree plan includes</div><div data-wf--pricing-pointer-with-tooltip--variant=\"dark\" class=\"pricing_pointer has-tooltip\"><div class=\"check_pointer_flex\"><div class=\"icon-1-25 is_pointer\"><img src=\"https://cdn.prod.website-files.com/60aacdcdcc32cf61b05512fb/68e0cd40f71391dc02bf4a3f_circle-check%2C%20check%20radio%2C%20circle%2C%20checkbox%2C%20check%2C%20checkmark%2C%20confirm.svg\" loading=\"lazy\" alt=\"\"/></div><div class=\"pricing_item_pointer_text text-16px is_625059 w-variant-4b2b5ef8-81c1-6388-a4cd-9ca2c7bf5439\">1 Seat</div></div></div><div data-wf--pricing-pointer-with-tooltip--variant=\"dark\" class=\"pricing_poi\n===\npricing-starter]')\npricing-growth]')\npricing-premium]')\n===\ntextContent = '$40';\ntextContent = '$50';\ntextContent = '$100';\ntextContent = '$125';\n=== ADDONS\n$40+ Add-on)\n$100+ Add-on)\n","type":"text"}]}
$ cd /tmp && timeout 20 curl -sSL "https://docs.storylane.io/llms.txt" -o llms.txt; grep -i -E "pricing|plan|quick|api|mcp|embed|track" llms.txt | head -40; wc -l llms.txt{"content":[{"text":"- [Quick Start Guide](https://docs.storylane.io/quick-start-guide.md): Ready to build your first demo? Create a Storylane account and follo…ctrl+o
{"content":[{"text":"- [Quick Start Guide](https://docs.storylane.io/quick-start-guide.md): Ready to build your first demo? Create a Storylane account and follow these steps to get started!\n- [Recording HTML Demos](https://docs.storylane.io/recording-demos/recording-html-demos.md): Create responsive, editable interactive demos. HTML demos are available Growth plan onwards.\n- [Embed on Website or Product](https://docs.storylane.io/sharing-demos/embed-on-website-or-product.md): Embedding interactive demos are a proven way to engage website visitors and drive conversions.\n- [Share via Extension](https://docs.storylane.io/sharing-demos/share-link/share-via-extension.md): Share your demo directly from the Storylane Chrome extension — quick and seamless.\n- [Share via HubSpot](https://docs.storylane.io/sharing-demos/share-link/share-via-hubspot.md): Track and share Storylane demos directly from your HubSpot account\n- [Capture Leads](https://docs.storylane.io/analytics-and-performance/capture-leads.md): Identify leads and track analytics from your interactive demo\n- [Lead Attribution](https://docs.storylane.io/analytics-and-performance/lead-attribution.md): It's incredibly easy to track a visitor's journey from anonymous viewers to engaged leads on Storylane.\n- [Tracking and Analyzing](https://docs.storylane.io/analytics-and-performance/tracking-and-analyzing.md): Storylane offers powerful insights into your demo performance.\n- [Understanding Intent](https://docs.storylane.io/analytics-and-performance/tracking-and-analyzing/understanding-intent.md): Intent helps you quickly understand how seriously buyers are engaging with your demos.\n- [Integrations & Data Flow](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow.md): Connect Storylane to your CRM, sales tools, and analytics stack to send demos and track engagement.\n- [HubSpot Website Embeds](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/hubspot/hubspot-website-embeds.md)\n- [MCP](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/mcp.md): Storylane MCP connects Claude, ChatGPT, Microsoft Copilot, Cursor,  and other MCP-compatible AI clients to your account. Personalize, analyze, and publish demos without leaving your AI client.\n- [Zapier](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/zapier.md): Send leads to 5000+ apps through Zapier\n- [Gong](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/gong.md): Embed interactive demo links directly in your Gong emails and templates\n- [Outreach](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/outreach.md): Embed interactive demo links directly in your Outreach emails with automatic lead tracking and engagement insights.\n- [Chili Piper](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/chili-piper.md): Embed live Chili Piper scheduling links directly into your interactive demos\n- [Calendly](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/calendly.md): Embed live Calendly scheduling links directly into your interactive demos\n- [Google Tag Manager](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/google-tag-manager.md): Integrate Google Tag Manager (GTM) with your Storylane demos - you can track user interactions, pass event data to platforms like Google Analytics, and improve demo attribution.\n- [Webhooks](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/webhooks.md): Webhooks are available starting from the Starter plan.\n- [External API](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/external-api.md)\n- [User activity](https://docs.storylane.io/account/user-activity.md): Track how your team is using Storylane - and who’s driving results.\n- [How to Add/Embed a Storylane Demo in a Zendesk Article](https://docs.storylane.io/tips-and-tricks/how-to-add-embed-a-storylane-demo-in-a-zendesk-article.md)\n- [Is it possible to connect a LinkedIn tracking pixel to Storylane?](https://docs.storylane.io/tips-and-tricks/is-it-possible-to-connect-a-linkedin-tracking-pixel-to-storylane.md)\n- [Customizing and Tracking Demo URL Links](https://docs.storylane.io/tips-and-tricks/customizing-and-tracking-demo-url-links.md): Storylane allows you to create personalized and trackable demo experiences using various parameters in your demo links. Below is a guide to the available options:\n- [Managing Your Billing](https://docs.storylane.io/trust-and-security/faqs/managing-your-billing.md): Everything you need to stay in control of your Storylane plan, seats, and invoices — all without leaving the app.\n- [Lead Forms And Embeds](https://docs.storylane.io/hubs-docs/creating-hubs/lead-forms-and-embeds.md): Options configuration to make the most of your Hubs\n- [HubSpot Website Embeds](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/hubspot/hubspot-website-embeds.md)\n- [Zapier](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/zapier.md): Send leads to 5000+ apps through Zapier\n- [Gong](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/gong.md): Embed interactive demo links directly in your Gong emails and templates\n- [Outreach](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/outreach.md): Embed interactive demo links directly in your Outreach emails with automatic lead tracking and engagement insights.\n- [Chili Piper](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/chili-piper.md): Embed live Chili Piper scheduling links directly into your interactive demos\n- [Calendly](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/calendly.md): Embed live Calendly scheduling links directly into your interactive demos\n- [Google Tag Manager](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/google-tag-manager.md): Integrate Google Tag Manager (GTM) with your Storylane demos - you can track user interactions, pass event data to platforms like Google Analytics, and improve demo attribution.\n- [Webhooks](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/webhooks.md): Webhooks are available starting from the Starter plan.\n- [External API](https://docs.storylane.io/hubs-docs/integrations-hub/integrations/external-api.md)\n- [Creating a new agent](https://docs.storylane.io/repx-chat/setting-up-repx/creating-a-new-agent.md): This is a quick guide on setting up a RepX chat agent for your website.\n- [Launching on your website](https://docs.storylane.io/repx-chat/setting-up-repx/launching-on-your-website.md): Quick guide to going live with RepX on your website\n- [Zapier](https://docs.storylane.io/repx-chat/zapier.md): With the Zapier integration, you can take RepX conversation data and push it to any product in your enterprise tech stack.\n- [September 14, 2026](https://docs.storylane.io/repx-updates/september-14-2026.md): Hey Storylaners! Marketo joins the list of apps RepX integrates with. RepX conversations now land on the person record alongside everything else you track on Marketo.\n- [July 28, 2026](https://docs.storylane.io/repx-updates/july-28-2026.md): Hey Storylaners! Quick replies that make it easier for your visitors to respond and Zapier, webhooks to push conversation data anywhere.\n331 llms.txt\n","type":"text"}]}
Thinking…

I need to pull the tracking parameters from the docs and check the billing FAQ for pricing details, then set up a local example with a static HTML page that embeds the demo using the Storylane JS SDK and handles cross-frame messaging to capture leads and events.

$ cd /tmp && timeout 20 curl -sSL "https://docs.storylane.io/tips-and-tricks/customizing-and-tracking-demo-url-links.md" -o tracking.md && head -120 tracking.md{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are av…ctrl+o
{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.storylane.io/tips-and-tricks/customizing-and-tracking-demo-url-links.md).\n\n# Customizing and Tracking Demo URL Links\n\nStorylane allows you to create personalized and trackable demo experiences using various parameters in your demo links. Below is a guide to the available options:\n\n***\n\n## 1. **Email Tracking**\n\nIf you'd like to track who is viewing the demo without requiring them to complete a form, you can append an email address to the demo link.\n\n**Example:**\n\n```\nhttps://app.storylane.io/demo/tc1vupmgcxtd?email=maria@storylane.io\n```\n\n**Use Case:**\n\n* Great for tracking specific users who access the demo via a direct link or email campaign.\n\n***\n\n## 2. **Personalization with Tokens**\n\nTo personalize your demo, you can use tokens. Add as many tokens as you need, and they will be automatically populated when sending an email campaign.\n\n**Example:**\n\n```\nhttps://app.storylane.io/demo/tc1vupmgcxtd?token[first_name]=Maria&token[last_name]=Nicolae\n```\n\n**Use Case:**\n\n* Perfect for creating tailored demo experiences by including the viewer's name or other relevant details.\n\n**Learn More:** [Personalizing Demos with Tokens](/editing-demos/personalizing-demos.md)\n\n***\n\n## 3. **UTM Parameters for Lead Tracking**\n\nUse UTM parameters to track the source of your leads. These parameters are visible in Storylane analytics and can also be sent to your CRM. Storylane captures UTM data from both the demo link and the page where the demo is embedded.\n\n**Example:**\n\n![](https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2FTCOvbEkZqSV1ZEcMmjKc%2Fimage.png?alt=media\\&token=REDACTED)\n\n```\nhttps://app.storylane.io/demo/tc1vupmgcxtd?utm_source=googleads\n```\n\n**Use Case:**\n\n* Ideal for understanding the effectiveness of marketing campaigns and lead sources.\n\n***\n\n## 4. **Redirect to a Specific Chapter**\n\nRedirect viewers to a specific chapter within the demo by specifying the chapter number at the end of the link.\n\n**Example:**\n\n```\nhttps://app.storylane.io/demo/tc1vupmgcxtd?chapter=2\n```\n\n**Use Case:**\n\n* Useful when directing users to a particular part of the demo, such as a specific feature or workflow.\n\n***\n\n## 5. **Hide Guided Steps**\n\nFor a sandbox experience without guided steps, use the `hide_all=true` parameter.\n\n**Example:**\n\n```\nhttps://app.storylane.io/demo/tc1vupmgcxtd?hide_all=true\n```\n\n**Use Case:**\n\n* Best for users who want to explore the demo independently without guidance.\n\n***\n\nFor more details or assistance, contact our support team at <support@storylane.io>.\n\n\n---\n\n# Agent Instructions\nThis documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.\n\n## Querying This Documentation\nIf you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.\n\nPerform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:\n\n```\nGET https://docs.storylane.io/tips-and-tricks/customizing-and-tracking-demo-url-links.md?ask=<question>&goal=<endgoal>\n```\n\n`ask` is the immediate question: it should be specific, self-contained, and written in natural language.\n`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.\n\nThe response will contain a direct answer to the question and relevant excerpts and sources from the documentation.\n\nUse this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.\n","type":"text"}]}
$ cd /tmp && grep -o -E 'class="[^"]*(price|amount|plan_name|tier)[^"]*"[^>]*>[^<]{1,40}' plans.html | sed 's/class=//' | head -60{"content":[{"text":"(no output)","type":"text"}]}
Thinking…

I need to figure out the pricing structure more clearly. Let me search the HTML for specific keywords like "per month," "billed annually," "Free," "seats," and add-on pricing to get concrete numbers. I could also check archive.org or Google cache, but first I'll try extracting the pricing tier names and their associated amounts from the plans section using Python to find where "Growth" and pricing values appear together.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"0 Storylane Product Demo Software Plans: Startups, SMB & Enterprises\n","type":"text"}]}ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S)
s=re.sub(r'<style.*?</style>','',s,flags=re.S)
t=re.sub(r'<[^>]+>','\n',s)
t=html.unescape(t)
lines=[l.strip() for l in t.split('\n') if l.strip()]
out=[]
for l in lines:
    if out and out[-1]==l: continue
    out.append(l)
# find index of first 'Free' plan region
for i,l in enumerate(out):
    if re.search(r'per month|/mo|Seat|seat|annually|Free|Starter|Growth|Premium|Enterprise|\$\d',l):
        print(i,l[:160])
EOF

Thinking…

The page appears to be entirely JavaScript-rendered with Webflow and Rive, so the static text isn't available in the HTML. I should check if the pricing data is embedded in the script payloads, or I could search for those dollar amounts like "$625" and "$500" in the scripts along with any feature names nearby, and look for a JSON configuration file.

$ cd /tmp && grep -o -E '.{200}\$625.{200}' plans.html | head -3; echo ====; grep -o -E '.{150}Add-on.{150}' plans.html | head -4{"content":[{"text":"====\n\" class=\"text-16px text-color-type-secondary\">Creator Seats</div><div class=\"div-block-196\"><div>1</div></div><div class=\"div-b…ctrl+o
{"content":[{"text":"====\n\" class=\"text-16px text-color-type-secondary\">Creator Seats</div><div class=\"div-block-196\"><div>1</div></div><div class=\"div-block-196\"><div>1 ($40+ Add-on)</div></div><div class=\"div-block-196\"><div>5 ($100+ Add-on)</div></div><div class=\"div-block-196\"><div>10</div></div><div class=\"div-block-196\"><div\n2C%20check%20radio%2C%20circle%2C%20checkbox%2C%20check%2C%20checkmark%2C%20confirm.svg\" loading=\"lazy\" alt=\"\"/></div><div class=\"div-block-196\"><div>Add-on</div></div><div class=\"div-block-196\"><div>Custom</div></div><div class=\"div-block-196\"><div>Custom</div></div></div><div class=\"div-block-192-copy\">\n2C%20check%20radio%2C%20circle%2C%20checkbox%2C%20check%2C%20checkmark%2C%20confirm.svg\" loading=\"lazy\" alt=\"\"/></div><div class=\"div-block-196\"><div>Add-on</div></div><div class=\"div-block-196\"><img src=\"https://cdn.prod.website-files.com/60aacdcdcc32cf61b05512fb/6ab2b6f27c0f131e78383e01_circle-check%2C%\n2C%20check%20radio%2C%20circle%2C%20checkbox%2C%20check%2C%20checkmark%2C%20confirm.svg\" loading=\"lazy\" alt=\"\"/></div><div class=\"div-block-196\"><div>Add-on</div></div><div class=\"div-block-196\"><img src=\"https://cdn.prod.website-files.com/60aacdcdcc32cf61b05512fb/6ab2b6f27c0f131e78383e01_circle-check%2C%\n","type":"text"}]}
Thinking…

I'm looking at a feature comparison table with four columns that seems to represent different pricing tiers. I'll parse the table structure in Python to extract the text content and identify the column headers, which are likely Free, Starter, Growth, and Premium or Enterprise options.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"7c8372fd1df2\" data-wf-site=\"60a\n","type":"text"}]}ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
s=re.sub(r'<script.*?</script>','',s,flags=re.S)
s=re.sub(r'<style.*?</style>','',s,flags=re.S)
# find region around 'Creator Seats'
i=s.find('Creator Seats')
print(s[i-3000:i+200].replace('><','>\n<')[-4000:])
EOF

Thinking…

The find operation returned -1 because the string wasn't located after script removal, so the slice calculation went wrong. I'll try a different approach—searching the original string first, then slicing from there before removing the scripts.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"idx 457594\n0827C49.6476 18.7746 49.1529 18.0197 49.1529 17.1716C49.1529 16.2273 49.8143 15.5177 50.829 15.1191C49.933 14.5056 49.3903 13.5…ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('Creator Seats')
print("idx",i)
seg=s[max(0,i-6000):i+3000]
seg=re.sub(r'<img[^>]*>','',seg)
t=re.sub(r'<[^>]+>',' | ',seg)
t=html.unescape(t)
t=re.sub(r'(\s*\|\s*)+',' | ',t)
print(t[-5000:])
EOF

{"content":[{"text":"idx 457594\n0827C49.6476 18.7746 49.1529 18.0197 49.1529 17.1716C49.1529 16.2273 49.8143 15.5177 50.829 15.1191C49.933 14.5056 49.3903 13.584 49.3903 12.4503C49.3903 10.231 51.349 8.8853 54.0878 8.8853C55.3624 8.8853 56.5411 9.21612 57.3918 9.80698C57.9825 8.98143 59.0198 8.31995 60.1531 8.31995V10.5873C59.5878 10.5873 58.9011 10.6806 58.3584 10.87C58.641 11.3195 58.8078 11.8623 58.8078 12.4503C58.8021 14.6216 56.8209 15.9644 54.0821 15.9644ZM53.8475 22.0088C55.9249 22.0088 57.4342 21.5366 57.4342 20.5217C57.4342 19.8376 56.8689 19.5775 55.9249 19.5775H52.4541C51.6995 19.5775 51.0862 19.8856 51.0862 20.6404C51.0862 21.5847 52.1715 22.0088 53.8475 22.0088ZM54.1076 10.607C53.0448 10.607 52.3128 11.2431 52.3128 12.4475C52.3128 13.6519 53.0448 14.2879 54.1076 14.2879C55.1929 14.2879 55.9249 13.6519 55.9249 12.4475C55.9249 11.2431 55.1929 10.607 54.1076 10.607Z\" fill=\"#13010C\"/> | @keyframes move {\n  from {\n    transform: translateX(0);\n  }\n  to {\n    transform: translateX(calc(-100% - 1.5rem));\n  }\n}\n.move {\n  animation: move 70s linear infinite;\n}\n.reverse {\n  animation: move 70s linear infinite reverse;\n}\n.pattern-lines-reverse {\n  position: relative; /* needed for the pseudo-element */\n}\n.pattern-lines-reverse::before {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n  z-index: 0;\n  --pattern-lines: #D2CEC2;\n  background-image: repeating-linear-gradient(\n    135deg,\n    var(--pattern-lines) 0,\n    var(--pattern-lines) 1px,\n    transparent 0,\n    transparent 50%\n  );\n  background-size: 10px 10px;\n  -webkit-mask-image: repeating-linear-gradient(\n    45deg,\n    black 0px,\n    black 3px,\n    transparent 3px,\n    transparent 6px\n  );\n  mask-image: repeating-linear-gradient(\n    45deg,\n    black 0px,\n    black 3px,\n    transparent 3px,\n    transparent 6px\n  );\n  -webkit-mask-size: 8px 8px;\n  mask-size: 8px 8px;\n} | Compare plans in detail | Compare | Free | Start for free | Starter | Request trial | Growth | Request trial | Premium | Request trial | Enterprise | Talk to sales | Usage | Creator Seats | 1 | 1 ($40+ Add-on) | 5 ($100+ Add-on) | 10 | Custom | Presenter Seats | Add-on | Custom | Custom | Viewer Seats | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited | Published Demos | 1 | Unlimited | Unlimited | Unlimited | Unlimited | Multi-teams | <img src=\"https://cdn.prod.website-files.com/60aacdcdcc32cf61b0551\n","type":"text"}]}
Thinking…

I'm looking at the pricing tiers and their seat allocations across the plans. Let me parse out the full feature matrix for each tier and cross-reference it with the pricing data I found in the scripts—there's a discrepancy where Premium shows $1500 in the data but I'm seeing different values listed.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"Compare plans in detail | Compare | Free | Start for free | Starter | Request trial | Growth | Request trial | Premium | Request trial | En…ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('Compare plans in detail')
seg=s[i:i+40000]
seg=re.sub(r'<img[^>]*>','[✓]',seg)
t=re.sub(r'<[^>]+>',' | ',seg)
t=html.unescape(t)
t=re.sub(r'(\s*\|\s*)+',' | ',t)
print(t[:9000])
EOF

{"content":[{"text":"Compare plans in detail | Compare | Free | Start for free | Starter | Request trial | Growth | Request trial | Premium | Request trial | Enterprise | Talk to sales | Usage | Creator Seats | 1 | 1 ($40+ Add-on) | 5 ($100+ Add-on) | 10 | Custom | Presenter Seats | [✓] | [✓] | Add-on | Custom | Custom | Viewer Seats | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited | Published Demos | 1 | Unlimited | Unlimited | Unlimited | Unlimited | Multi-teams | [✓] | [✓] | [✓] | [✓] | [✓] | Demo Formats | Interactive Demos (Images & Video) | [✓] | [✓] | [✓] | [✓] | [✓] | Interactive Demos (HTML) | [✓] | [✓] | [✓] | [✓] | [✓] | Sandbox Demos | [✓] | [✓] | [✓] | Add-on | [✓] | Hubs | [✓] | [✓] | Add-on | [✓] | [✓] | Demo Capture | Chrome Extension | [✓] | [✓] | [✓] | [✓] | [✓] | Multi-tab Capture | [✓] | [✓] | [✓] | [✓] | [✓] | Desktop App (Mac, Windows) | [✓] | [✓] | [✓] | [✓] | [✓] | Figma Plugin | [✓] | [✓] | [✓] | [✓] | [✓] | Media to Demo | [✓] | [✓] | [✓] | [✓] | [✓] | Screen Resize | [✓] | [✓] | [✓] | [✓] | [✓] | Mobile-friendly Demos | [✓] | [✓] | [✓] | [✓] | [✓] | Demo Editor | AI Editor | [✓] | [✓] | [✓] | [✓] | [✓] | Guide Modals (Hotspot, Tooltips, Text & Media Modals) | [✓] | [✓] | [✓] | [✓] | [✓] | Lead Forms | [✓] | [✓] | [✓] | [✓] | [✓] | Blur, Crop, Annotate | [✓] | [✓] | [✓] | [✓] | [✓] | Track, Pan, Zoom | [✓] | [✓] | [✓] | [✓] | [✓] | Chapters / Checklists | [✓] | [✓] | [✓] | [✓] | [✓] | Convert CTA | [✓] | [✓] | [✓] | [✓] | [✓] | Video Editor | [✓] | [✓] | [✓] | [✓] | [✓] | Search & Replace (HTML Demos) | [✓] | [✓] | [✓] | [✓] | [✓] | Dark/Light Mode | [✓] | [✓] | [✓] | [✓] | [✓] | Auto-link Screens (HTML Demos) | [✓] | [✓] | [✓] | [✓] | [✓] | Text Triggers | [✓] | [✓] | [✓] | [✓] | [✓] | Sales Notes | [✓] | [✓] | [✓] | <img src=\"https://cdn.prod.website-files.com/60aacdcdcc32cf61b05512fb/6ab2b6f27c0f131e78383e01_circle-check%2C%20check%20radio%2C%20circle%2C%20checkbox%2C%20check%2C%20checkmark%2C%20confirm-1.svg\" loading=\"lazy\" a\n","type":"text"}]}
$ cd /tmp && grep -o -E '.{400}\$1500.{200}' plans.html | head -2{"content":[{"text":"(no output)","type":"text"}]}
Thinking…

I'm noticing some discrepancies in the pricing data I extracted earlier—there are conflicting values for the Premium and Growth tiers, and I need to verify whether those dollar amounts came from inline scripts or static HTML content. Let me search more carefully for where these prices actually appear on the page.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"--- 1500 @5573: , \"priceCurrency\": \"USD\", \"unitText\": \"MONTH\" }, \"description\": \"5 Seats, HTML Demo Editor, AI Suite - Growth, P…ctrl+o
cd /tmp && python3 - <<'EOF'
import re
s=open('plans.html').read()
for pat in ['1500','1200','625','500','pricing-starter','pricing-growth','pricing-premium','Per month','per month','annual']:
    for m in re.finditer(re.escape(pat),s):
        a=max(0,m.start()-300); b=m.end()+300
        seg=re.sub(r'<[^>]+>',' ',s[a:b])
        seg=re.sub(r'\s+',' ',seg)
        print(f"--- {pat} @{m.start()}: {seg}")
        break
EOF

{"content":[{"text":"--- 1500 @5573: , \"priceCurrency\": \"USD\", \"unitText\": \"MONTH\" }, \"description\": \"5 Seats, HTML Demo Editor, AI Suite - Growth, Personalization Tokens, A/B Testing, Multi-teams, Dedicated CSM\" }, { \"@type\": \"Offer\", \"name\": \"Premium\", \"price\": \"1500\", \"priceCurrency\": \"USD\", \"priceSpecification\": { \"@type\": \"UnitPriceSpecification\", \"price\": \"1500\", \"priceCurrency\": \"USD\", \"unitText\": \"MONTH\" }, \"description\": \"10 Seats, AI Suite - Premium, Salesforce Integration, SSO, Whi\n--- 1200 @110939: b05512fb/69354604607afa413b203438_circle-info%2C%20info%20circle%2C%20tooltip%2C%20information%20(4).svg\" class=\"tooltip_wrapper_overlay_icon\"/> Premium $1200 Per month Request trial <div class=\"pricing_list_top_box list-v3 is\n--- 625 @5134: rency\": \"USD\", \"unitText\": \"MONTH\" }, \"description\": \"1 Seat, Unlimited Published Demos, AI Suite - Starter, Demo Chapters, Account Reveal, HubSpot & Zapier Integration, Custom Branding\" }, { \"@type\": \"Offer\", \"name\": \"Growth\", \"price\": \"625\", \"priceCurrency\": \"USD\", \"priceSpecification\": { \"@type\": \"UnitPriceSpecification\", \"price\": \"625\", \"priceCurrency\": \"USD\", \"unitText\": \"MONTH\" }, \"description\": \"5 Seats, HTML Demo Editor, AI Suite - Growth, Personalization T\n--- 500 @2825: tic.com\" rel=\"preconnect\" crossorigin=\"anonymous\"/> WebFont.load({ google: { families: [\"Montserrat:100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic\",\"Familjen Grotesk:300,400,500,600,700\",\"Poppins:300,400,500,600,700\",\"Roboto:300,400,500,600,700\"] }}); !function(o,c){var n=c.documentElement,t=\" w-mod-\";n.className+=t+\"js\",(\"ontouchstart\"in\n--- pricing-starter @1144046: sdelivr.net/gh/hadiaslam-repo/pro-webflow-components/pro-tab-menu.js\"> window.addEventListener('DOMContentLoaded', (event) => { const pricingToggle = () => { const toggleButton = document.querySelector('[toggle-button]') const pricingStarter = document.querySelector('[pricing-starter]') const pricingGrowth = document.querySelector('[pricing-growth]') const pricingPremium = document.querySelector('[pricing-premium]') const yearlyText = document.querySelectorAll('[yearly-text]') const starterSeat = document.querySelectorAll('[starter-seat]') const growthPremiu\n--- pricing-growth @1144116:  window.addEventListener('DOMContentLoaded', (event) => { const pricingToggle = () => { const toggleButton = document.querySelector('[toggle-button]') const pricingStarter = document.querySelector('[pricing-starter]') const pricingGrowth = document.querySelector('[pricing-growth]') const pricingPremium = document.querySelector('[pricing-premium]') const yearlyText = document.querySelectorAll('[yearly-text]') const starterSeat = document.querySelectorAll('[starter-seat]') const growthPremiumSeat = document.querySelectorAll('[growth-premium-seat]') toggl\n--- pricing-premium @1144186: t) => { const pricingToggle = () => { const toggleButton = document.querySelector('[toggle-button]') const pricingStarter = document.querySelector('[pricing-starter]') const pricingGrowth = document.querySelector('[pricing-growth]') const pricingPremium = document.querySelector('[pricing-premium]') const yearlyText = document.querySelectorAll('[yearly-text]') const starterSeat = document.querySelectorAll('[starter-seat]') const growthPremiumSeat = document.querySelectorAll('[growth-premium-seat]') toggleButton.addEventListener('click', function () { this.classList.to\n--- Per month @24545: \"0.666667\" stroke-linecap=\"square\"/> Starter $50 Per month Request Trial <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" \n","type":"text"}]}
Thinking…

Looking at the pricing structure, there's a free tier with one seat and one published demo, then Starter at $50/month with unlimited demos and integrations, and Growth at $625/month which appears to be cut off but likely includes additional features.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"Free | $0 None | 1 Seat, 1 Published Demo, Unlimited Demo Views, AI Suite - Free, Lead Capture, Video Recordings, Basic Analytics\nStarter …ctrl+o
cd /tmp && python3 - <<'EOF'
import re,json
s=open('plans.html').read()
m=re.search(r'<script[^>]*type="application/ld\+json"[^>]*>(.*?)</script>',s,re.S)
d=json.loads(m.group(1))
def walk(o):
    if isinstance(o,dict):
        if o.get('@type')=='Offer':
            print(o.get('name'), '| $'+str(o.get('price')), o.get('priceSpecification',{}).get('unitText'), '|', o.get('description'))
        for v in o.values(): walk(v)
    elif isinstance(o,list):
        for v in o: walk(v)
walk(d)
EOF

{"content":[{"text":"Free | $0 None | 1 Seat, 1 Published Demo, Unlimited Demo Views, AI Suite - Free, Lead Capture, Video Recordings, Basic Analytics\nStarter | $50 MONTH | 1 Seat, Unlimited Published Demos, AI Suite - Starter, Demo Chapters, Account Reveal, HubSpot & Zapier Integration, Custom Branding\nGrowth | $625 MONTH | 5 Seats, HTML Demo Editor, AI Suite - Growth, Personalization Tokens, A/B Testing, Multi-teams, Dedicated CSM\nPremium | $1500 MONTH | 10 Seats, AI Suite - Premium, Salesforce Integration, SSO, Whitelabel Demo URL, Presenter Demos, Hubs, Deal Intelligence\nEnterprise | $None None | Custom pricing with AI Suite - Enterprise, Enterprise Grade Security, Professional Services, Private Demo Links, API, Ongoing Coaching\n","type":"text"}]}
Thinking…

I'm noticing the Enterprise tier description mentions API access, but the external-api documentation states it's available on all paid plans—so I should present both perspectives and flag that the comparison table is the authoritative source. Let me look at the API and MCP rows in the plan matrix to see what they actually show.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"MCP -> (Hubs) | [Y] | [Y] | [Y] | [Y] | [Y] | MCP Server | [Y] | [Y] | [Y] | [Y] | [Y] | Personalization | Personalize Guided Steps (Text)…ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('Compare plans in detail')
seg=s[i:i+60000]
seg=re.sub(r'<img[^>]*circle-check[^>]*>','[Y]',seg)
seg=re.sub(r'<img[^>]*>','[N]',seg)
t=re.sub(r'<[^>]+>',' | ',seg)
t=html.unescape(t); t=re.sub(r'(\s*\|\s*)+',' | ',t)
for kw in ['API','MCP','Webhook','Zapier','HubSpot','Salesforce','Analytics','Intent','Attribution','Segment','SSO']:
    for m in re.finditer(re.escape(kw),t):
        print(kw,'->',t[max(0,m.start()-40):m.start()+180].replace('\n',' '))
        print()
EOF

{"content":[{"text":"MCP ->  (Hubs) | [Y] | [Y] | [Y] | [Y] | [Y] | MCP Server | [Y] | [Y] | [Y] | [Y] | [Y] | Personalization | Personalize Guided Steps (Text) | [Y] | [Y] | [Y] | [Y] | [Y] | Personalize HTML Screens (Text, Date, Time, Currencies,\n\n","type":"text"}]}
Thinking…

I'm realizing the keyword matching is only catching exact case-sensitive matches, so "API" might be buried in an image alt text or formatted differently. I need to adjust my search to be case-insensitive and print out the full matrix rows with their labels and surrounding context to find where it actually appears.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"277\n","type":"text"}]}ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('Compare plans in detail')
seg=s[i:i+60000]
seg=re.sub(r'<img[^>]*circle-check[^>]*>','Y',seg)
seg=re.sub(r'<img[^>]*>','N',seg)
t=re.sub(r'<[^>]+>','|',seg)
t=html.unescape(t); t=re.sub(r'(\s*\|\s*)+','|',t)
rows=[r.strip() for r in t.split('|')]
rows=[r for r in rows if r]
print(len(rows))
for idx,r in enumerate(rows):
    if re.match(r'(?i).*(api|webhook|integration|external|connect)',r):
        print(idx, r, '||', rows[idx+1:idx+7])
EOF

Thinking…

I'm noticing the table extends beyond where I expected—there's no "API" row visible in the current range, so I should expand my search window to around 150,000 characters to capture the full table and see all available rows.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"757\n227 | MCP Server || Y|Y|Y|Y|Y\n566 | HubSpot || Y|Y|Y|Y|Y\n572 | Salesforce || Y|Y|Y|Y|Y\n608 | Zapier || Y|Y|Y|Y|Y\n614 | Google Anal…ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('Compare plans in detail')
seg=s[i:i+250000]
seg=re.sub(r'<img[^>]*circle-check[^>]*>','Y',seg)
seg=re.sub(r'<img[^>]*>','N',seg)
t=re.sub(r'<[^>]+>','|',seg)
t=html.unescape(t); t=re.sub(r'(\s*\|\s*)+','|',t)
rows=[r.strip() for r in t.split('|') if r.strip()]
print(len(rows))
for idx,r in enumerate(rows):
    if re.search(r'(?i)\b(api|webhook|zapier|hubspot|salesforce|mcp|segment|gtm|google)\b',r):
        print(idx,'|',r,'||','|'.join(rows[idx+1:idx+6]))
EOF

{"content":[{"text":"757\n227 | MCP Server || Y|Y|Y|Y|Y\n566 | HubSpot || Y|Y|Y|Y|Y\n572 | Salesforce || Y|Y|Y|Y|Y\n608 | Zapier || Y|Y|Y|Y|Y\n614 | Google Analytics || Y|Y|Y|Y|Y\n620 | Segment || Y|Y|Y|Y|Y\n650 | HubSpot App || Y|Y|Y|Y|Y\n656 | Salesforce App || Y|Y|Y|Y|Y\n686 | Google Tag Manager || Y|Y|Y|Y|Y\n","type":"text"}]}
Thinking…

I'm noticing the integrations section marks everything as "Y" which doesn't seem right, especially given the "Add-on" text I spotted earlier—my regex might be converting unchecked icons to checkmarks. Rather than getting bogged down in plan gating details, I'll reference the documentation directly: External API and Webhooks are available on all paid plans, while MCP requires Starter or higher. Let me look at the integration rows around that range to verify the actual access levels.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"[IMG] | [IMG] | [IMG] | Professional Services | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Integrations | HubSpot | [IMG] | [IMG] | [IMG] | [I…ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('Compare plans in detail')
seg=s[i:i+250000]
seg=re.sub(r'<img[^>]*>','[IMG]',seg)
t=re.sub(r'<[^>]+>','|',seg)
t=html.unescape(t); t=re.sub(r'(\s*\|\s*)+','|',t)
rows=[r.strip() for r in t.split('|') if r.strip()]
print(' | '.join(rows[556:700]))
EOF

{"content":[{"text":"[IMG] | [IMG] | [IMG] | Professional Services | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Integrations | HubSpot | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Salesforce | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Marketo | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Pardot | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | FreshSales | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Dynamic365 | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Slack | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Zapier | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Google Analytics | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Segment | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Intercom | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | G2 | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Amplitude | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Chameleon | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | HubSpot App | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Salesforce App | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Gong | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Outreach | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Chilipiper | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Calendly | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Google Tag Manager | [IMG] | [IMG] | [IMG] | [IMG] | [IMG] | Rep | X | AI SDRs trained on your product and playbooks to run discovery, handle objections and convert inbound visitors to sales-ready meetings. | [IMG] | 30-day free trial | [IMG] | .free_pill {\n  border-radius: 999px;\n  animation: freePillPulse 5s cubic-bezier(0.4, 0, 0.2, 1) infinite;\n  will-change: box-shadow;\n}\n\n/* @keyframes freePillPulse {\n  0% {\n    box-shadow: 0 0 0 0 #61053C;\n  }\n\n  50% {\n    box-shadow: 0 0 0 8px #61053C;\n  }\n\n  100% {\n    box-shadow: 0 0 0 0 #61053C;\n  }\n} */ | Growth\n","type":"text"}]}
Thinking…

The image indicators in the matrix are all the same, so I can't distinguish which features are available on the free tier, but that's okay since I have enough pricing information to work with. Now I'm planning the example structure: I'll create a README explaining Storylane's purpose, the architecture of the example, setup instructions for API keys, pricing details, and how to run it by simply opening the HTML file directly without needing a server. The example folder will contain the HTML file for the hosted page.

Building out the interactive demo with inline embedding, popup options, and cross-frame event listening that displays an event log and forwards data to GA. I'm also creating a configuration file that holds the subdomain and demo link value with environment variable overrides, a Node script using only built-in fetch to call the External API for listing demos and managing links with email tokens and engagement reports, an .env.example file for credentials, and possibly a package.json with scripts.

Before finalizing, I need to verify a few things: whether there's an official Storylane npm package, what the JS SDK exposes (checking the sl.js file for the global API like Storylane.Play() and tracking methods), and confirm the embed code format from the oEmbed response. I should also examine how tokens are submitted through sl_token parameters and review the exported functions and message handlers in the Storylane script.

$ cd /tmp && node -e "{"content":[{"text":"Storylane\"in window)return;const t=new U;window.Storylane=t,oe();function e(){var a,s,c,b,h;Y(document),Z(document),te(document);const o=`…ctrl+o
cd /tmp && node -e "
const s=require('fs').readFileSync('sl.js','utf8');
const i=s.lastIndexOf('return');
console.log(s.slice(-2500));
" ; echo "=== npm"; timeout 25 curl -sS "https://registry.npmjs.org/-/v1/search?text=storylane&size=10" | python3 -c "import json,sys; d=json.load(sys.stdin); [print(o['package']['name'],o['package']['version'],'|',o['package'].get('description')) for o in d['objects']]"

{"content":[{"text":"Storylane\"in window)return;const t=new U;window.Storylane=t,oe();function e(){var a,s,c,b,h;Y(document),Z(document),te(document);const o=`\n      <div class=\"sl-ripple-backdrop\"></div>\n      <div class=\"sl-ripple-main\">\n        <div class=\"sl-ripple-center\" style=\"background-color: $ripple_color\"></div>\n        <div class=\"sl-ripple\" style=\"border: 8px solid $ripple_color\"></div>\n      </div>\n    `,n=document.querySelectorAll(\".sl-preview\");if(n.length>0)for(const r of n){let f=\"#9639e7\",p=o,y=(s=(a=r.parentElement)==null?void 0:a.parentElement)==null?void 0:s.querySelector(\".sl-preview-cta\");y!=null&&(f=y.style.backgroundColor);let v=document.createElement(\"img\");v.src=((c=r.style.backgroundImage.match(/(https?:\\/\\/[^ |'|\"]*)/))==null?void 0:c[0])||\"\",v.classList.add(\"sl-preview-img\"),r.appendChild(v),r.style.backgroundImage=\"\",r.parentElement&&(r.parentElement.style.borderRadius=\"10px\"),r.style.borderRadius=\"inherit\",r.innerHTML+=p.replaceAll(/\\$ripple_color/g,f)}const i=document.querySelectorAll(\".sl-demo-btn\");if(i.length>0)for(const r of i)(b=window.Storylane)==null||b.Player({element:r,demo_url:r.getAttribute(\"data-sl-url\"),scale:r.getAttribute(\"data-sl-scale\")});const l=document.querySelectorAll('a[href*=\"?sl_popup\"],a[href*=\"&sl_popup\"]');if(l.length>0)for(const r of l){let f=new URL(r.getAttribute(\"href\")||\"\"),p=new URLSearchParams(f.search),y=p.get(\"config\"),v=y&&y.length?JSON.parse(atob(y)):{};p.delete(\"config\"),p.delete(\"sl_popup\"),f.search=p.toString();const S={element:r,demo_url:f,type:\"popup\"};r.onclick=function(k){var x;k.preventDefault(),(x=window.Storylane)==null||x.Play(_(_({},S),v))}}const d=document.querySelectorAll(\".sl-inline-embed\");if(d.length>0)for(const r of d)(h=window.Storylane)==null||h.Embed({element:r,demo_preview:r.getAttribute(\"data-sl-preview\"),demo_url:r.getAttribute(\"data-sl-url\"),scale:r.getAttribute(\"data-sl-scale\")})}document.readyState===\"loading\"?document.addEventListener(\"DOMContentLoaded\",e):e()}function oe(){const t=[B,G,V,Q,X];window.addEventListener(\"message\",e=>{t.some(o=>{try{o(e)}catch(n){console.error(n)}})})}return function(){ne()}(),\"\"}();\n\n\n\nvar custom_style=\"#FirstWork{display: none !important;}\",sl_custom_style=document.createElement(\"style\");sl_custom_style.innerHTML=custom_style,document.head.appendChild(sl_custom_style),document.addEventListener(\"DOMContentLoaded\",(function(t){const s=document.createElement(\"script\");s.src=\"https://js.storylane.io/js/v1/analytics.js\",document.head.appendChild(s)}));\n=== npm\nstorylane-oboard 1.0.0 | Modified storylane version. For prevent inline integration.\n@creezio/interactive-demo 0.26.0 | Démo interactive native (product tour live) : moteur de scénarios déclaratifs + faux curseur animé, contenu hybride DB\ninteractive-preview-skill 1.0.0 | Installer for the interactive-preview Claude Code skill — generates an interactive 'try it before you sign up' demo for React/Next.js apps.\n","type":"text"}]}
Thinking…

The Storylane integration works through a CDN-hosted JavaScript library that exposes a window.Storylane object, with auto-initialization handling three different embed patterns: popup demos triggered by button clicks or anchor links with query parameters, and inline embeds using data attributes for the demo URL and preview settings.

Now I'm checking what public methods are available on the U class—looks like Play, Player, Embed, and PreviewEmbed—and I need to search for any tracking or token-related APIs, particularly around cross-domain tracking and form submission handling.

$ cd /tmp && node -e "{"content":[{"text":"--- Embed( @2594\ner(\"click\",o),this.addEventListener(\"disconnected\",()=>{e.removeEventListener(\"click\",o)})}}customElements.define(\…ctrl+o
cd /tmp && node -e "
const s=require('fs').readFileSync('sl.js','utf8');
for (const kw of ['Tracking','Embed(','Player(','sl_token','storylane-token-submit','host-info']) {
  let i=-1,c=0;
  while((i=s.indexOf(kw,i+1))!==-1 && c<2){ console.log('--- '+kw+' @'+i); console.log(s.slice(Math.max(0,i-400),i+500).replace(/\n/g,' ')); c++; }
}
"

{"content":[{"text":"--- Embed( @2594\ner(\"click\",o),this.addEventListener(\"disconnected\",()=>{e.removeEventListener(\"click\",o)})}}customElements.define(\"sl-close-button\",j);class U{constructor(){this.storylane_style=\"\",this.embed_style=\"\",this.demo_html=\"\"}Play(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;switch(this.params=e,this.params.type){case\"popup\":this.Player(e);break;case\"preview_embed\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:.9};this.params=_(_({},o),e);const n=this.params.demo_url;if(!n)return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;console.log(\"creating storylane demo with preview\");const i=this.params.element,l=i.closest(\".sl-embed-container\");if(!l){console.error(\"Unable to find .sl-embed-container\");return}const d=l.querySelector(\".sl-preview\");if(!d){console.erro\n--- Embed( @2617\nventListener(\"disconnected\",()=>{e.removeEventListener(\"click\",o)})}}customElements.define(\"sl-close-button\",j);class U{constructor(){this.storylane_style=\"\",this.embed_style=\"\",this.demo_html=\"\"}Play(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;switch(this.params=e,this.params.type){case\"popup\":this.Player(e);break;case\"preview_embed\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:.9};this.params=_(_({},o),e);const n=this.params.demo_url;if(!n)return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;console.log(\"creating storylane demo with preview\");const i=this.params.element,l=i.closest(\".sl-embed-container\");if(!l){console.error(\"Unable to find .sl-embed-container\");return}const d=l.querySelector(\".sl-preview\");if(!d){console.error(\"Unable to find .sl-p\n--- Player( @2546\nEvent(new Event(E.CloseModal))};e.addEventListener(\"click\",o),this.addEventListener(\"disconnected\",()=>{e.removeEventListener(\"click\",o)})}}customElements.define(\"sl-close-button\",j);class U{constructor(){this.storylane_style=\"\",this.embed_style=\"\",this.demo_html=\"\"}Play(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;switch(this.params=e,this.params.type){case\"popup\":this.Player(e);break;case\"preview_embed\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:.9};this.params=_(_({},o),e);const n=this.params.demo_url;if(!n)return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;console.log(\"creating storylane demo with preview\");const i=this.params.element,l=i.closest(\".sl-embed-container\");if(!l){console.error(\"Unable to find .sl-embed-container\");return}const d=l.\n--- Player( @3488\ne.error(\"Unable to find .sl-preview\");return}const a=l.querySelector(\"iframe.sl-demo\");if(!a){console.error('Unable to find \"iframe.sl-demo\"');return}(s=i.parentElement)!=null&&s.classList.contains(\"sl-preview-heading\")?i.parentElement.style.display=\"none\":i.style.display=\"none\",d.style.display=\"none\",a.style.display=\"block\",a.style.backgroundColor=\"#f3f5f7\",a.style.borderRadius=\"inherit\",a.src=n}Player(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:\"0.9\",width:\"100%\",padding_bottom:\"calc(56.33% + 27px)\",fullscreen:!1};if(this.params=_(_({},o),e),!this.params.hasOwnProperty(\"demo_url\"))return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;this.storylane_style=`           #__sl-demo-wrapper{             position: fixed;             top: 0px;             left: 0px;             width: 100%;             height: 100%;             z-\n--- storylane-token-submit @461\nect.defineProperty;var F=Object.getOwnPropertySymbols;var le=Object.prototype.hasOwnProperty,ae=Object.prototype.propertyIsEnumerable;var R=(w,m,u)=>m in w?ie(w,m,{enumerable:!0,configurable:!0,writable:!0,value:u}):w[m]=u,_=(w,m)=>{for(var u in m||(m={}))le.call(m,u)&&R(w,u,m[u]);if(F)for(var u of F(m))ae.call(m,u)&&R(w,u,m[u]);return w};const w=\"storylane-demo-event\",m=\"storylane-embed-demo\",u=\"storylane-token-submit\",H=\"storylane-tracking\",$=\"storylane-host-info\",T=\"storylane-demo-host\",I=\"hubspotutk\",O=\"_mkto_trk\",D=/visitor_id\\d+/,E={CloseModal:\"sl-close\"},N=`.sl-close-btn {   background: #ffffff;   color: #1a1348;   border: 2px solid #fffffe;   border-radius: 20px;   line-height: 18px;   box-shadow: 0px 0px 5px #444;   cursor: pointer;   font-family: Helvetica;   font-size: 14px;   font-weight: bold;   height: 24px;   width: 24px;   padding: 0px; }  .sl-close-btn:hover {   box-shad\n--- host-info @521\n=Object.prototype.hasOwnProperty,ae=Object.prototype.propertyIsEnumerable;var R=(w,m,u)=>m in w?ie(w,m,{enumerable:!0,configurable:!0,writable:!0,value:u}):w[m]=u,_=(w,m)=>{for(var u in m||(m={}))le.call(m,u)&&R(w,u,m[u]);if(F)for(var u of F(m))ae.call(m,u)&&R(w,u,m[u]);return w};const w=\"storylane-demo-event\",m=\"storylane-embed-demo\",u=\"storylane-token-submit\",H=\"storylane-tracking\",$=\"storylane-host-info\",T=\"storylane-demo-host\",I=\"hubspotutk\",O=\"_mkto_trk\",D=/visitor_id\\d+/,E={CloseModal:\"sl-close\"},N=`.sl-close-btn {   background: #ffffff;   color: #1a1348;   border: 2px solid #fffffe;   border-radius: 20px;   line-height: 18px;   box-shadow: 0px 0px 5px #444;   cursor: pointer;   font-family: Helvetica;   font-size: 14px;   font-weight: bold;   height: 24px;   width: 24px;   padding: 0px; }  .sl-close-btn:hover {   box-shadow: 0px 0px 6px rgba(255, 255, 100, 1); } `;class j extends \n","type":"text"}]}
Thinking…

I'm tracing how the parent page communicates token values to the iframe through storylane-token-submit, and understanding the token flow from URL query parameters like ?token[first_name]=Maria. Let me examine where these token handlers and tracking mechanisms are implemented in the code.

$ cd /tmp && node -e "{"content":[{"text":"idx 10538\n: 5px;\n color: #f5f5f5;\n text-align:center;\n text-decoration:none;\n back…ctrl+o
cd /tmp && node -e "
const s=require('fs').readFileSync('sl.js','utf8');
// find function definitions B,G,V,Q,X near end
const i=s.indexOf('function B(') >=0 ? s.indexOf('function B(') : s.search(/(?:const|function)\s*B\s*[=(]/);
console.log('idx',i);
const j=s.indexOf('window.Storylane\"in window');
console.log(s.slice(j-9000,j-3000));
"

{"content":[{"text":"idx 10538\n: 5px;\n              color: #f5f5f5;\n              text-align:center;\n              text-decoration:none;\n              background-color: #e0007a;\n              font-size: 22px;\n              font-weight: normal;\n              transition: all 0.3s ease;\n            position: relative;\n            top: 45%;\n            left: 45%;\n            cursor: pointer;\n          }\n\n          .__sl-play:hover {\n              background-color: #c1066c;\n              box-shadow: 0px 0px 6px rgba(255,255,100,1);\n          }\n        `;let n=document.getElementById(\"__sl-demo-embed-wrapper\");if(n==null){n=document.createElement(\"div\"),n.id=\"__sl-demo-embed-wrapper\",n.removeAttribute(\"style\");const a=document.createElement(\"style\");a.innerHTML=this.storylane_style,document.head.appendChild(a)}let i=this.params.element;const l=this.params.demo_url,d=this.params.demo_preview;if(l){console.log(\"creating storylane embedded demo\");const a=document.createElement(\"iframe\");a.classList.add(\"__sl-iframe\"),a.src=l,n.appendChild(a),i.innerHTML=\"\",i.appendChild(n)}else if(d){console.log(\"creating storylane embedded demo with preview\");const a=i.innerHTML,s=document.createElement(\"div\");s.classList.add(\"__sl-demo-preview\"),s.setAttribute(\"style\",\"background: linear-gradient(0deg, rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url('\"+d+\"') no-repeat; background-size: 100% auto;\");const c=document.createElement(\"a\");c.classList.add(\"__sl-play\"),c.text=\"Start Demo\",c.addEventListener(\"click\",b=>{s.remove(),n&&(n.innerHTML=a)}),s.appendChild(c),n.appendChild(s),i.innerHTML=\"\",i.appendChild(n)}}}function q(t,e){var n,i;if(t.origin===\"\"||t.origin===\"null\")return!0;let o=t.origin===e||/https:\\/\\/(www\\.)?[0-9a-zA-Z-]+\\.storylane\\.(io|us)\\/?$/.test(t.origin);if(!o){const l=/^(?:[a-zA-Z0-9-]+\\.)*([a-zA-Z0-9-]+\\.[a-zA-Z]{2,})$/,d=new URL(e),a=new URL(t.origin),s=(n=d.host.match(l))==null?void 0:n[1],c=(i=a.host.match(l))==null?void 0:i[1];o=s&&c&&s===c}return!o}function B(t){const e=t.data;if(typeof e==\"object\"&&e!==null&&\"message\"in e&&e.message===w){if(q(t,window.origin))return console.warn(\"sl: unable to process message\"),!0;let o=t.data.payload;return o.event===\"open_external_url\"&&o.target.target===\"_self\"&&window.open(o.target.url,o.target.target),!0}return!1}function K(t,e){const o=t.split(\"; \");for(let n=0;n<o.length;n++){const l=o[n].split(\"=\");if(l[0].match(e))return l[1]}return null}function W(t,e,o,n){try{return t.cookie=e+\"=\"+(o||\"\")+\"; max-age=\"+n+\"; path=/; secure\",!0}catch(i){return!1}}const M=\"slReferer\";function Z(t){try{const e=P(t);!z(t)&&e&&J(t,e)}catch(e){}}function P(t){const e=t.referrer;return e?btoa(e):null}function z(t){const e=K(t.cookie,M);return e?decodeURIComponent(e):null}function J(t,e){W(t,M,encodeURIComponent(e),31536e3)}function V(t){var o,n,i,l,d;const e=t.data;if(typeof e==\"object\"&&e!==null&&e&&e.source===m){let s=new URL(window.location.toString()).searchParams,c=s.get(\"email\");s.delete(\"email\");let b=[],h={};for(const[r,f]of s.entries())if(r.match(/token/)!==null){b.push(r);let p=(o=r.match(/^token\\[(.*)]$/))==null?void 0:o[1];p&&(h[p]=f)}for(const r of b)s.delete(r);return c&&((n=t.source)==null||n.postMessage({source:T,lead:{email:c}},{targetOrigin:t.origin})),Object.keys(h).length>0&&((i=t.source)==null||i.postMessage({message:u,payload:{token:h}},{targetOrigin:t.origin})),(l=t.source)==null||l.postMessage({source:$,host:{url:window.location.href},url_query:s.toString()},{targetOrigin:t.origin}),(d=t.source)==null||d.postMessage({source:H,tracking:btoa(document.cookie),ref:{original:z(document),current:P(document)}},{targetOrigin:t.origin}),!0}return!1}const L=({tracking:t,encode:e=!0})=>{try{const o=\"https://api.storylane.io/api/v1/shared/tracking\",n=e?btoa(t):t;if(\"sendBeacon\"in navigator){const i=new FormData;i.set(\"tracking\",n),navigator.sendBeacon(o,i)}else fetch(o,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},keepalive:!0,body:JSON.stringify({tracking:n})})}catch(o){}};function A(t,e){const o=t.split(\"; \");for(let n=0;n<o.length;n++){const i=o[n];if(i.split(\"=\")[0].match(e))return i}return null}function G(t){const e=t.data;if(typeof e==\"object\"&&e!==null&&e.type===\"hsFormCallback\"&&e.eventName===\"onFormSubmitted\"){const o=A(document.cookie,I);return o&&L({tracking:o}),!0}return!1}function Q(t){const e=t.data;if(typeof e==\"object\"&&e!==null&&e.message===\"LD_SAVE_FORM_DATA\"){const o=A(document.cookie,D);return o&&L({tracking:o}),!0}return!1}function X(t){if(typeof t.data==\"string\"&&t.data.startsWith('{\"mktoResponse\"')){const e=JSON.parse(t.data);if(typeof e==\"object\"&&\"mktoResponse\"in e&&e.mktoResponse.error===!1){const o=A(document.cookie,O);o&&L({tracking:o})}return!0}return!1}function Y(t){t.querySelectorAll(\"form\").forEach(o=>{o.addEventListener(\"submit\",n=>{try{L({tracking:t.cookie})}catch(i){console.error(i)}},{passive:!0,capture:!1})})}const ee=`.sl-preview-img {\n  width: 100%;\n  height: 100%;\n  webkit-filter: blur(1px);\n  filter: blur(1px);\n  background-color: rgba(0, 0, 0, 0.3);\n  border-radius: inherit;\n  z-index: 999991;\n  object-fit: fill;\n}\n.sl-ripple-backdrop {\n  width: 75px;\n  height: 75px;\n  webkit-backdrop-filter: blur(1px);\n  backdrop-filter: blur(1px);\n  background-color: rgba(0, 0, 0, 0);\n  position: absolute;\n  top: 20%;\n  left: 20%;\n  z-index: 999991;\n}\n.sl-ripple-main {\n  --ripple-max-size: 75px;\n  --circle-size: 22px;\n  --color: #9639e7;\n  --border-animation-duration: 1.5s;\n  --ripple-border-width: 8px;\n  --ripple-timing-func: ease-in;\n\n  width: var(--ripple-max-size);\n  height: var(--ripple-max-size);\n  position: absolute;\n  top: 20%;\n  left: 20%;\n  z-index: 999990;\n}\n\n.sl-ripple-center {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  width: var(--circle-size);\n  height: var(--circle-size);\n  transform: translate(-50%, -50%);\n  background-color: var(--color);\n  border-radius: 50%;\n}\n\n.sl-ripple {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  border-radius: 50%;\n  border: var(--ripple-border-width) solid var(--color);\n","type":"text"}]}
Thinking…

The SDK extracts email and token[*] parameters from the host page's query string and passes them to the embedded demo iframe through postMessage—so tokens flow from the marketing page URL into the demo for personalization and lead identification. Now I'm setting up the example with a README covering what it is, what gets built, the quickstart steps for opening the HTML file, and pricing details.

I'll create the embed page with inline and popup demo embeds plus a tokens form builder and event listener that logs activity and forwards to GA4 if available, a config module to load Storylane credentials from environment variables, a CLI script using Node's native fetch to list demos and generate engagement reports with email tokens, a sample .env file, and a package.json with Node 18+ requirement and CLI scripts. I'm keeping the demo URL flexible via environment variables so it works with any workspace, though I could validate the approach against a public Storylane demo link to confirm the oEmbed and embed endpoints are accessible without authentication.

Now I need to check the webhooks documentation to understand what payloads are available so I can accurately describe the webhook receiver in the README—I'll make sure it's just an optional snippet that posts to your own endpoint rather than something that spins up a local service.

$ cd /tmp && head -100 integrations-demo-suite_integrations-and-data-flow_webhooks.md{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are av…ctrl+o
{"content":[{"text":"> For the complete documentation index, see [llms.txt](https://docs.storylane.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/webhooks.md).\n\n# Webhooks\n\nWebhooks are available starting from the Starter plan.\n\nYou can send demo alerts to the Webhook URL.\n\n{% hint style=\"warning\" %}\n\n### **Identifying Leads**\n\nThere are a few ways in which you can capture or identify leads. You can refer to the article on [Lead Tracking](/analytics-and-performance/capture-leads.md) to learn more\n{% endhint %}\n\n***\n\n### **How to Set Up Webhooks inside Alerts**\n\n* Go to **Settings → Alerts**\n* Click **\"Create first Alert\"**\n* Toggle on **Webhooks**\n\n<div align=\"left\"><figure><img src=\"https://2431356420-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FN8hWd9k2Pbb6YSWO4pUQ%2Fuploads%2F2o9HGICgJmd8HF1zwcZW%2FScreenshot%202026-08-19%20at%208.14.19%E2%80%AFPM.png?alt=media&amp;token=b1f261c3-5496-425d-a391-0223cea9865e\" alt=\"\"><figcaption></figcaption></figure></div>\n\n* Paste your **Webhook URL** — this is the URL where you want to receive webhook events\n* Add **Custom headers** if needed. Custom headers let you attach extra key-value pairs to every webhook request — useful for passing authentication tokens, routing identifiers, or any metadata your receiving endpoint expects. Click **\"+ Add header\"** to add one\n* Use **Test webhook** to confirm everything is working, then hit **Save**\n\n***\n\n### How do I verify the webhook signature? <a href=\"#how-do-i-verify-the-webhook-signature\" id=\"how-do-i-verify-the-webhook-signature\"></a>\n\nTo ensure that webhook requests are coming from Storylane and have not been tampered with, we recommend verifying the signature included in the request headers.\n\nEvery webhook request includes an `x-storylane-signature` header. This signature is an HMAC-SHA256 hash of the raw request body, generated using your Webhook Verification Secret and then Base64 encoded.\n\n#### 1. Verification Logic\n\nTo verify the signature:\n\n1. Retrieve the `x-storylane-signature` header from the request.\n2. Get the raw body of the request (unparsed string).\n3. Compute the HMAC-SHA256 hash of the raw body using your Verification Secret as the key.\n4. Base64 encode the resulting hash.\n5. Compare your generated string with the value in the `x-storylane-signature` header.\n\n#### 2. Implementation Examples\n\n**Node.js (Express)**\n\nWhen using Express, ensure you use the raw body for the HMAC calculation.\n\n```js\nconst crypto = require('crypto');\n\napp.post('/webhook', (req, res) => {\n  const signature = req.headers['x-storylane-signature'];\n  const secret = process.env.STORYLANE_WEBHOOK_SECRET;\n\n  // 'req.rawBody' should be the unparsed string of the request body\n  const hmac = crypto.createHmac('sha256', secret);\n  const digest = hmac.update(req.rawBody).digest('base64');\n\n  if (signature === digest) {\n    // Valid signature\n    console.log('Webhook verified');\n    res.status(200).send('Verified');\n  } else {\n    // Invalid signature\n    res.status(401).send('Unauthorized');\n  }\n});\n```\n\n**Ruby**\n\n```ruby\nrequire 'openssl'\nrequire 'base64'\n\ndef verify_signature(payload, secret, received_signature)\n  hmac = OpenSSL::HMAC.digest(\n    'sha256',\n    secret.to_s,\n    payload\n  )\n\n  expected_signature = Base64.strict_encode64(hmac)\n\n  # Use secure comparison to prevent timing attacks\n  Rack::Utils.secure_compare(expected_signature, received_signature)\nend\n```\n\n**Python (Flask)**\n\n```python\nimport hmac\nimport hashlib\n","type":"text"}]}
Thinking…

I'm setting up the example directory structure under /sandbox/repo/storylane-example/ with a simple file layout: a README, environment config file, package.json, and an example folder containing the HTML page and a Node script for API operations. I'm keeping it minimal without extra files, and considering whether to include a .gitignore.

Now I need to write the CLI script carefully, implementing the Storylane API endpoints from their documentation—covering demos, publishing, stats, sessions, links, tokens, hubs, accounts, and leads. The authentication uses Bearer tokens with workspace IDs in the path. I'll add a --dry-run flag for verification and test the syntax with Node, plus validate that unauthenticated requests fail as expected. For the HTML page, I'm including the Storylane embed script.

Writing the embed markup...

$ cd /tmp && node -e "{"content":[{"text":",this.demo_html=\"\"}Play(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;switch(this.params=e,this.params.type…ctrl+o
cd /tmp && node -e "
const s=require('fs').readFileSync('sl.js','utf8');
const i=s.indexOf('Embed(e)');
console.log(s.slice(i-200,i+1800));
"

{"content":[{"text":",this.demo_html=\"\"}Play(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;switch(this.params=e,this.params.type){case\"popup\":this.Player(e);break;case\"preview_embed\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:.9};this.params=_(_({},o),e);const n=this.params.demo_url;if(!n)return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;console.log(\"creating storylane demo with preview\");const i=this.params.element,l=i.closest(\".sl-embed-container\");if(!l){console.error(\"Unable to find .sl-embed-container\");return}const d=l.querySelector(\".sl-preview\");if(!d){console.error(\"Unable to find .sl-preview\");return}const a=l.querySelector(\"iframe.sl-demo\");if(!a){console.error('Unable to find \"iframe.sl-demo\"');return}(s=i.parentElement)!=null&&s.classList.contains(\"sl-preview-heading\")?i.parentElement.style.display=\"none\":i.style.display=\"none\",d.style.display=\"none\",a.style.display=\"block\",a.style.backgroundColor=\"#f3f5f7\",a.style.borderRadius=\"inherit\",a.src=n}Player(e){if(typeof e!=\"object\")return console.log(\"Error: Invalid params.\"),0;let o={scale:\"0.9\",width:\"100%\",padding_bottom:\"calc(56.33% + 27px)\",fullscreen:!1};if(this.params=_(_({},o),e),!this.params.hasOwnProperty(\"demo_url\"))return console.log(\"Error: Invalid params. Please provide valid demo_url\"),0;this.storylane_style=`\n          #__sl-demo-wrapper{\n            position: fixed;\n            top: 0px;\n            left: 0px;\n            width: 100%;\n            height: 100%;\n            z-index: 9999999;\n            background: rgba(0,0,0,0.8);\n          }\n          .__sl-embed-container{\n            z-index: 99999999;\n            position: relative;\n            width: 100%;\n            display: flex;\n            max-width: 100%;\n            height: 100%;\n            max-height: 100%;\n            align-items: center;\n            justify-content: center;\n          }\n          .__sl-embed{\n","type":"text"}]}
Thinking…

I'm noticing the Embed method isn't defined on the U class, but the auto-init code is calling it with window.Storylane?.Embed({...}). Looking at the class definition, I only see Play, PreviewEmbed, and Player methods—so either Embed is assigned elsewhere or it's a missing method that would cause a TypeError. Let me search for other references to "Embed" to figure out where it's supposed to come from.

$ cd /tmp && node -e "{"content":[{"text":"2594 \"popup\\\":this.Player(e);break;case\\\"preview_embed\\\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if\"\n2617 \"reak;case\\\…ctrl+o
cd /tmp && node -e "
const s=require('fs').readFileSync('sl.js','utf8');
let i=-1; while((i=s.indexOf('Embed',i+1))!==-1){ console.log(i, JSON.stringify(s.slice(i-60,i+40))); }
" | head -30; echo ===; node -e "
const s=require('fs').readFileSync('/tmp/sl.js','utf8');
const i=s.indexOf('.sl-inline-embed');
console.log(s.slice(i-800,i+600));
"

{"content":[{"text":"2594 \"popup\\\":this.Player(e);break;case\\\"preview_embed\\\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if\"\n2617 \"reak;case\\\"preview_embed\\\":this.PreviewEmbed(e);break}}PreviewEmbed(e){var s;if(typeof e!=\\\"object\\\")ret\"\n7484 \"E.CloseModal,x)}k==null||k.addEventListener(E.CloseModal,x)}Embed(e){if(typeof e!=\\\"object\\\")return co\"\n16843 \"f(d.length>0)for(const r of d)(h=window.Storylane)==null||h.Embed({element:r,demo_preview:r.getAttri\"\n===\ntElement.style.borderRadius=\"10px\"),r.style.borderRadius=\"inherit\",r.innerHTML+=p.replaceAll(/\\$ripple_color/g,f)}const i=document.querySelectorAll(\".sl-demo-btn\");if(i.length>0)for(const r of i)(b=window.Storylane)==null||b.Player({element:r,demo_url:r.getAttribute(\"data-sl-url\"),scale:r.getAttribute(\"data-sl-scale\")});const l=document.querySelectorAll('a[href*=\"?sl_popup\"],a[href*=\"&sl_popup\"]');if(l.length>0)for(const r of l){let f=new URL(r.getAttribute(\"href\")||\"\"),p=new URLSearchParams(f.search),y=p.get(\"config\"),v=y&&y.length?JSON.parse(atob(y)):{};p.delete(\"config\"),p.delete(\"sl_popup\"),f.search=p.toString();const S={element:r,demo_url:f,type:\"popup\"};r.onclick=function(k){var x;k.preventDefault(),(x=window.Storylane)==null||x.Play(_(_({},S),v))}}const d=document.querySelectorAll(\".sl-inline-embed\");if(d.length>0)for(const r of d)(h=window.Storylane)==null||h.Embed({element:r,demo_preview:r.getAttribute(\"data-sl-preview\"),demo_url:r.getAttribute(\"data-sl-url\"),scale:r.getAttribute(\"data-sl-scale\")})}document.readyState===\"loading\"?document.addEventListener(\"DOMContentLoaded\",e):e()}function oe(){const t=[B,G,V,Q,X];window.addEventListener(\"message\",e=>{t.some(o=>{try{o(e)}catch(n){console.error(n)}})})}return function(){ne()}(),\"\"}();\n\n\n\nvar custom_style=\"#FirstWork{display: none !important;}\",sl_custom_style=document.createElement(\"style\");sl_custom_style.innerHTML=cus\n","type":"text"}]}
Thinking…

I'm identifying the correct embed class selector .sl-inline-embed with its data attributes and confirming that popup modals are triggered through .sl-demo-btn buttons or anchor links with ?sl_popup. Now I'll write out the implementation files and create a README with the pricing breakdown from their site.

Now I have what I need. Building the example.

write /sandbox/repo/storylane-example/.env.example{"content":[{"text":"Successfully wrote 914 bytes to /sandbox/repo/storylane-example/.env.example","type":"text"}]}
write /sandbox/repo/storylane-example/package.json{"content":[{"text":"Successfully wrote 685 bytes to /sandbox/repo/storylane-example/package.json","type":"text"}]}
Thinking…

I'm debating whether to include a script that just prints instructions versus keeping things minimal, and I'm leaning toward dropping it entirely to avoid the temptation of adding a server. The embed page can work fine when opened directly as a file, though there might be some postMessage origin edge cases to consider.

write /sandbox/repo/storylane-example/package.json{"content":[{"text":"Successfully wrote 642 bytes to /sandbox/repo/storylane-example/package.json","type":"text"}]}
write /sandbox/repo/storylane-example/example/index.html{"content":[{"text":"Successfully wrote 7810 bytes to /sandbox/repo/storylane-example/example/index.html","type":"text"}]}
Thinking…

I need to clean up that spacing issue in the comment, then move on to writing the API CLI script.

write /sandbox/repo/storylane-example/scripts/storylane-api.mjs{"content":[{"text":"Successfully wrote 12595 bytes to /sandbox/repo/storylane-example/scripts/storylane-api.mjs","type":"text"}]}
Thinking…

I should add a webhook signature verification helper that's pure logic without any server overhead. I'll create a script that takes raw webhook body input and validates the signature, keeping it lightweight and dependency-free.

$ cd /sandbox/repo/storylane-example && node --check scripts/storylane-api.mjs && echo "syntax ok" && node scripts/storylane-api.mjs help | head -30 && echo "=== unauth probe" && STORYLANE_API_KEY=x STO…{"content":[{"text":"syntax ok\n#!/usr/bin/env node\n/**\n * Minimal Storylane External API client. Zero dependencies (Node >= 18, global fetch).\n *\n * Docs: …ctrl+o
cd /sandbox/repo/storylane-example && node --check scripts/storylane-api.mjs && echo "syntax ok" && node scripts/storylane-api.mjs help | head -30 && echo "=== unauth probe" && STORYLANE_API_KEY=x STORYLANE_WORKSPACE_ID=y node scripts/storylane-api.mjs demos; echo "exit=$?"; echo "=== oembed (public)"; node scripts/storylane-api.mjs oembed --url=https://app.storylane.io/share/ha3eti5gqq2c | head -20

{"content":[{"text":"syntax ok\n#!/usr/bin/env node\n/**\n * Minimal Storylane External API client. Zero dependencies (Node >= 18, global fetch).\n *\n * Docs: https://docs.storylane.io/integrations-demo-suite/integrations-and-data-flow/external-api\n * Auth: Bearer <STORYLANE_API_KEY>, workspace id in the path.\n *\n * Usage:\n *   node scripts/storylane-api.mjs demos\n *   node scripts/storylane-api.mjs demo --demo=<demo-id>\n *   node scripts/storylane-api.mjs publish --demo=<demo-id>\n *   node scripts/storylane-api.mjs report                 # per-demo engagement stats\n *   node scripts/storylane-api.mjs sessions [--pages=3]\n *   node scripts/storylane-api.mjs session --session=<id>\n *   node scripts/storylane-api.mjs leads\n *   node scripts/storylane-api.mjs accounts\n *   node scripts/storylane-api.mjs hubs\n *   node scripts/storylane-api.mjs links --demo=<demo-id>\n *   node scripts/storylane-api.mjs create-link --demo=<demo-id> [--name=\"Partner A\"]\n *   node scripts/storylane-api.mjs update-link --demo=<demo-id> --link=<link-id> [--name=..] [--expires-at=..] [--passcode=..]\n *   node scripts/storylane-api.mjs delete-link --demo=<demo-id> --link=<link-id>\n *   node scripts/storylane-api.mjs token --demo=<demo-id> --link=<link-id> --email=you@co.com\n *   node scripts/storylane-api.mjs oembed --url=https://app.storylane.io/share/<link>   # public, no key needed\n \n=== unauth probe\n\nerror: GET /api/connect/v1/workspaces/y/demos -> 401 Unauthorized\n{\"error\":\"API Key is missing, invalid or expired. Please contact support to obtain a new API Key.\"}\n\nexit=1\n=== oembed (public)\n{\n  \"title\": \"Storylane No-code Editor\",\n  \"thumbnail_url\": \"https://app-pages.storylane.io/company/company_d3ab1eef-4a7e-4994-9234-64f19dd6b621/project/project_e4a21c52-c26d-45f2-9977-1770d8a4122b/preview.gif\",\n  \"width\": 750,\n  \"height\": 448\n}\n\nembed html:\n<div>   <script src=\"https://js.storylane.io/js/v1/storylane.js\"></script>   <div class=\"sl-embed\" style=\"position:relative;padding-bottom:calc(56.32% + 26px);width:100%;height:0;transform:scale(1)\">     <iframe class=\"sl-demo\" src=\"https://app.storylane.io/demo/ha3eti5gqq2c\" allow=\"fullscreen\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;border:none;\"></iframe>   </div> </div> \n","type":"text"}]}
Thinking…

Now I need to create the webhook verification script and documentation, plus add a .gitignore file to exclude .env. The verification script should accept either a signature passed via --sig= flag along with the raw body from stdin, or alternatively read the raw body from a file using --body-file=.

write /sandbox/repo/storylane-example/scripts/verify-webhook.mjs{"content":[{"text":"Successfully wrote 2100 bytes to /sandbox/repo/storylane-example/scripts/verify-webhook.mjs","type":"text"}]}
write /sandbox/repo/storylane-example/.gitignore{"content":[{"text":"Successfully wrote 19 bytes to /sandbox/repo/storylane-example/.gitignore","type":"text"}]}
Thinking…

I need to fix a spacing issue in the HTML comment and then write out the README file.

edit /sandbox/repo/storylane-example/example/index.html{"content":[{"text":"No changes made to /sandbox/repo/storylane-example/example/index.html. The replacement produced identical content. This might indicate an i…ctrl+o
{"content":[{"text":"No changes made to /sandbox/repo/storylane-example/example/index.html. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.","type":"text"}],"details":{}}
Thinking…

I need to correct a formatting issue in the HTML where the token syntax needs a space adjustment.

edit /sandbox/repo/storylane-example/example/index.html{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/storylane-example/example/index.html.","type":"text"}],"details":{"diff":" ...\n 65 …ctrl+o
{"content":[{"text":"Successfully replaced 1 block(s) in /sandbox/repo/storylane-example/example/index.html.","type":"text"}],"details":{"diff":"     ...\n  65   <!-- ===== 3. Personalisation tokens ======================================= -->\n  66   <h2>3. Personalise + identify the viewer</h2>\n  67   <div class=\"card\">\n  68     <p>\n- 69       The SDK reads <code>?email=</code> and <code>?token[...] =</code> from <em>this</em> page's URL and\n+ 69       The SDK reads <code>?email=</code> and <code>?token[…]</code> from <em>this</em> page's URL and\n  70       forwards them into the demo iframe, so tokens defined in the Storylane editor render with real\n  71       values and the session is attributed to a lead. No form, no gating.\n  72     </p>\n  73     <div class=\"row\">\n     ...","patch":"--- /sandbox/repo/storylane-example/example/index.html\n+++ /sandbox/repo/storylane-example/example/index.html\n@@ -65,9 +65,9 @@\n   <!-- ===== 3. Personalisation tokens ======================================= -->\n   <h2>3. Personalise + identify the viewer</h2>\n   <div class=\"card\">\n     <p>\n-      The SDK reads <code>?email=</code> and <code>?token[...] =</code> from <em>this</em> page's URL and\n+      The SDK reads <code>?email=</code> and <code>?token[…]</code> from <em>this</em> page's URL and\n       forwards them into the demo iframe, so tokens defined in the Storylane editor render with real\n       values and the session is attributed to a lead. No form, no gating.\n     </p>\n     <div class=\"row\">\n","firstChangedLine":69}}
Thinking…

Now I'm drafting a README that covers the essentials—what Storylane is, what's included in the box, a quickstart that works right out of the file system, where to swap in your own demo, the external API CLI and webhook verification setup, MCP integration notes, a pricing breakdown, and a candid assessment of how manageable it is for an agent. I'll keep it tight and skip anything about local stacks since this is serverless.

I'm also adding a section on what you can't do programmatically—creating demos through the External API isn't possible, that's MCP's domain with its image/video conversion endpoints, and HTML editing is all UI-driven. Then I'll list out the blockers we hit. For pricing, I'm pulling the live tiers from storylane.io/plans: Free at $0 with one creator seat and basic features, Starter at $50/mo with unlimited demos and integrations, Growth at $625/mo adding HTML editor and A/B testing, Premium at $1500/mo with SSO and Salesforce, and Enterprise as custom. Annual billing runs about 20% cheaper, and extra creator seats cost $40/mo on Starter or $100/mo on Growth.

One key clarification: viewer seats are always unlimited, so pricing scales with creator seats and feature tier, not demo traffic. Also need to note that External API requires a paid plan (their marketing lists it as Enterprise but the docs confirm any paid tier works), MCP needs Starter or above with writes in beta, and Webhooks are Starter+.

$ cd /tmp && grep -o -E 'yearly[^<>]{0,120}' plans.html | head -10; grep -o -iE '(save|discount)[^<>]{0,60}' plans.html | sort -u | head -10; grep -o -E 'toggleButton.{0,700}' plans.html | head -1{"content":[{"text":"yearlyText = document.querySelectorAll('[yearly-text]')\nyearlyText.forEach((item) =\nyearlyText.forEach((item) =\nyearlyTab = document.que…ctrl+o
{"content":[{"text":"yearlyText = document.querySelectorAll('[yearly-text]')\nyearlyText.forEach((item) =\nyearlyText.forEach((item) =\nyearlyTab = document.querySelector('[data-tab=\"yearly\"]');\nyearly') {\nyearly') {\nyearlyTab) {\nyearlyTab.addEventListener('click', function() {\nyearly');\nyearly)\ndiscount on the &quot;Growth&quot; and &quot;Premium&quot; plans. Pl\ndiscount?\nsave\"\ntoggleButton = document.querySelector('[toggle-button]')\n","type":"text"}]}
Thinking…

I'm looking for where the discount information appears in relation to the Growth and Premium plans—specifically checking if there's an annual billing discount mentioned and what the actual percentage is.

$ cd /tmp && grep -o -E '.{250}discount on the &quot;Growth&quot;.{250}' plans.html | head -2; echo ====; grep -o -E '.{200}discount\?.{200}' plans.html | head -2{"content":[{"text":"p=\"round\" stroke-linejoin=\"round\"></path></svg></div></div><nav class=\"pricing_faq_accordian_bottom w-dropdown-list\"><div class=\"pri…ctrl+o
{"content":[{"text":"p=\"round\" stroke-linejoin=\"round\"></path></svg></div></div><nav class=\"pricing_faq_accordian_bottom w-dropdown-list\"><div class=\"pricing_faq_accordian_answer\">For early stage companies that are either bootstrapped, seed or pre-seed stage, we offer a discount on the &quot;Growth&quot; and &quot;Premium&quot; plans. Please contact sales for more info.</div></nav></div></div><div class=\"pricing_faw_item_divider\"></div><div><div data-hover=\"false\" data-delay=\"0\" class=\"pricing_faq_accordion w-dropdown\"><div class=\"pricing_faq_accord\n====\nover=\"false\" data-delay=\"0\" class=\"pricing_faq_accordion w-dropdown\"><div class=\"pricing_faq_accordian_top w-dropdown-toggle\"><div class=\"max-width-520px\"><div class=\"text-24px\">Do you have a startup discount?</div></div><div class=\"icon-1x1-small\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" viewBox=\"0 0 24 24\" fill=\"none\" class=\"svg\"><path d=\"M8 10.8284L12 14.8284L16 10.8284\" stroke=\"#1E6263\" str\n","type":"text"}]}
Thinking…

I should also look at the FAQ section to understand the billing model better — specifically whether it's per seat or includes unlimited views. Let me pull out the FAQ questions and answers from the pricing page by extracting the accordion-style Q&A pairs.

$ cd /tmp && python3 - <<'EOF'{"content":[{"text":"- pricing_faq\">\n- Frequently asked questions\n- Do you have a startup discount?\n- For early stage companies that are either bootstrapped…ctrl+o
cd /tmp && python3 - <<'EOF'
import re,html
s=open('plans.html').read()
i=s.find('pricing_faq')
seg=s[i:i+120000]
seg=re.sub(r'<svg.*?</svg>','',seg,flags=re.S)
seg=re.sub(r'<img[^>]*>','',seg)
t=re.sub(r'<[^>]+>','|',seg)
t=html.unescape(t); t=re.sub(r'(\s*\|\s*)+','|',t)
rows=[r.strip() for r in t.split('|') if r.strip()]
for r in rows[:120]:
    print('-',r[:300])
EOF

{"content":[{"text":"- pricing_faq\">\n- Frequently asked questions\n- Do you have a startup discount?\n- For early stage companies that are either bootstrapped, seed or pre-seed stage, we offer a discount on the \"Growth\" and \"Premium\" plans. Please contact sales for more info.\n- Does the number of demo viewers affect pricing?\n- No, our pricing structures are not influenced by the volume of visitors watching your demos.\n- What platforms do you integrate with?\n- We natively integrate with most CRM and marketing automation platforms. Here are a few - HubSpot, Salesforce, Marketo, Pardot, Intercom, Google Analytics, Segment, Zapier, Slack and API. We are constantly adding integrations - please refer our Knowledge Base for latest.\n- What type of support does Storylane offer?\n- Our support team helps customers via in-app Intercom chat, Slack and email always. For customers on \"Growth\", \"Premium\" or \"Enterprise\" plan, we provide onboarding and training with our dedicated Demo specialist team. We also provide individual demo reviews and consultations to help customers build \n- What you get across plans\n- Onboarding with\n- 24x7 support\n- Training videos and
help documentation\n- SOC2 Type 2 and GDPR\n- Compliant\n- Hear from our customers\n- #1 Demo Automation Software\n- 4.7\n- The UX is fantastic — but what really drew us was the innovation\n- Read Case Study\n- Intuitive, powerful, and easy to use platform.\n- Matt Slotten\n- Marketing\n- The interactive demo influenced our biggest deal yet!\n- Read Case Study\n- It’s actually kind of shocking how fast it was for us.\n- Edward Smith\n- Sr. Solutions Consultant\n- 35%\n- increase in pipeline\n- Tom Josephson\n- Solutions Consulting\n- Revolutionized our demo process. The AI tools are incredibly powerful.\n- Edward Smith\n- Sr. Solutions Consultant\n- The UX is fantastic — but what really drew us was the innovation\n- Read Case Study\n- Intuitive, powerful, and easy to use platform.\n- Matt Slotten\n- Marketing\n- The interactive demo influenced our biggest deal yet!\n- Read Case Study\n- It’s actually kind of shocking how fast it was for us.\n- Edward Smith\n- Sr. Solutions Consultant\n- 35%\n- increase in pipeline\n- Tom Josephson\n- Solutions Consulting\n- Revolutionized our demo process. The AI tools are incredibly powerful.\n- Edward Smith\n- Sr. Solutions Consultant\n- Storylane's by far the easiest and most reliable out of other competitors.\n- Martin Kurowski\n- Product Marketing Manager\n- Great and super easy to use for quick shareable demo's\n- Tamar S.\n- Marketing\n- $1.3M\n- Pipeline directly attributed to the interactive demo\n- Read Case Study\n- Our executive team continues to rave about how well Storylane visualizes our own tool!\n- Emily Elizabeth\n- VP Marketing\n- My Go-To Tool for showing how our platform works — so simple to use!\n- Christion Parker\n- Sales Manager\n- 35\n- demos in 3 weeks\n- Michael DeMarco\n- Product Marketing Manager\n- Storylane's by far the easiest and most reliable out of other competitors.\n- Martin Kurowski\n- Product Marketing Manager\n- Great and super easy to use for quick shareable demo's\n- Tamar S.\n- Marketing\n- $1.3M\n- Pipeline directly attributed to the interactive demo\n- Read Case Study\n- Our executive team continues to rave about how well Storylane visualizes our own tool!\n- Emily Elizabeth\n- VP Marketing\n- My Go-To Tool for showing how our platform works — so simple to use!\n- Christion Parker\n- Sales Manager\n- 35\n- demos in 3 weeks\n- Michael DeMarco\n- Product Marketing Manager\n- <path d=\"M100.379 13.1231C99.7581 13.1227 99.144 13.2554 98.5785 13.5122C98.013 13.769 97.5092 14.1439 97.101 14.6116C96.4228 13.8364 95.4911 13.3268 94.4718 13.1735C93.4525 13.0203 92.4119 13.2333 91.5352 13.7747C91.4363 13.5468 91.2618 13.3598 91.041 13.2454C90.8201 13.1311 90.5666 13.0963 90.323 \n","type":"text"}]}
Thinking…

Now I'm ready to write the README with the confirmed pricing details.

write /sandbox/repo/storylane-example/README.md{"content":[{"text":"Successfully wrote 10555 bytes to /sandbox/repo/storylane-example/README.md","type":"text"}]}
Thinking…

I need to verify the webhook script is working correctly by generating a test signature and validating it, check that the CLI's --pages flag defaults to 1 for sessions, confirm the paginate helper is properly adding ?page= parameters to endpoints like /demo_sessions, and test the HTML for syntax errors by running a quick Node check on any inline scripts.

$ cd /sandbox/repo/storylane-example && echo '{"event":"lead_captured"}' > /tmp/body.json && SIG=$(STORYLANE_WEBHOOK_SECRET=s3cret node -e "{"content":[{"text":"sig=txBNWwKuOpuX7gmZ1dBRvAh0XMVBDurz+N8oYCCg8tk=\nvalid signature\nexit=0\nINVALID signature\nexit=1\n","type":"text"}]}ctrl+o
cd /sandbox/repo/storylane-example && echo '{"event":"lead_captured"}' > /tmp/body.json && SIG=$(STORYLANE_WEBHOOK_SECRET=s3cret node -e "
const c=require('crypto'),fs=require('fs');
console.log(c.createHmac('sha256','s3cret').update(fs.readFileSync('/tmp/body.json','utf8')).digest('base64'));
") && echo "sig=$SIG" && STORYLANE_WEBHOOK_SECRET=s3cret node scripts/verify-webhook.mjs --sig="$SIG" < /tmp/body.json; echo "exit=$?"; STORYLANE_WEBHOOK_SECRET=s3cret node scripts/verify-webhook.mjs --sig="bogus" < /tmp/body.json; echo "exit=$?"

$ cd /sandbox/repo/storylane-example && python3 - <<'EOF'{"content":[{"text":"blocks: 1\ninline JS parses OK\n","type":"text"}]}ctrl+o
cd /sandbox/repo/storylane-example && python3 - <<'EOF'
import re
s=open('example/index.html').read()
blocks=re.findall(r'<script>(.*?)</script>',s,re.S)
open('/tmp/inline.mjs','w').write('\n'.join(blocks))
print("blocks:",len(blocks))
EOF
node --check /tmp/inline.mjs 2>&1 | head -5 || true
# wrap as module-safe check
node -e "
const s=require('fs').readFileSync('/tmp/inline.mjs','utf8');
new (require('vm').Script)(s.replace(/window\.|document\./g,'globalThis.__x?.') ); console.log('inline JS parses OK');
"

Thinking…

Everything checks out. I'll do a final review of the file structure and make sure the .env file isn't present, then decide whether to commit—since the repo only has .gitignore right now, I'll leave that decision to the user. Let me show them the file tree and run a quick smoke test on the CLI to verify the help output works with no arguments.

$ cd /sandbox/repo && find storylane-example -type f | sort && echo === && cd storylane-example && node scripts/storylane-api.mjs 2>&1 | tail -3; echo "exit=$?"; node scripts/storylane-api.mjs token --d…{"content":[{"text":"storylane-example/.env.example\nstorylane-example/.gitignore\nstorylane-example/README.md\nstorylane-example/example/index.html\nstorylane-…ctrl+o
cd /sandbox/repo && find storylane-example -type f | sort && echo === && cd storylane-example && node scripts/storylane-api.mjs 2>&1 | tail -3; echo "exit=$?"; node scripts/storylane-api.mjs token --demo=x 2>&1 | tail -2

{"content":[{"text":"storylane-example/.env.example\nstorylane-example/.gitignore\nstorylane-example/README.md\nstorylane-example/example/index.html\nstorylane-example/package.json\nstorylane-example/scripts/storylane-api.mjs\nstorylane-example/scripts/verify-webhook.mjs\n===\n *   node scripts/storylane-api.mjs token --demo=<demo-id> --link=<link-id> --email=you@co.com\n *   node scripts/storylane-api.mjs oembed --url=https://app.storylane.io/share/<link>   # public, no key needed\n \nexit=0\nerror: missing --link=...\n\n","type":"text"}]}
Thinking…

I've confirmed Storylane supports the developer workflow through embeds and API access, so I can move forward with the response. I'll structure it around what I built, explain the pricing model, note how manageable it is for agents, and flag the key blockers like missing credentials for authenticated calls and the demo authoring limitations.

Storylane does have a developer workflow I can act on (hosted embed SDK + a public REST API + an MCP server), so I built the example. Everything is hosted — no local stacks, nothing long-running.

What's in /sandbox/repo/storylane-example

README.md                     # quickstart, API reference, pricing, agent-manageability assessment
example/index.html            # the example page — open it directly, file:// is fine
scripts/storylane-api.mjs     # External API CLI (Node ≥18, zero deps)
scripts/verify-webhook.mjs    # webhook HMAC-SHA256 verification (no listener started)
.env.example / .gitignore / package.json

example/index.html covers the three embed patterns the CDN SDK (js.storylane.io/js/v1/storylane.js) auto-wires: div.sl-inline-embed[data-sl-url], a[href*=sl_popup], button.sl-demo-btn. Plus a token-link builder (?token[first_name]=…&email=…&utm_source=… — the SDK forwards host-page params into the demo iframe) and a live postMessage event log for the 12 cross-frame events (step_view, convert_cta, lead_identify, demo_finished, …) with a GA4 fan-out stub.

scripts/storylane-api.mjs wraps /api/connect/v1/workspaces/:id/...: demos, publish, stats report, sessions, leads, accounts, hubs, link CRUD, email tokens, and the public oembed endpoint.

Verified live: public oEmbed returns the real embed HTML; webhook signature check passes/fails correctly; CLI syntax + arg validation + 401 handling all exercised. Not verified: authenticated calls — no Storylane credentials exist here (STORYLANE_API_KEY is minted in the web UI, Settings → Integrations, shown once). That's the one thing I can't self-serve.

Pricing (from the live storylane.io/plans page)

PlanListIncludes
Free$01 seat, 1 published demo, unlimited views, lead capture
Starter$50/mo1 seat, unlimited demos, chapters, account reveal, HubSpot/Zapier, branding
Growth$625/mo5 seats, HTML demo editor, personalization tokens, A/B testing, multi-teams, CSM
Premium$1,500/mo10 seats, Salesforce, SSO, whitelabel URL, presenter demos, Hubs, deal intelligence
Enterprisecustomsecurity, pro services, private links, API, coaching

Model: per creator seat/tier, not per traffic — viewer seats are unlimited on every plan and Storylane states viewer volume doesn't affect pricing. Extra creator seats are add-ons (~$40/mo Starter, ~$100/mo Growth). Annual billing ≈20% cheaper ($40/$500/$1,200). Developer gating: External API + webhooks on paid plans (docs say all paid = Starter+), MCP Starter+ with writes in beta, Salesforce on Premium. Startup discount on Growth/Premium for seed/pre-seed. Practically, $50/mo Starter is the programmatic entry point.

Easy for me to manage? Mostly yes

Easy: zero infra (one script tag, one Bearer token), declarative embed API, URL-param personalization, plain postMessage events, docs available as .md + llms.txt + a GitBook ?ask= endpoint, and a first-party MCP server.

Friction: no POST /demos — the REST API can read/publish/link but not author content; creating demos needs MCP (convert_images_to_demo, personalise_demo, add_step) or the GUI editor. Credentials require a human in the web UI. No OpenAPI spec and no real npm SDK (Postman collection is the reference), so field drift is possible. Docs' own sample data includes a time_spent: -462, so reporting code should clamp.

Net: "embed, personalise links per prospect, pull engagement into our stack" — I can own end to end from a text editor. "Build the demo itself" — needs MCP credentials or a human.

Not blocked; nothing to commit (files are written, uncommitted).

AX Fundamentals

23 checks. Open an area for details.

This check is a quick preview. Test more tasks and models with Gauge Agents.

Test your product with Gauge Agents

AX Check by Gauge