The short answer
Integration-led growth means your product gets found, trusted and kept because it connects to tools the buyer already uses. A GTM engineer owns the technical half: OAuth connections, marketplace listings and their review rules, automation-platform apps, and the data that shows which installs become revenue. This stage teaches the plumbing and each marketplace's rules.
Why this matters
The roadmap says: "Buyers trust their existing stack more than your cold email. Ecosystems are the ultimate distribution." Test it, do not repeat it.
- Buyers avoid reps. Gartner's March 2026 press release is titled "67% of B2B buyers prefer a rep-free experience". Gartner's page returned HTTP 403 to automated fetching, so only the headline is checked here and the survey method is not. It supports self-serve discovery. It does not measure trust in an existing stack.
- Partners lift win rates, unevenly. Crossbeam's network data (article dated November 8, 2024, data as of October 28, 2024) reports an 11.7% average win rate lift when partners are involved. The lift is 2.5% for 5 to 10 connected partners, 37.1% for 50 or more, and a 1% drop for enterprise firms in the 5 to 10 band. The page does not say whether lift means percentage points or a relative change. The data covers companies on the vendor's own network, so it is correlation, from a company that sells ecosystem tools.
- Integrations and churn. Crossbeam's 2023 survey says integration users are 58% less likely to churn. The page gives no sample size or method. Engaged customers may simply be the ones who connect. Test it on your own cohorts.
- Platforms control the gate. This part is true. Slack limits commercially distributed apps outside its Marketplace (apps created on or after May 29, 2025, and new installs of older ones) to 1 request per minute and 15 objects per request on
conversations.historyandconversations.replies.
Verdict: "ultimate distribution" is overstated. Ecosystems are one strong channel for products that sit beside a platform with a big installed base. No study found tests the "trust" claim directly. Every marketplace also sets minimums, so a startup with no customers cannot just list and wait.
Core concepts
Integration-led growth, defined
Integrations do three jobs. They are a distribution channel (a listing is a search result inside the tool the buyer lives in), a retention hook (a connected product costs more to remove) and a data source for the scoring and routing work from earlier stages. It is not partner marketing, and it is not a public API. An API is a door. An integration is a built, supported room behind it. Worked example: a sales team lives in Slack all day, so an app that answers /signal acme.com there gets used more than a dashboard in another tab.
| Shape | You build | Example |
|---|---|---|
| Automation-platform app | Triggers and actions on Zapier or Make | A Zapier integration with 5 to 10 Zap templates |
| Native marketplace app | An OAuth app plus handlers inside one platform | A Slack app with a slash command |
| Embedded integrations | An in-product "Connect your tools" layer | Merge, Paragon or Nango behind a settings page |
OAuth 2.0 authorization code flow with PKCE
OAuth lets a user give your app limited access to their account elsewhere without sharing a password. The authorization code flow, shown with Slack:
| Step | Who | What happens |
|---|---|---|
| 1 | Your app | Sends the browser to https://slack.com/oauth/v2/authorize with client_id, scope, redirect_uri and a random state |
| 2 | User | Reads the permission screen and clicks Allow |
| 3 | Provider | Redirects to your redirect_uri with a short-lived code and your state |
| 4 | Your app | Checks state matches what it sent, which blocks forged callbacks |
| 5 | Your app | Server-side, POSTs code and client_secret to https://slack.com/api/oauth.v2.access |
| 6 | Provider | Returns an access token (Slack bot tokens start xoxb-), plus bot_user_id, app_id, team, and authed_user if you asked for user scopes |
Fields are from Slack's OAuth guide. The Marketplace rules require the state parameter.
PKCE (Proof Key for Code Exchange, pronounced "pixy" in the RFC) stops a thief who steals the code from swapping it for tokens. Your app makes a random code_verifier (43 to 128 characters), sends only its SHA-256 hash as the code_challenge in step 1, and sends the real verifier in step 5. The provider hashes and compares. RFC 7636 defines it; use method S256. A worked pair, which the test code reproduces:
| Value | Example |
|---|---|
| code_verifier | dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk |
| code_challenge | E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM |
The 2025 OAuth security best current practice, RFC 9700 says public clients must use PKCE, recommends it for confidential clients like your server, requires exact redirect URI matching, and says not to use the implicit grant.
Support differs, so check each provider. Salesforce's web server flow help page did not load for automated fetching, so its PKCE support is unverified here. Zapier has built-in PKCE. HubSpot's quickstart and Slack's guide do not mention it. Make PKCE a per-provider switch. Slack's token rotation guide says Bolt handles Slack rotation for you, so the hand-written refresh code below is for providers like HubSpot.
Token storage and refresh
An access token is the key sent with each API call, and it expires. A refresh token gets a new one. Read expires_in instead of guessing. Worked example: a token issued at 10:00:00 with expires_in 1800 expires at 10:30:00, so refresh at 10:29:00 when the 60 second margin starts. A tenant is one customer account, such as one Slack workspace or one HubSpot account. HubSpot access tokens last "currently 30 minutes". Slack tokens with token rotation last 43,200 seconds (12 hours).
- Encrypt tokens at rest. Slack's security page says "Never place them in a public code repository or client-side code" and to make sure you are not logging user tokens. Keep the encryption key in an environment variable.
- Refresh before expiry, when about 60 seconds remain, not after a 401.
- Serialize refreshes per tenant. Slack says refresh tokens "are designed to be used once". Slack's docs say the used token is revoked after a short grace period and an app that reuses it "will fail on the second refresh". If two workers refresh together, one can spend a dead token and the customer's connection can break. Use a lock.
- Decide on rotation before launch. Slack says "Token rotation may not be turned off once it's turned on."
- Expect revocation. On uninstall, mark the tenant disconnected and prompt a reconnect. Do not retry forever.
Slack apps: Bolt, scopes and Marketplace review
Bolt for Python is Slack's framework. It checks the X-Slack-Signature header and rejects stale requests when you pass your signing secret, per Slack's verification guide. A scope is a named permission such as commands. Ask for the fewest you can justify. The guidelines list admin., identity., search:read, workflow.steps:execute and triggers:* as unsuitable. A slash command is a typed command such as /signal that Slack sends to your server as an HTTP request.
From the guidelines and review guide:
| Item | Requirement |
|---|---|
| Real usage | Ineligible if installed on fewer than 10 active workspaces and under 10 weekly active users (active means used in the past 28 days, not sandboxes) |
| Preliminary review | Up to 10 business days |
| Functional review | Up to 10 weeks for new apps, 6 weeks for updates |
| Short description | 10 words or fewer |
| Image | 1600 x 1000 px, under 2 MB, jpg or png |
| Support | Reply within 2 business days, with no extra account needed to reach you |
| Landing page | Public web page with an "Add to Slack" button, post-install next steps and a privacy link |
| Security | state on OAuth, TLS 1.2 or higher, signed-request verification |
| Not eligible | Sign in with Slack only, message export or backup, LLM training on Slack data, crypto or NFT payments, coded workflows |
You need real usage before you can list, so the first installs cannot come from the listing. They come from your customers, partners and a direct "Add to Slack" link.
Status, October 2026: Salesforce's April 17, 2026 press release says AppExchange, the Slack Marketplace and the Agentforce ecosystem are now one marketplace, AgentExchange (the Italian page was checked). The press release says 2,600+ Slack apps and agents are in it. Slack's developer docs still describe the review above, so follow them for Slack.
Salesforce: security review and AgentExchange
Salesforce's security review is the hardest gate here. What could be confirmed:
- Two scans. Code Analyzer v5 is free in the Salesforce CLI. The Source Code Scanner (Checkmarx) runs through the Partner Security Portal. Salesforce retired its hosted dynamic scanner (Chimera) in June 2025, so you now run your own dynamic scan, for example with OWASP ZAP or Burp Suite, on external services your package calls. Source: Tekunda, a third party, because Salesforce's own docs returned HTTP 403 to automated fetching.
- Fee. Paid solutions may carry a security review fee. The amount could not be verified, because Salesforce's docs returned HTTP 403, so confirm it before you plan a budget.
- Auth. Salesforce has been moving new apps from Connected Apps to External Client Apps. The release timing is unverified, so check your org's release notes.
Free path: a Developer Edition org from developer.salesforce.com/signup. The page returned HTTP 403 to automated fetching, so its limits are unverified.
HubSpot App Marketplace
The listing requirements say: at least three active, unique installs in the past 30 days, counted as production accounts unaffiliated with your organization that show successful app activity; OAuth as the sole authorization method; initial review "within 10 business days" and no more than 60 days in total from shared feedback; one app submitted at a time; live URLs, each capped at 250 characters.
Since September 22, 2025, apps created or migrated to platform version 2025.2 are capped at 25 installs until listed. The restrictions page bars apps that mainly broker credentials or OAuth tokens or are developer-only infrastructure, and delists apps that get no install in a 180 day period (existing installs stay).
Zapier and Make
Zapier apps expose triggers (events), actions (writes) and searches that users wire to other apps without code. On the developer platform, building and publishing is free. A Zapier developer replies "in 1 week or less" after you submit, and an approved integration stays in Beta for 90 days. Zap templates are pre-built workflows, only for public integrations; Zapier recommends 5 to 10, with about 2 weeks of review. Private integrations can invite up to 100 users. Plans start free with 100 tasks a month (pricing).
Make is the visual-scenario alternative. Its pricing page lists Free at $0 for 1,000 credits a month, then Core $12, Pro $21 and Teams $38 a month on monthly billing, each with 10,000 credits. You build custom apps in the web editor or VS Code, per the Make developer docs. Publishing and verification rules are unverified.
Embedded iPaaS: Merge, Paragon and Nango
An embedded iPaaS (integration platform as a service) is a vendor's integration layer that you put inside your product so customers connect their own tools. You trade control and margin for speed.
- Nango: free plan with 10 connections; pay-as-you-go at $50 a month (which includes $50 of credits) and $0.29 per connection; auth for 1,000+ APIs. Its repo says "fully open source", but the license is the Elastic License 2.0, which bars offering it as a hosted service and is not OSI-approved. Call it source-available.
- Merge: a unified API. Launch links 3 production accounts free, then $650 a month for up to 10. Higher tiers are contract priced.
- Paragon: billed on connected users, with a free Platform tier listed. Its site showed no ownership change on October 7, 2026, but no news search was possible, so treat that as unchecked.
These help you connect to many apps. They do not replace a native listing, where review and branding matter.
Co-selling and listing optimization
Co-selling means your rep and a platform's rep work one deal together. On AWS Marketplace, a buyer can spend a committed cloud budget on your software. AWS's fee page says, effective January 5, 2024: SaaS public offers 3%, server products 20%, private offers 3% under $1M, 2% from $1M to under $10M, 1.5% from $10M and on renewals, plus 0.5% for channel partner private offers.
Co-selling needs ecosystem data: which target accounts are also a partner's customers. Crossbeam, which lets two companies compare customer lists without exposing them, merged with Reveal in 2024. The combined company is called Crossbeam, and the announcement says over 25,000 companies had adopted either network. It gives no exact date or terms. Crossbeam's pricing page lists a free plan with 3 seats and a Connector plan at $4,800 a year. Without that tool, join two exchanged customer lists on domain:
SELECT o.account_name, o.amount, p.partner_contact
FROM open_opportunities o
JOIN partner_customers p ON lower(o.domain) = lower(p.domain)
ORDER BY o.amount DESC;Worked example: if open_opportunities holds Acme at 40000 and Globex at 15000, and the partner's list holds acme.com, the query returns one row, Acme at 40000. That row is your co-sell target, so you ask the partner rep for an intro.
A funnel is the ordered steps users pass through, with some lost at each step. A listing is a landing page with a funnel: impression, view, install click, installed, activated (core action within 7 days). Illustrative numbers: 1,000 views, 120 install clicks (12%), 90 installs, 27 activated (30%). The weakest step is activation, so fix it first. Change one thing at a time: name, 10-word description, first screenshot. Write around the job the user does. Slack can delist abandoned or broken apps, so activation protects the listing too.
Key terms
The tools
| Tool | What it does | Pricing model | Notes |
|---|---|---|---|
| Slack Bolt for Python | Framework for Slack apps with OAuth and signature checks | Free to build | Free basic sandbox (1 month); standard sandbox lasts 6 months and needs a paid plan or a payment method. Marketplace now sits inside AgentExchange |
| Salesforce AgentExchange | Marketplace for Salesforce apps, Slack apps and agents | Review fee for paid solutions, amount unverified | Free Developer Edition org. AppExchange is now AgentExchange. Confirm fees on current docs |
| HubSpot developer platform | Build and list apps in the HubSpot App Marketplace | Free to build and list | Free developer test accounts with a 90 day trial. 25 install cap for 2025.2 apps until listed |
| Zapier Developer Platform | Public or private Zapier integrations and Zap templates | Free to build and publish | Private integrations work for testing with up to 100 users |
| Make | Visual automation with custom apps | Free plan, then $12 to $38 a month (monthly billing) | Billed in credits. Publishing rules unverified |
| Nango | Auth, token refresh and API proxy for embedded integrations | Free plan; then from $50 a month | Elastic License 2.0 source-available. README says self-hostable, limits unverified |
| Merge | Unified API across software categories | 3 free production accounts; then $650 a month | Contract tiers above Launch |
| Paragon | Embedded iPaaS with workflows and managed sync | Free Platform tier; Pro by sales | Priced by connected users |
| Crossbeam | Ecosystem data: account overlap with partners | Free plan (3 seats); Connector $4,800 a year | Merged with Reveal. Use a CSV join with no budget |
Build the project
You will build "SignalPing", a Slack app that a workspace installs in one click and uses with /signal acme.com to get a score and a reason for an account. A manifest is a YAML file that describes a Slack app, so you can create it without clicking through forms. An HTTPS tunnel (for example cloudflared or ngrok) gives your laptop a public HTTPS address that Slack can reach. Fernet is the encryption recipe in Python's cryptography package. It stores installs securely, records usage events and reports an install-to-activation funnel. You will also write its marketplace listing copy and test it against the requirements above. A provider-neutral OAuth client with PKCE and safe refresh is included for HubSpot.
| Piece | Choice |
|---|---|
| App framework | Slack Bolt for Python on Flask |
| Install storage | Bolt's SQLite installation store |
| Third-party tokens | Fernet-encrypted rows with a refresh lock |
| Metrics | An events table and one SQL query |
| Local hosting | Any HTTPS tunnel to localhost |
- Join the Slack Developer Program.
- Provision a free sandbox workspace.
- Start an HTTPS tunnel to port 3000 and copy its host name.
- Replace YOUR-HOST in the manifest below with that host name.
- Create a Slack app from the manifest, with the single bot scope
commands. - Set the client ID, client secret and signing secret as environment variables.
- Run
app.py. - Open
/slack/installin a browser, click Add to Slack, and confirm a row inslack_installations. - Run
/signal acme.comin Slack and confirm you see an answer and aneventsrow. - Add
oauth_pkce.pyand connect it to a HubSpot developer test account. - Run the funnel query and record installs, activated installs and the rate.
- Write the listing copy: a 10-word description, a privacy page and a support page.
display_information:
name: SignalPing
description: Account buying signals in Slack, one command.
features:
bot_user:
display_name: signalping
slash_commands:
- command: /signal
url: https://YOUR-HOST/slack/events
description: Show the latest buying signal for an account domain
usage_hint: acme.com
oauth_config:
redirect_urls:
- https://YOUR-HOST/slack/oauth_redirect
scopes:
bot:
- commands
settings:
token_rotation_enabled: truepython -m venv .venv && source .venv/bin/activate
pip install slack-bolt flask requests cryptography
export SLACK_CLIENT_ID="paste-client-id"
export SLACK_CLIENT_SECRET="paste-client-secret"
export SLACK_SIGNING_SECRET="paste-signing-secret"
python app.pyapp.py (tested with slack-bolt 1.30.0 and Flask 3.1: a signed request from an installed workspace returns the answer, a bad signature gets HTTP 401):
import os
import sqlite3
import time
from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_sdk.oauth.installation_store.sqlite3 import SQLite3InstallationStore
from slack_sdk.oauth.state_store.sqlite3 import SQLite3OAuthStateStore
DB = os.environ.get("SIGNALPING_DB", "signalping.db")
CLIENT_ID = os.environ["SLACK_CLIENT_ID"]
installation_store = SQLite3InstallationStore(database=DB, client_id=CLIENT_ID)
state_store = SQLite3OAuthStateStore(database=DB, expiration_seconds=600)
installation_store.init()
state_store.init()
with sqlite3.connect(DB) as db:
db.execute("CREATE TABLE IF NOT EXISTS events "
"(team_id TEXT NOT NULL, name TEXT NOT NULL, ts INTEGER NOT NULL)")
app = App(
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
oauth_settings=OAuthSettings(
client_id=CLIENT_ID,
client_secret=os.environ["SLACK_CLIENT_SECRET"],
scopes=["commands"],
installation_store=installation_store,
state_store=state_store,
),
)
SIGNALS = { # mock feed; in production this is a warehouse query
"acme.com": {"score": 82, "reason": "Visited pricing 3 times this week"},
"globex.io": {"score": 41, "reason": "New hire: VP Sales"},
}
def lookup(domain):
return SIGNALS.get(domain.strip().lower())
@app.command("/signal")
def signal_command(ack, command, respond):
ack()
domain = command.get("text", "").strip()
with sqlite3.connect(DB) as db:
db.execute("INSERT INTO events VALUES (?, 'command_used', ?)",
(command["team_id"], int(time.time())))
hit = lookup(domain)
if not hit:
respond(f"No signal found for `{domain}`. Try `/signal acme.com`.")
return
respond(f"*{domain}* score *{hit['score']}*\n{hit['reason']}")
flask_app = Flask(__name__)
handler = SlackRequestHandler(app)
@flask_app.route("/slack/events", methods=["POST"])
@flask_app.route("/slack/install", methods=["GET"])
@flask_app.route("/slack/oauth_redirect", methods=["GET"])
def slack_routes():
return handler.handle(request)
if __name__ == "__main__":
flask_app.run(port=3000)oauth_pkce.py (the PKCE pair, URL builder and refresh path were tested with mocked HTTP; the pair reproduces the RFC 7636 example). Set HUBSPOT_CLIENT_ID, HUBSPOT_CLIENT_SECRET and HUBSPOT_REDIRECT_URI, and a TOKEN_KEY from Fernet.generate_key():
import base64
import hashlib
import os
import secrets
import sqlite3
import time
import urllib.parse
import requests
from cryptography.fernet import Fernet
HUBSPOT = { # HubSpot's OAuth quickstart does not document PKCE, so it is off
"authorize_url": "https://app.hubspot.com/oauth/authorize",
"token_url": "https://api.hubapi.com/oauth/v3/token",
"scopes": ["oauth", "crm.objects.contacts.read"],
"pkce": False,
}
def pkce_pair():
verifier = secrets.token_urlsafe(64) # 86 characters, inside 43 to 128
digest = hashlib.sha256(verifier.encode("ascii")).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
return verifier, challenge
def authorize_url(cfg, state, challenge=None):
params = {
"client_id": os.environ["HUBSPOT_CLIENT_ID"],
"redirect_uri": os.environ["HUBSPOT_REDIRECT_URI"],
"scope": " ".join(cfg["scopes"]),
"state": state,
"response_type": "code",
}
if cfg["pkce"] and challenge:
params.update(code_challenge=challenge, code_challenge_method="S256")
query = urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
return cfg["authorize_url"] + "?" + query
def token_request(cfg, **fields):
fields["client_id"] = os.environ["HUBSPOT_CLIENT_ID"]
fields["client_secret"] = os.environ["HUBSPOT_CLIENT_SECRET"]
fields["redirect_uri"] = os.environ["HUBSPOT_REDIRECT_URI"]
r = requests.post(cfg["token_url"], data=fields, timeout=15)
r.raise_for_status()
return r.json()
class TokenStore:
"""One encrypted row per tenant."""
def __init__(self, path, key):
self.db = sqlite3.connect(path, isolation_level=None, timeout=30)
self.fernet = Fernet(key)
self.db.execute(
"CREATE TABLE IF NOT EXISTS tokens "
"(tenant TEXT PRIMARY KEY, blob BLOB, expires_at REAL)"
)
def save(self, tenant, resp):
pair = resp["access_token"] + "\n" + resp["refresh_token"]
self.db.execute(
"INSERT OR REPLACE INTO tokens VALUES (?, ?, ?)",
(tenant, self.fernet.encrypt(pair.encode()),
time.time() + int(resp["expires_in"])),
)
def load(self, tenant):
row = self.db.execute(
"SELECT blob, expires_at FROM tokens WHERE tenant=?", (tenant,)
).fetchone()
if not row:
return None
access, refresh = self.fernet.decrypt(row[0]).decode().split("\n")
return {"access": access, "refresh": refresh, "expires_at": row[1]}
def get_access_token(cfg, store, tenant, skew=60):
"""Refresh at most once per expiry. BEGIN IMMEDIATE is the lock, so two
workers cannot spend the same one-time refresh token."""
store.db.execute("BEGIN IMMEDIATE")
try:
tok = store.load(tenant)
if tok is None:
raise LookupError("tenant has not connected")
if tok["expires_at"] - skew > time.time():
return tok["access"]
body = token_request(cfg, grant_type="refresh_token",
refresh_token=tok["refresh"])
body.setdefault("refresh_token", tok["refresh"])
store.save(tenant, body)
return body["access_token"]
finally:
store.db.execute("COMMIT")After the HubSpot callback, check that the returned state matches the one you sent. Then call token_request(HUBSPOT, grant_type="authorization_code", code=code), then store.save(str(response["hub_id"]), response). HubSpot's token response includes hub_id.
The funnel query counts a workspace as activated if it ran /signal within 7 days of first install. On three sample installs it returned 3 installs, 1 activated, 33.3 percent:
WITH installs AS (
SELECT team_id, MIN(installed_at) AS installed_at
FROM slack_installations
GROUP BY team_id
),
activated AS (
SELECT DISTINCT i.team_id
FROM installs i
JOIN events e
ON e.team_id = i.team_id
AND e.name = 'command_used'
AND e.ts >= CAST(strftime('%s', i.installed_at) AS INTEGER)
AND e.ts <= CAST(strftime('%s', i.installed_at, '+7 days') AS INTEGER)
)
SELECT
(SELECT COUNT(*) FROM installs) AS installs,
(SELECT COUNT(*) FROM activated) AS activated_7d,
ROUND(100.0 * (SELECT COUNT(*) FROM activated)
/ NULLIF((SELECT COUNT(*) FROM installs), 0), 1) AS activation_pct;No-budget path. Every piece is free. The Slack sandbox is free as a basic sandbox (archived after 1 month of inactivity). A standard sandbox lasts six months but needs a paid plan or a payment method on file. HubSpot developer test accounts are free with a 90 day trial of many enterprise features, and they expire after 90 days with no API calls. A Salesforce Developer Edition org is free. A private Zapier integration is free, but Zap templates need a public one. You cannot reach the Marketplace usage minimum of 10 active workspaces alone, so a real submission is out of scope. The listing copy is not.
What done looks like. A sandbox user installs the app, runs /signal, and sees an answer. The installation table holds a bot token that no log shows. The funnel query returns numbers. Your listing copy meets every row of the Slack table, and a stranger can say what the app does from your 10-word description.
Stretch goal. Add the HubSpot callback route with TokenStore, or build the Salesforce External Client App version of the flow with PKCE on.
Common mistakes
- Building before checking marketplace minimums. Slack wants 10 active workspaces and HubSpot wants 3 unaffiliated installs, so plan how to find early installs.
- Asking for broad scopes "just in case". Reviewers read the list, and Slack calls several scopes generally unsuitable.
- Skipping the
statecheck on the callback. An attacker could then attach their account to a victim's session. - Refreshing tokens from several workers at once. A one-time refresh token means the loser can break the connection.
- Treating approval as the finish. Slack can delist apps with broken pages or no active use, and HubSpot delists apps with no installs in 180 days.
- Quoting vendor ecosystem statistics as proof. "58% less likely to churn" and "11.7% win rate lift" come from sellers of ecosystem tools with unclear method.
- Counting installs as success. Count activated installs, then pipeline and retention.
Check yourself
1. Why does the OAuth callback compare the returned state to the value your app sent?
Why: The state value ties the callback to the browser session that started the flow, so an attacker cannot inject their own code.
2. What does PKCE add to the authorization code flow?
Why: The code_challenge is sent at the start and the code_verifier only at exchange, so a thief with just the code fails.
3. Two workers both refresh the same expiring token with the same one-time refresh token. What does Slack's documentation say about reusing it?
Why: Slack says refresh tokens are designed to be used once, so a per-tenant lock must wrap the refresh.
4. A founder says "integrations cut churn by 58%, so fund three integrations". What is the best response?
Why: The figure is correlation from a vendor survey, so compare retention of integration users and non-users in your own data first.
5. Your Slack app is active in 2 workspaces with 5 weekly active users and you want a Marketplace listing. What blocks you?
Why: The Slack guidelines name that usage floor, and sandboxes do not count as active workspaces.
6. You sell an embedded iPaaS wrapper and want a HubSpot Marketplace listing. Which HubSpot rule should worry you most?
Why: HubSpot's restrictions page excludes these app types, which describes many integration-layer products.
How to learn this faster
Learn OAuth first, because every marketplace here sits on it. Write one authorize URL and one token request by hand with curl before you use an SDK. Then read one marketplace's requirement page end to end. Skip Salesforce Apex and any iPaaS until you can explain what it hides from you.
Deliberate-practice drill. Pick a listing for a product you know. Rewrite its description in 10 words, guess the scopes it needs, and name the funnel step where it loses the most users. Then check against public signals such as review counts.
- Day 1: Read the verifier and challenge sections of RFC 7636 and reproduce the example pair in Python.
- Day 2: Provision the Slack sandbox, create the app from the manifest, and get
/slack/installworking. - Day 3: Add
/signal, the events table and the funnel query, and run them on test installs. - Day 4: Connect a HubSpot test account and force a refresh by editing
expires_at. - Day 5: Write the listing copy and a one-page co-sell plan, then check both against the requirement tables.
Resources
- Slack: Installing with OAuthDocsFree
The authorize URL, token call and response fields for Slack apps.
- Slack: Using token rotationDocsFree
12 hour tokens, one-time refresh tokens and the two-token limit.
- Slack Marketplace guidelines and requirementsDocsFree
The full checklist for listing, scopes, security and support.
- Slack developer sandboxesDocsFree
Free basic sandboxes for testing, with standard ones needing a paid plan or payment method.
- RFC 7636: Proof Key for Code ExchangeDocsFree
The primary source for verifier and challenge rules.
- RFC 9700: OAuth 2.0 Security Best Current PracticeDocsFree
Current guidance on PKCE, redirect URIs and refresh tokens.
- HubSpot: App Marketplace listing requirementsDocsFree
Install minimums, review timeline and listing fields.
- HubSpot: OAuth quickstartDocsFree
Authorize, token and refresh calls with real field names.
- Zapier: Public integration publishingDocsFree
Review timeline, the Beta period and what to build while you wait.
- AWS Marketplace listing feesDocsFree
The exact fee table for public, private and channel partner offers.
- Nango on GitHubRepoFree
A source-available integration layer you can read, under the Elastic License 2.0.
- Crossbeam: win rate lift with partnersResearchFree
Vendor data by ecosystem size, useful for judging the claim.
Next module
Integrations give you trackable installs, accounts and activity. Stage 10, Dark Social and Community Attribution, covers the opposite problem: buying influence that happens in private groups, DMs and communities, where no install event exists to count.
Built the project and passed the quiz? Mark the stage done. Your progress stays in this browser only.
Talk to Claude instead of typing to it.
Ducktate turns what you say into clean text, on your Mac, and pastes it anywhere.
Get early access