Marketplace
HubSpot Marketplace is a curated catalog and a real review, not a publish button. This page walks through every artifact a 2026 reviewer will actually open: the security questionnaire, the sensitive-scope justification, the platform-version requirement, the listing copy, and the five reasons most submissions get bounced. By the end you'll have a submission you can defend in writing.
TL;DR — A Marketplace listing is a real review, not a publish button. A 2026 reviewer opens your security questionnaire, sensitive-scope justifications, platform-version compliance, and listing copy, and most submissions fail for five known reasons. This guide preps every artifact before you click submit.
Before you begin
The HubSpot Marketplace is a curated catalog. The team running it has the same incentive any app-store reviewer has: keep the catalog something the platform owner is proud to recommend. Getting listed is a review, not a publish, and the reviewer is a human who opens your install URL, clicks around, and reads your privacy policy. The clearer you make their job, the faster you get out of the queue.
There are five concerns the reviewer works through, in roughly this order. Get all five right and the review is short. Miss one and you bounce, usually with a one-line reason and a four-week wait before you can resubmit.
The five reviewer concerns, in order
- Security. OAuth handling, token storage, the scopes you ask for, and what happens when something goes wrong. The May 2026 update expanded the OAuth/token sections of the questionnaire and a lot of the new fields are about sensitive-scope justification.
- Reliability. Does the app stay up? Does it handle HubSpot's 429s gracefully? Does it degrade or just throw on a transient failure? Reviewers test this by installing on a clean portal and watching for visible errors during the first 24 hours.
- UX. Does install actually work? Does the app communicate state (loading, empty, error) instead of staring blankly at the user? Is there a path to uninstall and re-install without manual support?
- Accuracy. Does what the listing claims match what the app does? If your one-liner says "scores email deliverability" and the reviewer can't find a score anywhere inside the app within 60 seconds of installing, that's a rejection.
- Brand fit. Is the app something HubSpot is comfortable recommending? This is the soft criterion and the one most under-prepared founders skip. A perfectly secure app that overlaps 90% with HubSpot's native CRM is a harder sell than a perfectly secure app that fills an obvious gap.
The rest of this guide walks the five concerns in order, with the exact artifacts each one asks for.
The app under review: Email Guard
The submission this guide prepares is Email Guard, the email-deliverability app every other guide builds: the validate-email workflow action, the suppression-list sync, the email-health card, and the email-verified timeline event. It started life as a private app in Getting started, and the app-events guide already flipped distribution: 'private' to 'marketplace' and auth to 'oauth' in hsx.config.ts, because timeline events require both. That flip is the technical half of going public; this guide is the review half, where the same declaration becomes the thing a human evaluates.
What HS-X handles for you (so you can skip those sections)
HS-X auto-generates the OAuth install URL, handles BAD_REFRESH_TOKEN recovery with a re-install nudge, pins your manifest to a supported platform version, and routes 429s through a rate-limit-aware HTTP client. The reviewer-facing surface is still yours, but the load-bearing parts of "yes the OAuth flow works" are handled. The places you still have to write something yourself are the questionnaire answers, the scope justifications, and the listing copy, which are exactly what the rest of this guide covers.
Pre-flight: from 'works on my portal' to OAuth-ready
Most pre-marketplace apps are subtly portal-coupled. They were built and tested on one developer portal, with one OAuth token, against one set of properties. The marketplace reviewer installs on a fresh portal with a different account topology, different custom properties, and a user role that may not match yours. Pre-flight is the exercise of making the app behave correctly under that mismatch.
Scope minimization
Ask for the narrowest scopes that let your features work, and nothing else. Reviewers explicitly look at the scope list on install and flag any scope whose use is not obvious from the listing description. A common 2026 rejection is "asks for crm.objects.contacts.write but the listing only describes a reporting feature." Email Guard requests exactly two scopes, and each one maps to a feature the listing names:
// hsx.config.ts — declare scopes once; the app manifest is generated from this.
export default defineApp({
name: "Email Guard",
scopes: [
"crm.objects.contacts.read", // the email-health card and check-email-health tool read verdicts
"crm.objects.contacts.write", // validate-email and the suppression-list sync write them
],
});The write scope survives review because the listing describes a write feature: the verdict properties on the contact. If Email Guard were card-only, the same scope would be the rejection example above. When a feature inside your app needs a broader scope than the core ones (say, a future deal-stage write from the card), declare it as an optional scope and gate the feature behind it. The reviewer sees a smaller required-scope list at install and a clearly named optional permission users opt into separately.
BAD_REFRESH_TOKEN recovery
HubSpot refresh tokens rotate. Tokens get revoked when a portal admin uninstalls and reinstalls, when a user is removed from a portal, or when HubSpot rotates the underlying app secret. Every production install hits this within the first few weeks. The reviewer will not test this directly, but they will install, uninstall, and reinstall on the same portal, and if your app crashes on the second install because it cached a stale token, that's an immediate bounce.
The HS-X runtime owns this failure mode. When a token refresh fails with a definitive revocation (BAD_REFRESH_TOKEN, invalid_grant), the runtime marks the install uninstalled and stops invoking capabilities against that portal; the portal's uninstalledAt lands in your install telemetry. It distinguishes revocation from transient failures, so a HubSpot 5xx never gets a portal marked dead. When the same portal reinstalls, the OAuth callback accepts the fresh install and the runtime resumes: no stale-token crash, because nothing in your handler code ever held the token.
What is yours: surfacing the state. Watch for uninstalled portals in your monitoring and treat a portal that could reinstall as a customer-success ping, not just a metric.
Install URL builder and install error handling
A live install URL is required for submission. Build it from your app's client ID, scope list, and redirect URI, and confirm you're hitting HubSpot's current OAuth endpoints (the 2026 update migrated to the v3 OAuth endpoints; double-check the OAuth quickstart before pasting). Keep the URL's scope list in sync with the scopes array in hsx.config.ts; the reviewer compares scope-list-on-install against listing-page-claim.
A dedicated hs-x marketplace install-url subcommand is on the roadmap but not yet shipped. For now, assemble the URL by hand from the values in your app config, or copy the install URL HubSpot surfaces in the developer-portal app settings.
For install error handling, the rule is simple: if anything fails during the OAuth callback, show a page that names the error and links to your support URL. The most common bounce in this area is "install loop": the OAuth callback throws, the user gets bounced back to the consent screen, they click approve again, the callback throws again. See step 7 for the fix pattern.
Secrets handling (where the refresh token lives, who can read it, how it's rotated) is covered separately. The reviewer will ask in the questionnaire how you store tokens at rest; read How to · Secrets before you fill out step 2.
The security questionnaire
The questionnaire is the single longest artifact in the submission. The May 2026 update expanded the OAuth/token sections; the form spans data handling, access control, incident response, and sensitive-scope justification. Most are short text answers; a handful are essay-length and those are where reviewers spend their time. This section gives you a fact matrix for HS-X — what is actually true about where your app's data lives and how it is handled — so your answers are grounded rather than aspirational. It uses Email Guard as the worked example; the shape is the same for any HS-X app.
The answers below are a factual starting point, not a compliance sign-off. Every one still needs legal and security review before you submit, and that review is the app owner's responsibility (for HS-X itself, Carter McKay). Two of the fields the questionnaire asks for are owner-owned open items that are not settled yet: a published security-reporting contact, and committed data-retention / deletion numbers. Where a row below says not yet available, manual, or owner-decision-pending, that is the honest answer — do not upgrade it to a capability the app does not have. Under-claiming costs you nothing with a reviewer; a promise they can disprove with one network request costs you the listing. The internal reference for the incident and security-contact gaps is the HS-X incident runbooks (docs/operations/incidents/README.md, "Reporting a security issue").
The HS-X data-handling fact matrix
HS-X stores data in two tiers (ADR-014 §9), and almost every questionnaire answer follows from that split:
- Tenant data plane — the developer's own Cloudflare account (D1, KV, R2). This holds installer/user records, app-generated data, and tenant-emitted events. In direct/unlinked mode it is the only place this data ever lives (ADR-010, ADR-014 §9).
- HS-X control plane — HS-X's own Cloudflare account. It holds only platform-operating metadata: install-lifecycle state, billing/invoice rows, sync mappings, and the platform credentials the developer explicitly connected. It never holds a shadow copy of installer PII or event payloads (ADR-014 §9).
| Questionnaire topic | What is actually true for an HS-X app |
|---|---|
| Data ownership | The developer owns tenant data; it lives in their own Cloudflare account (ADR-010). HS-X operates the platform but is not the custodian of installer PII or app records. |
| Storage location / residency | Tenant data lives in whatever region the developer's Cloudflare account/D1 provisions — HS-X does not relocate it (ADR-014 §9). Control-plane metadata lives in HS-X's Cloudflare account. HS-X makes no specific-region or EU-residency guarantee for the control-plane tier; do not claim one. (Region commitments: owner-decision-pending, confirm with legal.) |
| Retention | Tenant-data retention is the developer's choice — HS-X documents recommendations but does not enforce a number (ADR-014 §9). Control-plane billing/lifecycle rows are retained for billing/tax needs. No customer-facing retention period is committed (the P04 recovery/retention policy is not yet written — see incident runbook 05). Owner-decision-pending. |
| Data flow — HubSpot | Access is limited to the OAuth scopes the customer grants on install; the install token is tenant-owned and never sent to the HS-X control plane (ADR-010). |
| Data flow — Cloudflare | Cloudflare is the compute + storage substrate. Tenant data stays in the developer's Cloudflare account; control-plane metadata in HS-X's (ADR-006). |
| Data flow — Stripe | Only in hsx-platform billing mode. The developer's connected Stripe account is the merchant of record for their app revenue and owns tax handling; HS-X takes an application fee via Stripe Connect (ADR-012, ADR-024). In self-managed mode HS-X is not in the money path at all. |
| Uninstall / deletion behavior | Detection is real: HubSpot sends no uninstall webhook, so the runtime detects the revocation at the next token refresh, marks the install uninstalled, and records uninstalledAt (ADR-016). Deletion of portal-keyed data is the developer's own code against their own bindings — HS-X ships no automatic uninstall data-deletion job and no committed deletion window. Manual / not yet automated. |
| Encryption at rest | Connected platform credentials in the control plane use AES-256-GCM envelope encryption with per-record AAD and a Worker-held keyring (ADR-019). Web-session tokens are stored hash-only (ADR-020). Tenant data at rest sits on Cloudflare storage under Cloudflare's platform encryption; HS-X does not add app-layer encryption over arbitrary tenant records. |
| Encryption in transit | TLS/HTTPS on every hop (Cloudflare-terminated). |
| Breach / incident response | HS-X runs a set of incident runbooks (credential exposure, D1 data incident, and others) under a single-operator model where the owner is incident commander (ADR-017; docs/operations/incidents/). No breach-notification SLA in hours is committed yet, and there is no published security-reporting address (owner email only, today). Owner-decision-pending. |
| Certifications (SOC2 / ISO / GDPR DPA) | None held or claimed. Do not assert any. If a customer requires one, that is a roadmap conversation, not a checkbox. |
Paste-ready answers (adapt, then have them reviewed)
These honor the matrix above. Fill the bracketed owner-decision items with a real value after legal/security sign-off — do not ship them with the bracket text in place.
Data residency:
"Installer data and app-generated records live in the developer's own Cloudflare account (D1/KV/R2), in whatever region that account provisions; HS-X does not relocate it (ADR-010, ADR-014 §9). HS-X's control plane stores only platform-operating metadata — install-lifecycle state, billing records, and connected platform credentials — and holds no copy of installer PII or event payloads. We do not currently guarantee a specific processing region for that control-plane tier and make no EU-residency commitment beyond the developer's own Cloudflare account. [Owner to confirm any region commitment with legal before this answer ships.]"
Deletion on uninstall:
"Uninstalls are detected at the next authentication attempt for the portal — HubSpot sends no uninstall webhook — at which point the install is marked uninstalled, all capability invocations for that portal stop, and
uninstalledAtis recorded in install telemetry. Deletion of data keyed to that portal is application code running against the developer's own Cloudflare bindings; there is no built-in automatic deletion job today, and we do not currently commit a deletion-window number. [Owner/legal to set and publish a retention-and-deletion policy before this answer ships.]"
Breach notification:
"HS-X operates incident runbooks covering credential exposure, data incidents, and related classes under a single-operator model in which the owner is the incident commander. Suspected security issues are reported to the owner directly today; a published security-reporting channel and a committed breach-notification SLA are not yet in place. [Owner/security to set the notification SLA and publish a security contact before this answer ships.]"
Reviewers strongly prefer one honest paragraph that names a gap over a confident claim they can disprove. The bracketed items are exactly the fields the app owner must close with legal and security before submission — not before this guide can help you draft the rest.
Sensitive-scope justification
Step 3 covers this in full because it is the single most-rejected questionnaire section in 2026.
Sensitive-scope justification — the 2026 rule
Some OAuth scopes are flagged sensitive and require a written justification at submission. The exact list and the format HubSpot wants you to use both move around. Check the current app marketplace listing requirements before you write yours, and treat that page as canonical.
The shape of a justification that tends to land: name the user-visible feature that requires the scope, then name the safeguards (what you write, when, what you don't touch, how conflicts resolve). Keep it specific. "We sync data" with no feature named or no conflict rule is the most common rewrite request.
What does not work
Do not bundle a sensitive scope into the required list "in case we need it later." The justification reviewer asks "what feature today uses this scope," and if the answer is "future roadmap," the listing is rejected. Add the scope when the feature ships, not before.
Where branding lives: project source vs the listing editor
HubSpot shows your app's branding on two surfaces, and they are edited in two different places. The install consent screen and the in-account app view read from the app manifest your project deploys. The marketplace listing page reads from a listing you fill out in HubSpot's UI, and it never reads your project source. Most "where do I upload the logo?" confusion is a mix-up between the two.
What ships from your project source
The deployed app manifest carries the app name and the install-screen description. In an HS-X project both come from hsx.config.ts and are regenerated on every hs-x deploy:
// hsx.config.ts — name and description ship in the generated app manifest.
export default defineApp({
name: "Email Guard",
description: "Scores every contact's email deliverability and imports your suppression list every 5 minutes.",
distribution: "marketplace",
auth: "oauth",
platformVersion: "2026.03",
scopes: ["crm.objects.contacts.read", "crm.objects.contacts.write"],
});App name and description ship from your project source on every deploy; HubSpot's developer UI shows them read-only. To confirm what is live:
- In your HubSpot developer account, go to Development.
- On the Projects page, click your project's name.
- On the Overview tab, under Project Components, click your app's name.
This page shows the most recently deployed app schema, with Auth and Distribution tabs alongside it. There is no edit field for name or description here; change them in hsx.config.ts and deploy again.
defineApp fields for the manifest logo and the support-contact block (support email, documentation URL, support URL, support phone) are planned but not yet shipped. Until they land, the generated manifest carries placeholder support values and no logo entry. The marketplace icon below is a separate upload and is unaffected.
What you upload in the listing editor
Everything a marketplace visitor sees comes from the listing editor. The marketplace icon is an 800px by 800px JPG, JPEG, or PNG, uploaded on the listing's Listing info tab: no text, no wordmark, and the image should touch at least two edges of the canvas. To open the editor:
- In your HubSpot developer account, go to Development.
- In the left sidebar, click App Listings.
- In the upper right, click Create listing. To change an existing listing, hover over it, click More, then Edit.
- Select your app, pick the primary listing language, and click Next.
You must be a Super admin in the developer account to create or submit an app listing. The wizard has seven tabs: Listing info, App details, Pricing, App features, Support info, Testing info, and Review info. Step 5 covers the copy that goes into them; the table below covers which place owns which branding field.
Field by field: source tree or HubSpot UI
| Branding field | Where to change it |
|---|---|
| App name (consent screen and in-account) | name in defineApp, then redeploy |
| Install-screen description | description in defineApp, then redeploy |
| Public app name, company name, tagline | Listing editor, Listing info tab |
| Marketplace icon (800 × 800) | Listing editor, Listing info tab, App icon section |
| Categories, URL path, search terms | Listing editor, Listing info tab |
| Screenshots, demo video, app overview | Listing editor, App details tab |
| Pricing plans and currencies | Listing editor, Pricing tab |
| Support contact and resources | Listing editor, Support info tab |
| Verified domain (install-screen trust banner) | Developer account, Development, then Domain |
The listing's Public app name is a separate field from the manifest name; keep the two identical so the name a customer clicks in the marketplace matches the name on the consent screen. The verified domain in the last row is what replaces the "this app hasn't been verified" install banner for apps that aren't listed yet; HubSpot's app management guide walks the DNS setup. For every manifest field beyond branding, the canonical reference is HubSpot's app configuration page.
Listing copy: the four required artifacts
The listing page is your conversion surface. A reviewer reads it for accuracy. A prospective customer reads it for fit. The same copy has to satisfy both. Four artifacts are required, and each has a pattern that converts.
The one-liner (≤80 characters)
This is the headline next to your app icon in marketplace search results. The pattern that converts: a verb, a noun the user recognizes, and a payoff. Skip the company name, skip "the best," skip adjectives.
- Bad: "Email Guard — the best email verification integration for HubSpot users." (puffery, no payoff)
- Better: "Score every contact's email deliverability before your next send."
- Bad: "AI-powered email intelligence for revenue teams." (vague, no concrete output)
- Better: "Flag bounced and suppressed addresses on the contact record automatically."
The description
Roughly 200–400 words, structured as: problem → who has it → what the app does in one sentence → how install works in three steps → who it integrates with. The reviewer is checking the third bullet against the actual install. The customer is reading the first two. Keep paragraphs short. Email Guard's:
Every send to a dead address chips away at your sender reputation, and
most teams find out from a bounce report after the damage is done. If
your deliverability rate drops every time marketing imports a list, this
app is for you.
Email Guard scores every contact's email deliverability through your
verification provider, imports your suppression list every 5 minutes,
and shows the verdict on an email-health card on every contact record.
Workflows can branch on the score before a send goes out.
To install:
1. Click Install on this page and grant the requested scopes.
2. Paste your verification provider's API key on the welcome screen.
3. Add the "Validate email address" action to any contact workflow — done.
Works with: any verification provider exposing a verify-and-suppression
API. Writes only Email Guard's own contact properties (email health
status, score, suppression fields); never edits the address itself.The install walkthrough
A linear, screenshot-backed walkthrough of the first 90 seconds after Install. Three to five screens, each with a one-sentence caption. Reviewers use this as the test script when they install your app; if step 2 of Email Guard's walkthrough shows a "paste your API key" screen and the reviewer can't find that screen after Install, the listing fails accuracy review.
Screenshots and video
Three to six screenshots minimum, captured from a real portal with realistic data. Stock-looking screenshots (three contacts named "John Doe," empty pipeline columns, lorem-ipsum company names) are a common reason we see listings sent back ("screenshots not from a real portal"). Use a dev portal seeded with the kind of data your target customer would have. A 60–90 second video is optional but generally helps install conversion.
An hs-x marketplace diff subcommand that compares your local listing copy against the live draft is planned but not yet shipped. Today, hand-diff the listing form against your local copy before resubmitting.
The platform-version supported requirement
HubSpot's May 2026 changelog lists both 2025.2 and 2026.03 as currently supported platform versions for new listings; either one is fine, as long as you target a currently supported version. Older versions outside that supported set cannot be listed. The HS-X CLI defaults new projects to 2026.03.
Check your current version in hsproject.json (the first deploy writes it; the value mirrors platformVersion in hsx.config.ts):
{
"name": "email-guard",
"srcDir": "src",
"platformVersion": "2026.03"
}Before you submit, confirm your last deploy ran against a currently supported version. HubSpot's supported platform versions page is the source of truth; re-check it when you submit, because the supported set rolls forward over time.
Multi-environment setup (which Cloudflare account ID and which HubSpot portal each platform version targets) is covered in How to · Environments. Get that wired before you submit, so the reviewer's test portal lands on your prod environment, not your dev.
Common rejection reasons and how to dodge them
Five reasons we commonly see listings sent back. Each one has a specific fix. Run through the table before you submit; most teams get bounced on something they could have caught in 20 minutes of self-review.
| Common reason we see | What the reviewer saw | The fix |
|---|---|---|
| OAuth install loop | Clicked Install, granted scopes, callback threw, bounced back to consent. Tried twice. Gave up. | Wrap the callback handler in a try/catch that renders a named error page with a support link. An onInstallError hook that renders a default error page is planned; today, own that try/catch yourself. |
| Sensitive scope unjustified | A scope in the sensitive category was requested without a feature-specific justification, or the justification said "future use." | Step 3. Justify per scope, name the feature, name the safeguards. If the feature is opt-in, declare the scope optional. |
| No uninstall data deletion | Installed, populated data, uninstalled, asked in writing whether data was deleted; answer was vague or a number the app can't actually meet. | The runtime's uninstall detection is real (installs get marked uninstalled, ADR-016); the deletion sweep is your code against your own bindings — HS-X ships no built-in deletion job. Write the sweep, then quote only a deletion window you can actually meet, or say deletion is on request until you've set a policy. Don't invent a number. |
| Screenshots not from a real portal | Screenshots showed "John Doe", empty pipelines, an email-health card with every field reading "deliverable". | Use a dev portal seeded with realistic data: contacts with risky and suppressed addresses, not just clean ones. The reviewer compares screenshots against the live app on their test install — too much polish in the screenshot vs the install is also a flag. |
| Listing claim does not match feature surface | One-liner promised deliverability scoring. Reviewer installed, opened a contact, and the email-health card sat empty with no hint that a workflow action does the scoring. | The "what the app does in one sentence" bullet must point to a UI element the reviewer can find on first install. Email Guard's card renders an empty state that names the workflow action and links the setup page; if your feature lives behind a settings toggle, name the toggle. |
What happens after submission
HubSpot's listing-requirements doc states the review/feedback loop should take no more than 60 days from the time HubSpot shares feedback; expect up to that long, and longer when sensitive scopes are involved. You'll get one or two rounds of questions from the reviewer in writing. Answer the same day if you can, because every reply restarts a multi-day clock on their side.
You can ship updates to the underlying app during review without resubmitting the listing, as long as you don't change the scope list or break the install walkthrough. Scope changes mid-review reset the security clock. Listing copy changes mid-review reset the accuracy clock. Save both for after approval.
Where next
- How to · Billing — once the listing is live, wire usage-based or seat-based billing so installs actually generate revenue. HubSpot does not currently operate a marketplace billing system; you bring your own (Stripe, etc.) and the billing guide covers the wiring.
- How to · Secrets — the questionnaire's "how do you store tokens at rest" section maps directly onto HS-X's secrets handling, and the answer is short if you've already read this guide.
- How to · Environments — keeping a clean
prodenvironment separate from yourdevandstagingportals is what lets the marketplace reviewer land on the right install without you scrambling at the last minute.
