Documentation
CRM pipeline
HubSpot contacts and deals: closed-won attributed to ad journeys.
Connect HubSpot or Salesforce so Intently can attribute closed-won pipeline (and optionally selected open stages) to the same first-party journeys as on-site and Stripe conversions.
Open connections under Attribution settings → CRM. Full setup guide below.
Developer portals:
---
HubSpot — get credentials
Intently needs a private app access token with read access to contacts and deals.
- Sign in to your HubSpot account (or create a free CRM)
- Go to Settings → Integrations → Private Apps (or follow HubSpot private apps)
- Create a private app
- Under Scopes, enable at least:
crm.objects.contacts.readcrm.objects.deals.read
- Create the app and copy the access token (
pat-na1-…/pat-na2-…) - In Intently: Settings → Attribution → CRM → HubSpot → paste token → Save & verify
- Click Sync contacts & deals
Treat the token like a password. If it leaks, rotate it in HubSpot and paste the new one.
---
Salesforce — get credentials
Intently needs:
| Field | Example |
|---|---|
| Instance URL | https://yourorg.my.salesforce.com |
| Access token | Long Bearer token from OAuth (not the Consumer Key/Secret) |
Consumer Key and Consumer Secret stay in Salesforce / your terminal — only the access token + instance URL go into Intently. There is no refresh in Intently yet; when the token expires, mint a new one and paste it again.
1. Org and instance URL
Use a production org or a free Developer Edition (no credit card; activate from email).
- In Salesforce: Setup (gear) → Quick Find My Domain
- Copy Current My Domain URL, e.g.
orgfarm-….develop.my.salesforce.com - Instance URL for Intently:
https://+ that host (no Lightning path)
You do not need to edit My Domain for API access.
2. External Client App (OAuth)
- Setup → App Manager (or External Client App Manager)
- New External Client App (modern DE orgs) — or a classic Connected App if that is what your org offers
- Basic Information: name the app (e.g.
Intently), contact email, Distribution Local - Expand API (Enable OAuth Settings) and enable OAuth
- Callback URL:
https://localhost/callback(required; not used by Client Credentials) - Selected OAuth scopes:
- Manage user data via APIs (
api) - Access unique user identifiers (
openid) - Access the identity URL service (
id,profile,email, …) - Perform requests at any time (
refresh_token,offline_access) — recommended
- Under Flow Enablement, check Enable Client Credentials Flow
- Create, then open Policies (Edit):
- Ensure Enable Client Credentials Flow is on
- Set Run As to your admin user
- Set IP Relaxation to Relax IP restrictions (Enforce often blocks tokens from your laptop / Intently)
- Open Settings → reveal Consumer Key and Consumer Secret
3. Mint an access token
Trade Key + Secret for a token (run locally; do not paste secrets into chat):
curl -s -X POST \
"https://YOUR_INSTANCE.my.salesforce.com/services/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CONSUMER_KEY" \
-d "client_secret=YOUR_CONSUMER_SECRET"Success includes access_token and instance_url. Copy access_token into Intently.
Quick alternative: Workbench → log into the org → copy Session Id as the access token (expires with the session).
4. Connect and sync in Intently
- Settings → Attribution → CRM → Salesforce
- Paste instance URL + access token → Save & verify
- Sync contacts & opportunities (closed-won Opportunities in the last ~year)
Single-currency orgs
Developer Edition and many orgs are single-currency and have no CurrencyIsoCode on Opportunity. Intently retries without that field and defaults amounts to USD.
Seed test data
- Create a Contact with an email you control
- Create an Opportunity, stage Closed Won, set Amount, add that Contact as a Contact Role
- Sync again
- Fire an Intently site or Track API event with the same email so the deal can stitch
---
How identity stitches
- CRM contacts sync with normalized email + SHA-256
emailHash - When track events include
email/user_email, Intently links that session/userHashonto matching CRM contacts - Closed-won deals (and optional allowlisted stages) resolve the contact’s linked identity, then credit first/last touch inside the attribution lookback window
- A conversion is written:
crm_closed_wonfor won deals, orcrm_pipeline_stagefor allowlisted open stages (idempotent per deal + stage)
Order: sync CRM first, then send events with that contact’s email. Linking only updates existing CRM contact rows.
Conversion stages (customer setting)
Under Attribution settings → CRM, choose how deals become conversions:
| Mode | Behavior |
|---|---|
| Closed-won only (default) | Same as historically — only won deals → crm_closed_won with deal amount |
| Specific pipeline stages | Open deals in the stages you list → crm_pipeline_stage; optionally still attribute closed-won |
Open-stage revenue defaults to $0 so ROAS stays closed-won / Stripe. You can switch to deal amount (advanced — can inflate attributed revenue until the deal wins).
Stage names match HubSpot dealstage ids and/or Salesforce StageName (and labels), case-insensitive. After a sync, the settings UI suggests stages seen on your deals. Re-sync after changing the setting.
Report
On Attribution, the CRM pipeline panel shows:
- Open / won / attributed-won counts
- Closed-won amount by ad channel
- Recent won deals with first/last touch
APIs
GET /api/v1/attribution/crm: connection status +conversionStages+syncedStagesPUT /api/v1/attribution/crm: save/clear token (instanceUrlrequired for Salesforce), or{ "conversionStages": { "mode", "stages", "includeWon", "stageRevenue" } }POST /api/v1/attribution/crm:{ "action": "sync", "provider": "hubspot"|"salesforce" }or{ "action": "link", "email": "…" }GET /api/v1/attribution/crm/pipeline?range=30d