Crossdeck Docs
Dashboard

Connect HubSpot — every sales touch on the same timeline as the app journey

Setup HubSpot OAuth 2.0 · read-only CRM · ~6 min read · Updated July 27, 2026

Connect your HubSpot and every sales touch — deals and their stage changes, contacts as they appear — lands on the same customer timeline as the journey Crossdeck already captures in your app, joined to the person by their contact email. In reverse, a Prism by Crossdeck card renders inside HubSpot on every contact and deal, showing the revenue, product usage and errors HubSpot can't see. And if you send email — from your own code, through HubSpot's transactional API or any tool — one helper joins each click to the person. Crossdeck only ever reads your HubSpot. Nothing is written back.

TL;DR

What you get

HubSpot is where your sales and marketing team's version of the customer lives — the deals, the pipeline stages, the leads. Crossdeck already holds the product version: the anonymous first visit, the sign-up, the feature usage, the subscription, the errors they hit. On their own, each is half the picture. Connecting HubSpot merges the two halves onto one timeline, keyed to one person.

Concretely, once connected:

Read-only, by design.

Crossdeck reads your HubSpot; it does not write to it. No deals are created, no properties overwritten, no contacts mutated. The connection holds only the two read scopes above — it has no permission to change anything in your portal.

How the loop works

The read side is automatic once you connect. Crossdeck polls your HubSpot on an hourly schedule (the source of truth), reading the deals and contacts already in your portal and joining each to the person's journey by contact email. It also pulls a person's HubSpot record onto their timeline on the spot the moment they click a tagged campaign link, so the sales context is there without waiting for the next poll.

In reverse, the Prism card is served to HubSpot's UI extension on demand — HubSpot signs the request, Crossdeck verifies it, and returns the joined portfolio for the contact or deal being viewed (below).

Connect HubSpot  (OAuth, read-only)
  → Crossdeck reads your deals + contacts (hourly, and on a campaign click)
  → each joins the person's app journey by contact email
  → the Prism card renders inside HubSpot with what HubSpot can't see
  → (optional) stamp your email links so a click joins to the person

Step 1 — Connect HubSpot

In the Crossdeck dashboard, open Developers → Integrations → HubSpot and click Connect. Crossdeck redirects you to HubSpot's OAuth 2.0 authorize screen, where you approve the connection in HubSpot's own UI under whichever portal you're signed into. There is no API key to generate and paste.

Crossdeck requests only two read scopes — crm.objects.contacts.read and crm.objects.deals.read. Disconnect anytime from the same page; Crossdeck stops polling and discards the tokens.

What flows into the journey

Once connected, these events land on the person's timeline, each joined to them by contact email:

Because they share the timeline with everything else Crossdeck holds, you can finally see the whole arc — the content someone read before they became a lead, the product usage that ran alongside the sales cycle, and which journeys produce deals that actually close.

Identity model — joined by email

HubSpot records join the person by their contact email. A deal carries no email of its own, so Crossdeck resolves the deal's primary associated contact and matches on that address. If a contact's email matches a Crossdeck customer, the two are the same person; if no one carries that address yet, the record is held and stitches to the person on a later identify.

This is the integration lane, not the bank-grade resolver.

Crossdeck's core identity graph never treats an email as a proof of identity — that is a deliberate security stance (see How identity works). Integrations are different: an email match between a HubSpot contact and a known customer is good enough to join a sales touch to a person. The two systems are separate by design, so an integration match never weakens the graph. To make the match airtight rather than email-soft, use the campaign-link stamp (below), which binds a specific click to a specific person deterministically.

The Prism by Crossdeck card

The integration ships a Prism by Crossdeck UI-extension card that renders inside HubSpot on contacts and deals. When a rep opens a record, HubSpot signs the request (X-HubSpot-Signature-v3) and calls Crossdeck; Crossdeck verifies the signature — so the request provably came from your portal — and returns the joined portfolio for that person: revenue, plan, last active, recent errors and their recent journey. Your reps read the product-side truth without leaving the CRM, and nothing about it is written back to HubSpot.

When you send email — from your own backend, through HubSpot's transactional API, or any tool — you know the recipient's address. Stamp your email links with campaignLink() and the click binds to that person on landing, so an email touch joins their journey and you can follow what they did next in your app. This is the same one helper across every email tool (Resend, HubSpot, your own SMTP) — it is one line, written into your email-sending code.

// your backend, wherever you build a link that goes into an email
import { CrossdeckServer } from "@cross-deck/node"

const crossdeck = new CrossdeckServer({ secretKey: process.env.CROSSDECK_SECRET_KEY })

// Stamp the link with the recipient's identity ticket (server-side), then send
// the returned URL however you already send email.
const { url } = await crossdeck.campaignLink({
  email: contact.email,                     // resolved to the person, once, here
  url: "https://app.yoursite.com/welcome",   // where they should land
})
// → put `url` in the email body
Use a secret key, server-side only.

campaignLink() mints an identity bind for the address you name, so it must run on your server with a secret key (cd_sk_ or a workspace key). A publishable key is refused — a browser must never mint a bind for an arbitrary address. The email travels in the request body, never in a URL. Prefer raw HTTP? It's POST /v1/campaign/email-link with { email, url }; the response is { url }. The returned URL carries only an opaque cd_ref ticket (no PII), single-use, valid 30 days.

It degrades honestly: with the Crossdeck Web SDK on your landing page the click deterministically binds the session to the person and the full post-click journey joins their identity; without it, the touch still lands by identity — only the deep session stitch softens. Never implied, always stated.

Read-only & security

Troubleshooting

Connected, but sales touches aren't appearing on the timeline

The connector polls hourly, so allow a little time after connecting for the first pull (or click a tagged campaign link to pull a person's record on the spot). If touches still don't appear, the contacts aren't resolving to a customer — check that the HubSpot contact's email matches an email Crossdeck knows for that person. A contact whose email Crossdeck has never seen is held until a later identify connects it.

Deals show up under the wrong person, or as new people

Almost always the emails differ. HubSpot records match on contact email, so a contact whose HubSpot email is a work address while Crossdeck knows their personal one resolves softly or not at all. Stamp your email links (above) to make the join deterministic per click, and make sure your app identifies people by the same email HubSpot holds.

The Prism card is blank inside HubSpot

The card renders for a contact or deal that resolves to a Crossdeck customer. If the record's email isn't one Crossdeck knows, there's no person to show yet — the card states that rather than inventing data.

What's next


Read-only HubSpot connector — OAuth 2.0 connect (contacts + deals read scopes), hourly poll + on-click enrichment, lead_created / deal_stage_changed onto the person's timeline joined by contact email, the signature-verified Prism card, and the optional campaignLink() email-click stamp (July 27, 2026). Related: Connect Resend, How identity works.

Using an AI assistant? Read this page as clean markdown — index.md — or the whole docs index at /docs/llms.txt.