10 min de lectureIntégrations

HubSpot link click tracking: write clicks to deal timeline

Pipe Elido short-link clicks into the HubSpot contact and deal timeline via the conversion-forwarding API. Setup, UTM mapping, and refresh tokens.

Ana Kowalska
Marketing solutions engineering
HubSpot link click tracking diagram: Elido edge captures clicks, forwards them to the HubSpot Timeline Events API, and writes UTM values to contact properties

If your sales team lives in HubSpot but your campaign tracking lives in a short-link tool, you have two timelines that never talk to each other. The marketer sees clicks; the AE sees deal stage. Nobody sees the line between them. This guide walks through wiring Elido to HubSpot so every short-link click shows up on the contact timeline, UTM values land in CRM properties, and click-volume thresholds can nudge deal stages forward.

The plumbing rests on three HubSpot APIs: the Timeline Events API for the per-click records, the Contacts API for property writes, and the Deals API for stage advancement. Auth is OAuth 2.0 with the scopes documented under HubSpot OAuth scopes. HubSpot is Live on Elido (since 2026-04), and the connector handles refresh-token rotation, retries, and idempotent timeline writes. The rest is configuration.

TL;DR#

  • Connect via OAuth with three scopes: crm.objects.contacts.write, crm.objects.deals.read, timeline. Anything less and HubSpot will reject the install.
  • Elido posts each click as a Timeline Event with eventTemplateId provisioned at install time. UTM params land on the event payload and on three custom contact properties (elido_last_utm_source, _campaign, _medium).
  • HubSpot's analytics properties like original_source_drill_down_1 are first-touch only. Use custom properties for ongoing attribution, not the built-in ones.
  • Click-threshold rules (e.g. "50 clicks on the proposal link advances deal to Engaged") run server-side in api-core. Set them in Workspace Settings, not in HubSpot workflows.
  • 401 errors on the integration almost always mean a broken refresh-token chain. Reinstall from the marketplace tile, do not paste tokens by hand.

How clicks reach the HubSpot contact timeline#

A short-link click in Elido follows a five-step path before it surfaces in HubSpot.

  1. The redirect handler at the edge (services/edge-redirect) reads the click, decides the destination, and writes the click event to Redpanda. This is the hot path, p50 around 5ms; HubSpot is never on the request path.
  2. click-ingester reads the Redpanda topic and persists to ClickHouse for analytics.
  3. The HubSpot connector inside api-core (formerly services/hubspot-connector before the collapse) subscribes to a fan-out topic. For every click on a workspace with HubSpot connected, it constructs a Timeline Event payload.
  4. The connector resolves the contact: if the click carries an Elido contact_id (set via ?eid= parameter or by a logged-in dashboard share), that maps to a HubSpot contact directly. If only an fbclid or gclid is present, Elido tries email match on the latest form submission within 14 days; otherwise the event is held in a pending queue for 72 hours.
  5. The connector POSTs to /crm/v3/timeline/events with the event template ID provisioned at install. The timeline write is idempotent on eventId, so retries are safe.

The event payload includes tokens for the structured fields HubSpot displays (link slug, destination URL, campaign name, country, device) and extraData for everything else (full UTM set, referrer, user-agent fragments, raw timestamp). HubSpot's timeline UI renders the tokens; the extraData is available via the API but hidden from the default view.

Flow diagram: Elido edge redirect captures a click, publishes to Redpanda, click-ingester writes to ClickHouse, HubSpot connector POSTs a Timeline Event with UTM fields mapped to contact properties

The UTM-to-property map#

This is the part that bites teams who try to wire it themselves. HubSpot has two classes of "source" properties and they behave differently.

Analytics properties (first-touch only). original_source_drill_down_1, hs_analytics_first_url, hs_analytics_first_referrer, and the rest of the hs_analytics_* family are set once, when the contact is first created. Subsequent writes via the Contacts API are silently dropped. HubSpot does not return an error, the value just does not change. If you have ever wondered why your "last campaign" value seems frozen in 2024, this is why.

Custom properties (read/write). Anything you define yourself is freely writable. Elido provisions three on first connect: elido_last_utm_source, elido_last_utm_campaign, elido_last_utm_medium. Each click PATCHes these on the resolved contact. The deal-level rollup uses the most recent values via a HubSpot workflow that copies from the primary contact.

Figure 2 below summarises the mapping Elido applies by default. You can override any row in Workspace Settings, then Integrations, HubSpot, Field Mapping. For posts that need a deeper dive on UTM hygiene, the end-to-end UTM tutorial covers naming conventions, and the UTM templates guide explains how to enforce them at link-creation time.

A real example#

A B2B SaaS account books a webinar. The follow-up email contains an Elido short link to a pricing PDF with UTM utm_source=webinar&utm_campaign=q2-pricing&utm_medium=email. The recipient clicks twice over two days. In HubSpot:

  • Two new timeline events appear on the contact, both titled "Clicked: Q2 pricing PDF (s.elido.me/abc123)".
  • elido_last_utm_source = webinar, elido_last_utm_campaign = q2-pricing, elido_last_utm_medium = email.
  • The contact's existing original_source_drill_down_1 (set last September when they downloaded an ebook) does not change. This is correct first-touch behaviour, not a bug.
  • The associated deal's elido_recent_link_clicks property increments by 2 via a HubSpot workflow listening on the contact property.

The AE looking at the deal now sees a click counter rising before they call. The marketer running the webinar can pull a HubSpot list filter on elido_last_utm_campaign = q2-pricing and ship it to a re-engagement sequence. Same data, two views.

Wiring click thresholds to deal stages#

Timeline visibility is the table stakes. Threshold rules are where the integration earns its keep, because they convert click signal into a CRM action without anyone watching a dashboard.

The shape of a rule:

trigger:
  link_tag: "sales-collateral"   # all links tagged this way count
  contact_window: 30d            # rolling
  click_threshold: 50
action:
  type: advance_deal_stage
  pipeline: "default"
  from_stage: "appointmentscheduled"
  to_stage: "qualifiedtobuy"
  guard:
    require_associated_contact: true
    deal_amount_min: 5000        # only deals worth advancing

Rules live in api-core and run on the same fan-out topic that powers timeline writes. Every click recomputes the rolling count per (contact_id, link_tag). When the count crosses the threshold and the contact is associated with a deal in from_stage, the connector PATCHes /crm/v3/objects/deals/{dealId} with properties.dealstage = qualifiedtobuy.

A few practical notes.

Use it for high-intent assets. Pricing pages, proposal PDFs, demo-recording replays. Threshold-driven advancement on a cold-outreach link tag will pollute your pipeline within a week. The fastest way to lose AE trust is to advance a deal because someone scraped a link with curl.

The guard block matters. Without require_associated_contact, anonymous clicks (someone forwarding the link to a friend) can trigger the rule. Without deal_amount_min, you will advance $400 trial deals into stages reserved for enterprise pursuits.

Reverse rules are not symmetric. Elido does not auto-demote stages on inactivity, because HubSpot reporting treats stage reversals as suspicious. If you want stale-deal handling, build it as a HubSpot workflow on hs_lastmodifieddate, not as an Elido rule.

For the conversion-forwarding mechanics under the hood, the conversion-forwarding guide documents the event schema, retry policy, and dead-letter queue. The conversion tracking feature page shows the same flow for Meta CAPI, GA4, and Mixpanel; HubSpot is one destination among several.

You have two ways to scope a threshold rule. Tag-based covers a set of links sharing a tag (e.g. all 12 links in your Q2 nurture sequence count toward the same threshold). Link-based scopes to a single short link.

UTM to HubSpot property mapping table: utm_source to original_source_drill_down_1 (first-touch only, read-only after creation) and to elido_last_utm_source (writable), utm_campaign to hs_analytics_first_url (first-touch) and to elido_last_utm_campaign, utm_medium to original_source_drill_down_2 and elido_last_utm_medium

Use tag-based when the prospect's journey crosses multiple touchpoints (this is most of B2B). Use link-based when the asset itself is the signal - a single proposal link where clicks 3+ mean the deal is real. Both rule types coexist; an account engineer recently set up a workspace with 8 tag-based and 14 link-based rules running in parallel without conflict.

Refresh-token rotation and the 401 you are about to see#

HubSpot OAuth uses rotating refresh tokens. Every call to /oauth/v1/token with grant_type=refresh_token returns a new refresh token and invalidates the previous one. This is good for security and terrible for anyone who tries to manage tokens by hand.

Elido's connector handles rotation correctly. The flow:

  1. Access token expires every 30 minutes (HubSpot's default; the expires_in value in the token response confirms it).
  2. About 90 seconds before expiry, the connector calls the refresh endpoint with the current refresh token.
  3. HubSpot returns a new access_token + new refresh_token + new expires_in.
  4. Elido stores both atomically in the token table. The old refresh token is now dead.

The places this breaks:

Database restores. If you restore a backup older than your last refresh, the stored refresh token is already invalidated upstream. First refresh call returns a 401 with BAD_REFRESH_TOKEN. Symptoms: every HubSpot API call from Elido fails until you reinstall.

Token copies between environments. A developer copies a workspace's HubSpot tokens from staging to local. Both environments now try to refresh against the same token. Whichever runs first wins; the other dies on the next attempt.

Manual edits to the token row. Tempting when debugging, never a good idea. The token_version column is incremented atomically with the refresh; manual edits break the optimistic-concurrency check and the next refresh fails.

Long downtimes. HubSpot does not document a hard expiry on refresh tokens, but in practice tokens unused for 6+ months sometimes return 401. If you have a workspace that has been dormant since last summer, expect to reinstall.

The fix in all four cases is the same: open the HubSpot marketplace tile from Workspace Settings, click Reinstall, accept the scopes. HubSpot issues a fresh authorization code, Elido exchanges it for a new token pair, and the integration resumes. No data is lost; timeline events queued during the outage flush within a minute. The HubSpot OAuth docs describe the auth code flow in more detail, and the rotation rules are linked from the refresh-token section.

What about token-paste integrations?#

Some vendors let you paste a Private App access token instead of doing OAuth. HubSpot supports this, and it sidesteps the rotation problem entirely - Private App tokens do not expire and do not rotate. Elido does not use this path for HubSpot because Private Apps are tied to a single HubSpot account and cannot install across multiple portals from a single Elido workspace. If you only have one HubSpot portal and you want to skip the marketplace install, get in touch via /contact; the connector supports both modes, it is just not exposed in the default UI.

Monitoring the refresh chain#

Two signals tell you whether refresh is healthy.

The hubspot_refresh_attempts_total{result="ok|error"} Prometheus counter lives in api-core. A sustained error rate above 1% across a workspace is the early warning. Most workspaces show zero errors over weeks. The observability guide walks through wiring this to alerts.

The Integrations page in Workspace Settings shows the last successful refresh timestamp per integration. If HubSpot says "Last refreshed: 6 days ago" while everything else shows minutes, that is the workspace to look at first.

Putting it together#

A reasonable rollout sequence for a team adopting the integration:

  1. Install from /integrations, accept the three scopes. Allow 60 seconds for HubSpot to provision the timeline event template.
  2. Confirm the first click. Send yourself an Elido short link with ?eid=<your_hubspot_contact_id>, click it from a different device, refresh your HubSpot contact page. The timeline event should appear within 30 seconds.
  3. Add the three Elido custom properties to your contact view. Workspace Settings, then Contacts, then Customize Sidebar. This is where marketing and sales finally see the same UTM values.
  4. Wait two weeks before configuring threshold rules. You need real click data to know what "high intent" looks like for your asset mix; arbitrary thresholds set on install day are usually wrong. The marketers solution page and the link analytics primer help frame what to measure.
  5. Set up your first rule on a single high-intent asset (pricing page, proposal link). Watch it for a week. Tune the threshold and the deal-amount guard. Repeat.

The full feature set is documented in the integrations catalog and the connector source lives under the hubspot package in services/api-core. If you are evaluating the broader platform, Elido pricing covers the tier where HubSpot integration is included (Pro and above), and the server-side conversion tracking overview compares HubSpot to the other CRM and analytics destinations Elido forwards to.

One closing rule of thumb. Treat the timeline events as the source of truth for engagement; treat the custom properties as the source of truth for the latest campaign; never trust the hs_analytics_* family for anything beyond first-touch. That triplet covers 95% of what marketing and sales argue about, and HubSpot's data model finally starts to feel honest.

Essayer Elido

Collez une URL, obtenez un lien court

Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.

Gratuit, sans inscription · 2 par jour

Essayer Elido

Raccourcisseur d'URL hébergé en UE : domaines personnalisés, analyses approfondies et API ouverte. Forfait gratuit - sans carte bancaire.

Tags
hubspot link click tracking
hubspot url shortener
hubspot utm tracking
hubspot deal timeline links
link clicks crm property

Lire la suite