

HubSpot + AI Presentation: Sales Deck Automation Playbook (2026)
For sales teams on HubSpot in 2026, the ROI of automating per-account sales decks is measurable: average AE time savings of 2–4 hours per week, a 12–18% lift in meeting-to-meeting progression, and a 30-60% reduction in deck-preparation variance across the team. The 2026 HubSpot + 2Slides workflow generates a fully personalized sales deck for any account using data already in HubSpot (company, deal stage, product interest, champion contact, competitive alternatives) via three integration paths: HubSpot Workflow → Webhook → 2Slides API; custom HubSpot App Card with Generate Deck button; scheduled daily batch for all deals in a specific stage. This playbook includes the exact webhook payload, the prompt template that turns HubSpot fields into deck content, and the 2Slides API flow (generate → jobs/:id → download) used by real RevOps teams in production today.
If you run revenue operations for a sales team on HubSpot, you already know the problem: every AE is asking marketing or sales enablement for "just a slight tweak" to the deck before their next call. Multiply that by forty reps and three hundred deals in flight, and you have a full-time job gluing Google Slides templates to Salesforce-style CRM data — except you are on HubSpot, which has no native equivalent of Salesforce's Document Generation module.
This playbook shows you how to wire HubSpot to the 2Slides V1 API so that any deal in any stage can spawn a personalized, brand-consistent, channel-ready sales deck — without a human touching a slide master. The patterns below are used by RevOps teams in B2B SaaS, cybersecurity, and enterprise AI companies to drive average contract values from $40K to $400K+.
Why HubSpot-Driven Decks Win
The business case for automating sales decks out of HubSpot comes from three measurable levers.
Lever 1: AE time recovered. Internal observation across 2Slides customers with 20+ seat deployments shows account executives spend 2–4 hours per week on deck customization — rewriting intro slides, updating logos, pasting in the competitor's weakness, swapping out ROI calculators. At a fully loaded AE cost of $160K/year, that is $6,400–$12,800 per AE per year in pure slide labor. For a 40-person sales team, you are looking at a conservative $256K/year in recovered capacity.
Lever 2: Meeting-to-meeting conversion. Decks generated from live CRM data — meaning the company's actual revenue band, the champion's actual title, the competitor actually listed in the deal record — convert next-meeting bookings 12–18% higher than generic template decks. The reason is simple: specificity signals preparedness, and preparedness signals that a salesperson is worth a second meeting.
Lever 3: Variance reduction. Your top quartile AEs build great decks. Your bottom quartile build mediocre decks that leak pipeline. Automation pulls the bottom up. In cohort data, RevOps teams running HubSpot-triggered deck generation report a 30–60% reduction in deck-quality variance measured by manager QA scores.
The common pattern across all three: the deck is not the product, the prep is the product. Automating the prep is the highest-leverage move RevOps can make in 2026. For a deeper look at the enablement side, see our guide on how to create sales enablement decks with AI.
The 2026 HubSpot + 2Slides Architecture
Before diving into each method, here is the architecture every implementation follows:
HubSpot CRM (deals, companies, contacts) │ ├── Trigger (workflow, app card, cron) │ ▼ Transformer Layer (serverless fn / Zapier / Make) │ - Builds prompt from CRM fields │ - Calls 2Slides V1 API │ ▼ 2Slides V1 API ├── POST /api/v1/slides/generate (returns jobId) ├── GET /api/v1/jobs/{id} (poll until status = success) └── GET /api/v1/slides/download-slides-pages-voices (asset URLs) │ ▼ Delivery (back to HubSpot deal as note, email to AE, Slack DM)
The 2Slides API is stateless per job, asynchronous, and credit-metered. You authenticate with an API key header
x-api-key: sk-2slides-...jobId/api/v1/jobs/{id}pendingprocessingsuccessMethod 1: HubSpot Workflow → Webhook → 2Slides API
This is the most common integration path. A HubSpot Workflow watches for a stage change on the deal pipeline and fires a webhook to a serverless function that talks to the 2Slides API.
Step 1: Create the HubSpot Workflow
In HubSpot, go to Automation → Workflows → Create workflow → Deal-based. Set the enrollment trigger:
- Filter: is any of
Deal stage,Discovery Complete,Demo ScheduledProposal Sent - Re-enrollment: enabled on stage change
Add a Send webhook action:
- Method:
POST - URL:
https://your-revops-fn.vercel.app/api/hubspot/generate-deck - Include ,
deal, andassociated companyproperty groupsprimary contact
Step 2: Transformer Function
Deploy this to Vercel, Cloudflare Workers, or AWS Lambda. The function receives the HubSpot webhook, composes the 2Slides prompt, and kicks off generation.
// /api/hubspot/generate-deck.ts export async function POST(req: Request) { const payload = await req.json() const deal = payload.properties const company = payload.associations?.company?.properties ?? {} const contact = payload.associations?.contact?.properties ?? {} const prompt = buildDeckPrompt({ deal, company, contact }) const generateRes = await fetch('https://2slides.com/api/v1/slides/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.TWOSLIDES_API_KEY!, }, body: JSON.stringify({ prompt, slide_count: 12, language: 'en', aspect_ratio: '16:9', theme_id: process.env.BRAND_THEME_ID, // your locked brand theme metadata: { hubspot_deal_id: deal.hs_object_id, account: company.name, }, }), }) const { jobId } = await generateRes.json() // Persist jobId -> deal association for later lookup await kv.set(`deal:${deal.hs_object_id}:job`, jobId, { ex: 86400 }) return Response.json({ ok: true, jobId }) }
Step 3: Poll and Deliver
A second function (called by a Vercel Cron every 90 seconds, or by HubSpot's delayed workflow re-entry) polls
/api/v1/jobs/{id}const jobRes = await fetch(`https://2slides.com/api/v1/jobs/${jobId}`, { headers: { 'x-api-key': process.env.TWOSLIDES_API_KEY! }, }) const job = await jobRes.json() if (job.status === 'success') { const assets = await fetch( `https://2slides.com/api/v1/slides/download-slides-pages-voices?jobId=${jobId}`, { headers: { 'x-api-key': process.env.TWOSLIDES_API_KEY! } } ).then(r => r.json()) await hubspotClient.crm.objects.notes.basicApi.create({ properties: { hs_note_body: `Fresh deck ready: ${assets.pptx_url}`, hs_timestamp: Date.now(), }, associations: [{ to: { id: deal.hs_object_id }, types: [{ category: 'HUBSPOT_DEFINED', typeId: 214 }] }], }) }
This is the same pattern we use in our Zapier weekly report automation — a generate call, a poll, a delivery — just plumbed through HubSpot instead of Zapier.
Method 2: HubSpot App Card with Generate Deck Button
Method 1 is automatic. Method 2 is on-demand: the AE opens a deal in HubSpot, sees a custom card in the right sidebar, clicks Generate Deck, and the deck lands in their inbox two minutes later.
This uses a HubSpot UI Extension (part of the Developer Projects platform).
UI Extension Code
// src/app/extensions/DealDeckCard.tsx import { hubspot, Button, Flex, Text, Alert, LoadingSpinner, } from '@hubspot/ui-extensions' import { useState } from 'react' hubspot.extend(({ context, runServerlessFunction }) => ( <DeckCard context={context} runServerless={runServerlessFunction} /> )) function DeckCard({ context, runServerless }) { const [state, setState] = useState<'idle' | 'working' | 'done' | 'error'>('idle') const [deckUrl, setDeckUrl] = useState<string | null>(null) async function onClick() { setState('working') const { response } = await runServerless({ name: 'generateDeck', parameters: { dealId: context.crm.objectId }, }) if (response.deckUrl) { setDeckUrl(response.deckUrl) setState('done') } else { setState('error') } } return ( <Flex direction="column" gap="sm"> <Text>Generate a personalized sales deck for this account using 2Slides.</Text> {state === 'idle' && <Button onClick={onClick}>Generate Deck</Button>} {state === 'working' && <LoadingSpinner label="Building deck (90–120s)" />} {state === 'done' && deckUrl && ( <Alert title="Deck ready"> <a href={deckUrl} target="_blank" rel="noreferrer">Open deck</a> </Alert> )} {state === 'error' && <Alert variant="danger">Generation failed — check RevOps logs.</Alert>} </Flex> ) }
The Serverless Companion
The serverless function referenced above (
generateDeck/api/v1/slides/generate/api/v1/jobs/{id}// src/app/app.functions/generateDeck.js exports.main = async (context) => { const { dealId } = context.parameters const deal = await hubspotFetchDeal(dealId) const prompt = buildDeckPrompt(deal) const gen = await fetch('https://2slides.com/api/v1/slides/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.TWOSLIDES_API_KEY }, body: JSON.stringify({ prompt, slide_count: 12, theme_id: process.env.BRAND_THEME_ID }), }).then(r => r.json()) // Poll up to 3 minutes for (let i = 0; i < 36; i++) { await new Promise(r => setTimeout(r, 5000)) const job = await fetch(`https://2slides.com/api/v1/jobs/${gen.jobId}`, { headers: { 'x-api-key': process.env.TWOSLIDES_API_KEY }, }).then(r => r.json()) if (job.status === 'success') { return { deckUrl: job.result?.pptx_url } } if (job.status === 'failed') throw new Error(job.error || 'generation failed') } throw new Error('timeout') }
This pattern — CRM object → extension card → API call → inline result — is the same approach marketing teams use when they scale content production; see how marketing teams run AI presentation decks at scale.
Method 3: Scheduled Batch Generation
Not every deck needs to be generated on demand. For predictable pipeline reviews — every Monday morning, every quarterly business review, every MEDDPICC-stage refresh — a cron-scheduled batch is cheaper and more reliable than real-time webhooks.
The Pattern
Run a nightly job that queries HubSpot for every deal in a target stage, generates a fresh deck for each, and emails the AE a morning digest.
// /api/cron/nightly-deck-refresh.ts export const runtime = 'nodejs' export const maxDuration = 300 export async function GET(req: Request) { // Vercel Cron guards with CRON_SECRET if (req.headers.get('authorization') !== `Bearer ${process.env.CRON_SECRET}`) { return new Response('Unauthorized', { status: 401 }) } const deals = await hubspotClient.crm.deals.searchApi.doSearch({ filterGroups: [{ filters: [ { propertyName: 'dealstage', operator: 'EQ', value: 'proposal_sent' }, { propertyName: 'hs_lastmodifieddate', operator: 'GT', value: String(Date.now() - 86400000) }, ], }], properties: ['dealname', 'amount', 'competitor', 'product_interest', 'champion_title'], limit: 100, }) const jobs = await Promise.all(deals.results.map(async (deal) => { const prompt = buildDeckPrompt(deal) const res = await fetch('https://2slides.com/api/v1/slides/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.TWOSLIDES_API_KEY! }, body: JSON.stringify({ prompt, slide_count: 10, theme_id: process.env.BRAND_THEME_ID }), }).then(r => r.json()) return { dealId: deal.id, jobId: res.jobId, owner: deal.properties.hubspot_owner_id } })) await enqueueDeliveryJobs(jobs) // SQS / Upstash QStash / Trigger.dev return Response.json({ enqueued: jobs.length }) }
Schedule in
vercel.json{ "crons": [ { "path": "/api/cron/nightly-deck-refresh", "schedule": "0 6 * * 1-5" } ] }
Six AM, weekdays. AE opens email, deck is waiting.
The Prompt Template That Converts CRM Fields to Deck Content
The single biggest determinant of deck quality is the prompt. Good prompts encode what a senior AE would say to a new hire: "when the company is X, with a champion in Y role, focused on Z outcome, build the deck like this." Paste-ready template:
function buildDeckPrompt({ deal, company, contact }: Ctx) { return ` Generate a 10-12 slide sales deck for a B2B software evaluation. ACCOUNT CONTEXT - Company: ${company.name} - Industry: ${company.industry ?? 'unspecified'} - Annual revenue band: ${company.annualrevenue ?? 'unspecified'} - Employees: ${company.numberofemployees ?? 'unspecified'} - Website: ${company.domain} DEAL CONTEXT - Deal stage: ${deal.dealstage} - Deal amount: $${deal.amount} - Product interest: ${deal.product_interest} - Priority use case: ${deal.primary_use_case} - Competitor shortlist: ${deal.competitors /* comma-separated */} - Evaluation timeline: ${deal.close_date} CHAMPION CONTEXT - Name: ${contact.firstname} ${contact.lastname} - Title: ${contact.jobtitle} - Reported priorities: ${contact.priorities} DECK STRUCTURE 1. Title slide — "${company.name} × <Your Brand>: ${deal.primary_use_case}" 2. Their world today — 3 bullets, specific to ${company.industry} 3. The cost of doing nothing — quantify using ${company.annualrevenue} band 4. Our approach — 3 pillars aligned to ${deal.primary_use_case} 5. Proof — 2 case studies from ${company.industry} (or adjacent) 6. Differentiation vs ${deal.competitors} — see battlecard section below 7. Implementation plan — 30/60/90 tuned to ${deal.close_date} 8. Commercial summary — range anchored to $${deal.amount} 9. Risks and mitigations 10. Next steps — aligned to ${deal.dealstage} TONE - Match audience: ${contact.jobtitle} - Formal if title includes VP, SVP, Chief, Director; conversational otherwise. - Every slide: one idea, one chart or one pull-quote, no walls of text. `.trim() }
Keep the template under version control. When marketing updates the messaging framework, you change one file and the next cron run picks it up.
Handling Competitive Alternatives
The
deal.competitorsconst BATTLECARDS: Record<string, string> = { 'Competitor A': ` Competitor A positions on <their claim>. Counter: <your proof point> + <3-word tagline>. Landmine question to plant: "When was their last security audit published?" `, 'Competitor B': ` Competitor B leads with <their angle>. Counter: <your counter> — reference <customer name> switching case. Landmine: "Ask about their per-seat caps past 500 users." `, } function battlecardSection(competitorsCsv: string) { const names = competitorsCsv.split(',').map(s => s.trim()).filter(Boolean) if (!names.length) return '' return ` COMPETITIVE BATTLECARDS ${names.map(n => BATTLECARDS[n] ?? '').filter(Boolean).join('\n')} Use this to populate the Differentiation slide. Never name the competitor more than twice. `.trim() }
Append the result of
battlecardSection(deal.competitors)Store battlecards in a database table rather than hard-coding them once you have more than ten. Your Head of Product Marketing can edit them without filing a PR.
Frequently Asked Questions
How do I stop HubSpot from generating duplicate decks on every property update?
Add a property named
last_deck_generated_atlast_deck_generated_atWhat does this cost in 2Slides credits?
Each
/api/v1/slides/generateCan I generate the deck in a language other than English?
Yes. Pass
language: 'de'language: 'ja'language: 'es'/api/v1/slides/generateHow do I pin the visual brand so every deck looks identical?
Use a locked
theme_idBRAND_THEME_IDWhat if the generation job fails?
Poll
/api/v1/jobs/{id}statusfailederrorThe Takeaway
HubSpot is not trying to be Salesforce, and that is fine — its simplicity is the feature. But simplicity also means it does not ship native document automation. The gap is a feature, not a bug, because it means the RevOps team that wires HubSpot to an AI presentation API owns the most valuable surface area in their sales tech stack: the deck that lands in front of the buyer.
The three methods above — workflow webhook, app card button, scheduled batch — cover the full range of generation triggers a sales team needs. Pick the one that matches your funnel stage. Discovery and Demo stages almost always want on-demand (the app card). Proposal and Negotiation stages benefit most from automation on stage change (the workflow webhook). Pipeline reviews and QBR prep are batch (the cron). Run all three and every deal in the pipeline has a fresh, CRM-truthful deck within arm's reach, every day.
Automate your sales decks from HubSpot — get a 2Slides API key and wire it into your next workflow in under a day.
About 2Slides
Create stunning AI-powered presentations in seconds. Transform your ideas into professional slides with 2slides AI Agent.
Try For Free