How I Would Wire OpenAI Apps SDK Widgets Into HubSpot Campaign QA
On October 6, 2025, OpenAI introduced apps in ChatGPT and the Apps SDK, and the use case that caught my eye was not a travel booking demo or a shopping flow. It was campaign QA. A widget inside ChatGPT can turn a messy launch review into a small operating console, especially when every flagged asset […]
On October 6, 2025, OpenAI introduced apps in ChatGPT and the Apps SDK, and the use case that caught my eye was not a travel booking demo or a shopping flow. It was campaign QA. A widget inside ChatGPT can turn a messy launch review into a small operating console, especially when every flagged asset is written back to a HubSpot custom object tied to the campaign record.
That sounds narrow. Good. Marketing ops work gets expensive when it becomes vague. In one March 2026 build for a 40-person B2B SaaS team spending $92,000 a month across Google Ads, LinkedIn, Meta, Webflow pages, and HubSpot email, the launch checklist lived in five places. The growth lead had comments in a Google Sheet. The lifecycle marketer had HubSpot email drafts. The founder had Slack screenshots. The paid-search contractor had UTMs in a Notion table. Nobody had one record that said: this campaign is ready.
The boring fix was the right fix. We made HubSpot the source of truth and used a ChatGPT widget as the review surface. OpenAI’s Apps SDK is still in preview according to the August 2026 help docs, and it runs on Model Context Protocol, so I treated the widget as an interface layer, not the database. HubSpot kept the durable state. ChatGPT did the inspection, summarization, and reviewer workflow.
The Object Model
The HubSpot object was called Campaign Asset QA. In HubSpot’s 2026-03 CRM API, custom object records can be created, updated, searched, batch-read, and upserted through endpoints like /crm/objects/2026-03/{objectTypeId} and /batch/upsert. HubSpot also documents a 100-record limit on object batch endpoints, which matters the first time a launch has 143 ad variants and 18 landing-page snippets. That limit shaped the queue from day one.
I used one custom object record per reviewable asset, not one record per issue. That choice saved us from fake precision. A LinkedIn ad with a weak CTA, a missing UTM, and a non-approved claim is still one asset in the campaign manager’s head. The object had asset_external_id, campaign_code, channel, asset_type, qa_status, risk_level, owner_email, fix_notes, first_flagged_at, approved_at, and launch_blocker. A HubSpot deal-style pipeline would have been clumsy here. A custom object matched the work.
The associations mattered as much as the fields. HubSpot lets custom object records associate with existing records and activities once the association type is defined. We associated each Campaign Asset QA record to a parent Campaign record, plus a Company record when the campaign was account-specific. For a partner webinar on May 14, 2026, one HubSpot campaign had 37 QA records: 12 Google Ads sitelinks, 8 LinkedIn sponsored posts, 5 emails, 4 Webflow sections, 6 partner blurbs, and 2 retargeting audiences.
Why Put The Console In ChatGPT?
The review already started in ChatGPT. The operator pasted ad copy and landing-page blocks into a thread, asked for compliance checks against internal rules, then manually copied the flags into HubSpot. That last step was where records died. On the March 2026 launch, 11 of 64 flagged fixes never made it into HubSpot because the reviewer closed the tab after a call with sales.
A widget changes the muscle memory. The reviewer can ask ChatGPT to inspect a campaign bundle, then see a table of flagged assets with owners, risk, and fix status inside the same conversation. The widget is not there to look fancy. It gives the operator checkboxes, filters, owner assignment, and an approve button without forcing them to leave the reasoning context.
OpenAI’s UI guidance points toward this split. New UI should use _meta.ui.resourceUri to connect tools to UI resources, and the docs still mention _meta[openai/outputTemplate] as a compatibility alias for existing integrations. The docs also recommend keeping tools useful without UI, so I used data tools for inspection and mutation, then one render tool for the review console. When the iframe fails, the model still has structured JSON it can explain.
The Tool Shape
The MCP server had four tools. inspect_campaign_assets accepted raw asset text, URLs, HubSpot campaign ID, and a ruleset ID such as claims_policy_v3_2026_02. sync_asset_flags_to_hubspot upserted Campaign Asset QA records using asset_external_id as the custom unique identifier. update_asset_qa_status changed status, owner, or fix notes. render_campaign_qa_console returned the widget resource.
I kept write tools blunt. sync_asset_flags_to_hubspot had to say exactly what it changed: 37 inputs, 31 updated, 6 created, 0 failed. HubSpot supports batch upsert with idProperty, so the integration did not need to guess whether an asset already had a record. That is the small thing that prevents duplicate QA rows named Final final launch ad v2.
The widget had four tabs: Blockers, Needs Fix, Approved, and All. Each row showed channel, asset type, claim text, UTM status, owner, and last HubSpot sync time. The reviewer could approve 10 low-risk items in one pass, but launch blockers required a typed note. That rule was not moral theater. In the first two launches, every serious miss came from a claim or tracking gap, not from a typo.
What We Checked
The first ruleset covered 26 checks. Six were boring plumbing: missing utm_campaign, inconsistent utm_source, broken final URL, wrong HubSpot form GUID, stale pricing, and mismatched CTA. Eight were brand checks, including forbidden phrases, unsupported customer-name use, and product-tier naming. Four were paid media checks for Google Ads and LinkedIn character limits. The rest were launch readiness checks, like missing approval owner or no matching nurture email.
A concrete example helps. One Google Ads headline said SOC 2 ready in 7 days. The policy source in Notion said the product team only approved SOC 2 evidence workspace language after the February 9, 2026 legal review. ChatGPT flagged it as high risk, the widget assigned it to the lifecycle owner, and HubSpot stored the fix note against the campaign. Two hours later, the operator changed the copy to SOC 2 evidence in one workspace and hit Approve in the console.
The record in HubSpot carried the audit trail the team needed later. Not a perfect audit trail. HubSpot object history and associations are useful, but they are not a substitute for a regulated approval system like Veeva PromoMats. For a founder-led SaaS team with 9 marketers and no legal operations platform, it was enough: who saw the issue, what changed, when it cleared, and which campaign absorbed the fix.
The Sync Pattern
The HubSpot side used private app auth, scoped to CRM object read and write access for custom objects. The Apps SDK server never exposed the HubSpot token to the widget. OpenAI’s Apps SDK docs say tool results can include structured content for the model, text content, and _meta for client-specific data hidden from the model. I still kept secrets out of _meta. Hidden from the model does not mean safe from sloppy architecture.
For each inspection run, the server normalized asset IDs before calling HubSpot. Google Ads used googleads:{customer_id}:{campaign_id}:{ad_group_id}:{asset_id}. LinkedIn used linkedin:{account_id}:{campaign_id}:{creative_id}. HubSpot emails used hubspot_email:{email_id}. Webflow sections used webflow:{site_id}:{page_id}:{slug_hash}. Those IDs look ugly, but ugly stable IDs beat pretty labels every launch week.
The job processed HubSpot writes in chunks of 100 because of the documented batch limit. Each chunk got an idempotency key stored in Postgres 16.3 with the campaign ID and SHA-256 hash of the asset payload. If the reviewer clicked Sync twice, the second run updated the same records and returned the existing HubSpot IDs. No duplicate mess.
What Changed After Two Launches
Before the console, the team’s April 2026 launch review took 3 hours and 40 minutes across two meetings. After the widget and HubSpot custom object went live, the May 2026 partner campaign review took 74 minutes, including 18 minutes of copy fixes. The bigger win was cleaner ownership. The first launch had 14 Slack follow-ups after approval. The second had 3.
The quality numbers moved too. In April, 9 assets launched with at least one tracking or copy issue. In May, 1 did, and it was a LinkedIn thumbnail mismatch outside the first ruleset. That miss became check number 27. I like systems that admit new scar tissue quickly.
The founder cared about a different number: launch delay. The April launch slipped by 2 business days because nobody wanted to sign off with unresolved comments. The May launch shipped at 9:12 a.m. Pacific on the planned Tuesday. Not because the QA was magical. The open issues were visible on the HubSpot campaign record before the go or no-go call.
Where This Breaks
This setup breaks when HubSpot is not already the team’s operating spine. If campaign truth lives in Airtable, Salesforce, Linear, or Monday.com, forcing HubSpot custom objects into the middle will feel like furniture bolted to the floor. The Apps SDK widget can still work, but the durable record should sit where approvals already happen.
It also needs discipline around policy sources. The checker is only as good as the rule pack. For this team, the rule pack pulled from three places: a Notion claims page last edited February 9, 2026, a CSV of approved product terms, and a JSON file of UTM conventions. If those files rot, the console will confidently file bad flags. I would rather have 18 maintained checks than 80 stale ones.
The security line is simple. ChatGPT can host the review experience, but HubSpot owns approval state, Postgres owns sync idempotency, and the MCP server owns authorization. The widget is allowed to ask for a status change. The server decides whether the user can make it.
Build The Small Version First
I would start with 3 HubSpot statuses: Needs Fix, Approved, and Blocked. Add 12 checks, not 50. Wire one channel first. Google Ads is a good candidate because final URLs, headlines, descriptions, and UTMs have crisp failure modes. Once the object model survives two launches, bring in LinkedIn, HubSpot email, and Webflow pages.
Use the current docs as the guardrails. OpenAI’s Apps SDK preview docs explain the MCP-based app model and Developer Mode testing. The ChatGPT UI docs spell out the tool-to-resource pattern, including _meta.ui.resourceUri, ui/notifications/tool-result, and widget state. HubSpot’s custom object API docs cover 2026-03 object endpoints, associations, upsert, and the 100-record batch ceiling. Those are enough for a useful build.
The point is not to make ChatGPT the CRM. That would be backwards. The useful move is to put the review console where the review conversation already happens, then write every decision back to HubSpot before it can evaporate into Slack, screenshots, or someone’s Tuesday morning memory.
Sources: OpenAI Apps SDK help, OpenAI ChatGPT UI docs, OpenAI launch post, HubSpot custom object records API.
Newsletter
Get growth playbooks in your inbox.
Practical SEO, PPC, automation, and web strategy from the Micromarketing team. No fluff, unsubscribe anytime.
