Now live across the AI ecosystem: ChatGPT GPT Store · MCP Registry · mcp.so

Platform guide

The TofuBofu HubSpot integration publishes blog posts, and touches nothing else

By Arnav Mukherjee, founder of TofuBofu · September 3, 2026

We built a check that fails the build when an adapter can see a field at its destination and never says what it does with it. HubSpot passed on the first run. It passed because the check was comparing one half of the adapter against the other half of itself, so we could have added a brand new key to the outgoing payload and the build would've stayed green. The check now assembles a real payload from a fully populated test article and diffs that instead.

Which is the right mood for a guide about publishing into somebody's marketing system. HubSpot sells its own AI visibility tracker, and we publish into HubSpot, and both of those are true at once. Their CMS happens to be a perfectly good place to put the fix. Owning the CMS was never the work.

So here's the whole connector in one sentence: it publishes blog posts to your HubSpot blog, and it never opens your CRM.

One endpoint, three calls

The adapter talks to /cms/v3/blogs/posts and to nothing else on api.hubapi.com. It calls it three times across a connection's whole life. Once with limit=1 to prove your token works. Once with a slug filter, to check whether the article's already there. Once as a POST, to create it.

No contacts, no deals, no companies, no forms, no timeline events. The private app you create needs the CMS content scopes and nothing wider, so the token you hand us can't reach your CRM even if our code went looking. The only other place HubSpot appears in our product: our crawler detects HubSpot CMS on a prospect's site, and the scan intake says we can publish straight into it.

The outgoing article is six keys, built in one place:

name · The title, capped at 255 characters.

contentGroupId · Which blog it lands in, taken from the connection.

postBody · The rendered article.

slug · Derived from the title, capped at 190 characters.

state · PUBLISHED or DRAFT, whichever you asked for.

metaDescription · Only when we hold one, capped at 300 characters.

Four fields we decline, with the reason for each

HubSpot's blog schema offers more than six keys. We leave four of them alone deliberately, and our build won't let us leave them alone quietly.

featuredImage

HubSpot hosts its own images and expects a File Manager id here, not a remote URL. So your hero image sits at the top of the post body instead, where it renders for a reader and stays readable to a crawler.

tagIds

HubSpot blog tags are numeric portal ids. We have never read your tag list, so offering you a picker would mean inventing the options. Posts land untagged, and you tag them in HubSpot in about four seconds.

authorName

HubSpot resolves the author from a blog author record, not from a free-text name. A string here gets ignored rather than applied, and an ignored field that looks filled is worse than an empty one.

publishDate

A draft has no publish date, and HubSpot stamps one itself the moment you publish.

Our Webflow connector makes you map six field roles before it'll write anything, because a Webflow collection's fields are invented by whoever built the site. HubSpot needs none of that, and the reason isn't that HubSpot's easier. HubSpot decided your blog schema for you. Every field is either always written or always declined, so there's nothing left to ask. Real convenience, real cost, and a guide that pretends the cost away isn't worth reading.

Two connectors, two bargains

Webflow HubSpot Bespoke collection Fixed blog schema You map six roles once Nothing to map Schema field if you give it one No schema field at all Cannot read a post back Returns the live URL Six questions, then it honours every answer. No questions, because you were never asked.

Connecting it, step by step

One prerequisite comes first and it sinks people quietly: blog publishing needs a HubSpot Content Hub (CMS) plan. Marketing Hub on its own won't serve it. Your brand also needs a domain saved on its Settings, because we store the connection against a website, and we refuse with "Set this brand's domain first" when there isn't one.

The multi-blog trap, and how to walk around it

Leave Blog ID blank and we resolve your blog by reading one existing post and taking its content group id. On a portal with a single blog, that's exactly right and you'll never think about it. On a portal running three blogs, one for the product, one for careers, one a legacy acquisition, that's an ordering rather than a choice. Whichever post HubSpot hands back first decides where your articles go.

We won't dress that up. Webflow's connect screen gives you a collection dropdown and HubSpot's has no blog picker, so the optional Blog ID field is doing that job. If you run more than one blog, paste it. The lookup's a best-effort read on our side, and a blank result leaves you with a connection that publishes nowhere useful.

Your FAQ schema stays with you, permanently

Of the 39 B2B sites we crawled, 26 carried no FAQ schema at all, 67% of them. So the gap's worth closing and we won't close it here for you. HubSpot's API can't read a post back to us, which makes it a destination we can't verify, and our rule for an unverifiable destination is that the JSON-LD doesn't leave. Unlike Webflow, there's no configuration that switches it on, because HubSpot's blog post schema has no field to put it in.

You get the block back through the Copy schema button on the content card, and HubSpot gives you a good home for it: Settings, Website, Pages, Site Header HTML. Thirty seconds, once. A wall of raw JSON stranded inside the body of your live post is worse than no schema, so where we can't check, we don't embed and hope.

Find out which buying questions your HubSpot blog already loses

A free scan asks six AI engines what your buyers ask, and names whoever they answer with instead of you.

Get your free audit

Where HubSpot beats Webflow outright: the receipt

HubSpot returns the live URL when it creates a post. Small detail, large consequence. With a URL we can go and read the published page, so a delayed check fetches it and reports what it saw, with the time it saw it. The receipt lands on the content card as plain sentences, never a score, never a grade, never a percentage:

Checked your live page, 2 minutes after publishing.
The post is live and the text matches what we sent.
Your page carries BlogPosting structured data. Its headline is empty, which usually means a title field on your CMS was not filled.
We did not find the FAQ schema in the page's HTML. If your site adds it with JavaScript, most AI crawlers will not run it.

The check reads raw HTML on purpose and executes no JavaScript, because most AI crawlers don't run any either. So a HubSpot module that injects your schema client side produces a silent receipt, and the silence is the finding.

One line on that receipt is worth your attention, because it took a bug to earn it. Every publish records one of three states for your FAQ schema: we sent it, we deliberately withheld it, or we don't know. On a HubSpot publish the answer's always the middle one, so the receipt says the absence is ours and not your site's. The third state exists because the receipt once told a customer their FAQ schema was missing from their page when we'd chosen not to send it. Their page was missing nothing. Absent is a claim, and a claim needs evidence.

Same discipline runs through the rest of the receipt. A missing headline, a body we couldn't match, a title we never had: each one can come back as "asked, and no" or as "not asked", and the two never collapse into each other. A receipt that says nothing about your FAQ schema isn't a receipt that looked and found nothing. It's a receipt refusing to make a claim it can't evidence. And when a page refuses our own crawler, we record that as our obstacle in your copy as well as in our row. One customer's site answers 200 to GPTBot and 403 to us. We identify as TofuBofuBot and we won't swap in a browser string to get around it, because a measurement you got by lying isn't a measurement.

What goes wrong, and what we say when it does

A rejected token at connect gets you one of two messages, and the difference matters. A 401 means the token's wrong, so check you copied the whole thing. A 403 means the token's real and the scopes are missing, so add CMS content read and write, then make a new token. Editing scopes on an existing private app doesn't retro-fit the token you already copied.

A portal with no blog on it refuses at publish time with the fix in the message: create a blog in HubSpot, or enter its Blog ID when connecting. Anything else HubSpot rejects comes back with HubSpot's own status code and HubSpot's own words, not ours. We don't paraphrase a vendor's error into something friendlier and less useful.

And we can't take a post down. HubSpot has no retraction path for us, so removing a published article is your click inside HubSpot. Our own row clears either way, so the card never sticks.

The reason any of this exists

Our own research found firms already running their site on HubSpot CMS and still missing from AI answers. We wrote that up separately and the finding hasn't aged. A crawlable, well-built, professionally hosted site is table stakes, and table stakes buy you nothing on their own.

In our own citation corpus, only 48 of 882 distinct cited sources had no path at all, 5.4% of them. Engines cite pages that answer a question, not companies. Treat that as a direction and not a rate: it describes Google AI Mode and Bing Copilot across 55 scans, and four of those scans supply 95.8% of the sources.

So the publish isn't the deliverable. The re-scan is. A post that goes out through the connector carries its target question with it, and gets marked cited when a later scan finds an engine naming your brand for that exact question. We count the transition only, and we return nothing at all when there's no earlier report to compare against, because a first scan already showing citations isn't a win we caused. Paste the article in by hand and that chain breaks at the first link: nothing on our side knows the page exists, so nothing can referee it.

Frequently asked questions

Does connecting HubSpot give TofuBofu access to my CRM?

No. The adapter calls exactly one HubSpot endpoint, the blog posts API at /cms/v3/blogs/posts, and it calls it three times: once with limit=1 to check your token works, once with a slug filter to look for an existing post, and once as a POST to create the article. No contacts, no deals, no companies, no forms, no timeline events. The private app you create only needs the CMS content scopes, so the token you hand us cannot reach the CRM even if the code tried.

What HubSpot plan do I need to publish from TofuBofu?

Blog publishing through the API requires a HubSpot Content Hub (CMS) plan. Marketing Hub alone will not do it. If your portal has no blog on it at all, the publish is refused with a plain message telling you to create a blog in HubSpot or enter its Blog ID when connecting.

How do I create the HubSpot private app token?

In HubSpot: Settings, Integrations, Private Apps, Create a private app. On the Scopes tab, enable the CMS content read and write scopes, create the app, and copy the access token. You can find it again later under Settings, Integrations, Private Apps, your app, Auth. Paste it into the Private app access token field in TofuBofu and press Connect.

Do I need to enter a Blog ID?

Only if your portal runs more than one blog, and then you should. Leave it blank and we resolve your blog by reading one existing post and taking its content group id, which on a multi-blog portal is an ordering and not a choice. There is no blog picker. Paste the Blog ID and the ambiguity disappears.

Does TofuBofu add FAQ schema to my HubSpot post?

No, and there is no setting that turns it on. HubSpot's blog post schema has no field for structured data, and its API cannot read a post back to us, so we will not write JSON-LD we cannot verify. You get the schema handed back through the Copy schema button on the content card. Paste it in HubSpot under Settings, Website, Pages, Site Header HTML.

Why did my post arrive with no tags and no featured image?

Both are declined on purpose. HubSpot's featured image field takes a File Manager id rather than a remote URL, so your hero sits at the top of the post body instead. HubSpot blog tags are numeric portal ids, and we have never read your tag list, so offering a picker would mean inventing the options. Posts land untagged and you tag them in HubSpot. We also decline the author name, because HubSpot resolves authors from a blog author record and ignores a free-text string, and the publish date, because HubSpot stamps one when the post goes live.

What does the publish receipt actually check?

HubSpot returns the live URL when it creates a post, so a delayed check fetches that page and reads its raw HTML, with no JavaScript executed, because most AI crawlers do not run any either. It reports what it observed and the time it observed it: whether the post is live, whether the text matches what we sent, and what structured data the page carries. Where it could not check something it says nothing about it rather than reporting a zero.

Sources and further reading

Keep reading: the Webflow connector and its field map · they run HubSpot and AI still does not name them · HubSpot AEO at $50, claims vs reality