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.

  1. Sign in to your HubSpot account (or create a free CRM)
  2. Go to Settings → Integrations → Private Apps (or follow HubSpot private apps)
  3. Create a private app
  4. Under Scopes, enable at least:
  • crm.objects.contacts.read
  • crm.objects.deals.read
  1. Create the app and copy the access token (pat-na1-… / pat-na2-…)
  2. In Intently: Settings → Attribution → CRM → HubSpot → paste token → Save & verify
  3. 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:

FieldExample
Instance URLhttps://yourorg.my.salesforce.com
Access tokenLong 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).

  1. In Salesforce: Setup (gear) → Quick Find My Domain
  2. Copy Current My Domain URL, e.g. orgfarm-….develop.my.salesforce.com
  3. 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)

  1. Setup → App Manager (or External Client App Manager)
  2. New External Client App (modern DE orgs) — or a classic Connected App if that is what your org offers
  3. Basic Information: name the app (e.g. Intently), contact email, Distribution Local
  4. Expand API (Enable OAuth Settings) and enable OAuth
  5. Callback URL: https://localhost/callback (required; not used by Client Credentials)
  6. 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
  1. Under Flow Enablement, check Enable Client Credentials Flow
  2. 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)
  1. 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

  1. Settings → Attribution → CRM → Salesforce
  2. Paste instance URL + access token → Save & verify
  3. 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

  1. Create a Contact with an email you control
  2. Create an Opportunity, stage Closed Won, set Amount, add that Contact as a Contact Role
  3. Sync again
  4. Fire an Intently site or Track API event with the same email so the deal can stitch

---

How identity stitches

  1. CRM contacts sync with normalized email + SHA-256 emailHash
  2. When track events include email / user_email, Intently links that session/userHash onto matching CRM contacts
  3. Closed-won deals (and optional allowlisted stages) resolve the contact’s linked identity, then credit first/last touch inside the attribution lookback window
  4. A conversion is written: crm_closed_won for won deals, or crm_pipeline_stage for 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:

ModeBehavior
Closed-won only (default)Same as historically — only won deals → crm_closed_won with deal amount
Specific pipeline stagesOpen 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 + syncedStages
  • PUT /api/v1/attribution/crm: save/clear token (instanceUrl required 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

Next