Most partners already run a CRM (HubSpot, RD Station, Pipedrive, ActiveCampaign, Salesforce) as the source of truth for contacts. This guide shows how to keep the Cativa community in sync with that CRM — what’s available today, what’s coming soon, and the design decisions (natural key, conflicts, idempotency) you need to make.Documentation Index
Fetch the complete documentation index at: https://docs.cativa.digital/llms.txt
Use this file to discover all available pages before exploring further.
Scenario
Your marketing team uses HubSpot. Every time a lead is qualified and taggedPago Anual there, you want that person to show up in your Cativa community with the Premium badge (which unlocks the VIP group, the paid course, etc.). When the tag is removed (or the customer churns), the badge needs to come off.
Cativa is the source of truth for the community (who’s in the group, who completed which lesson, who posted what). The CRM is the source of truth for the commercial relationship (lead, opportunity, customer, churn). The integration bridges the two.
Prerequisites
- Cativa API Key — generated in the Console (Developers > API Keys). See Quick Start: API Key.
- API access to your CRM — HubSpot token, RD Station token, etc. Cativa does not ship a CRM SDK; use the official ones (
@hubspot/api-client,pipedrive-node-sdk, etc.). - An integration server running on your side — could be a Node worker, a Cloud Function/Lambda, an Airflow job, anything that runs code with access to both sides.
Conceptual mapping
The central question: how do you recognize that contact1234 in HubSpot is the same user 01HQ7Z3X4Y... on Cativa?
| HubSpot | Cativa |
|---|---|
| Contact ID | User ID |
| Email (natural key) | |
Tag Pago Anual | Badge Premium |
email — it exists on both sides from sign-up. You can optionally create a custom property in HubSpot called cativa_user_id to cache the Cativa User ID after the first match and avoid email lookups on every run.
Cativa does not currently expose an
external_id field on User that would let you bind directly to your CRM’s key. The natural key is email. When that field is added to the public model, this page will be updated with the upsert-by-external_id option.Two possible flows
- Pull-based (periodic worker)
- Webhook-based (event-driven)
When to use: you don’t have a CRM webhook, the volume is small (a few thousand contacts), or a delay of minutes/hours is acceptable.A worker on your side runs on a cadence (e.g. every 15 minutes), reads the CRM state, reads the Cativa state, computes the diff, applies it.Pros: simple to implement, easy to debug, easy to backfill.
Cons: delay (not real-time), wastes API calls when nothing changed.
Look up an existing user on Cativa
Today there’s one public endpoint you can call with your API Key:GET /social/v1/auth/me — returns the user owning the key (it does not look up arbitrary users). Use it to sanity-check your credential and discover the customer (tenant) the key belongs to.
Looking up an arbitrary user by email (which is the CRM-sync case) using a partner API Key is coming soon. Today this lookup uses an admin endpoint (
/social/v1/admin/users/email/{email}) that is not available to partner API Keys — only to a tenant admin signed in to the Console.Workaround today: coordinate with the Cativa team at dev@cativa.digital to enable the lookup for your scenario, or use the user_created webhook event (see below) to populate your own email → Cativa User ID mapping table as new users come in.Recommended strategy today: a mapping table populated via webhook
Instead of looking up by email on every sync, keep a table on your side:- Subscribe a Cativa listener to the
user_createdevent — every time someone joins the community, the webhook arrives withUser.IdandUser.Email. Insert/update that row. - Subscribe a listener to
user_received_badge— every badge change updates the row.
email → cativa_user_id locally, no Cativa API call needed.
Create a user from the CRM
The public endpoint for partners to create users via API Key is coming soon. Today, user creation happens when the user signs up themselves in the community (via Sign in with Cativa, an invite, or the tenant’s public sign-up link).Workarounds today:
- Email invite — generate the tenant’s sign-up link and send it from your CRM (HubSpot Email, RD Station Email). When the lead clicks and finishes registration, the
user_createdevent lands on your webhook and you can grant the badge right after. - Bulk import — for large initial backfills (thousands of contacts), the Cativa team can import a spreadsheet via Console. Coordinate at dev@cativa.digital.
Assign a badge
Badge assignment via partner API Key is coming soon. Today, assigning/removing badges happens:
- Manually in the tenant’s Console (admin), or
- Via bulk import (spreadsheet), or
- In production, via platform-internal automated flows (paywall purchase, course completion) — not via direct partner API.
Pull-based worker skeleton
Even with the write endpoints coming soon, model the worker today. What’s insideassignBadge/removeBadge is what’s on the wait list.
Handling conflicts
| Scenario | What to do |
|---|---|
| Email changed in the CRM, hasn’t changed on Cativa | Cativa is the source of cativa_user_id. If the HubSpot contact’s email changed and you can’t match by the new email anymore, keep the cached cativa_user_id (in crm_cativa_mapping) and update the email column when it’s different. The Cativa user still exists — only the CRM-side angle changed. |
| CRM email never showed up on Cativa | The HubSpot contact has no Cativa user yet. Send an invite or skip until they sign up. Don’t try to force-create them until the public endpoint is available. |
| Same email appears on two CRM contacts | Your call which contact is canonical (usually the most recent one, or the one with the most advanced lifecyclestage). Resolve before calling Cativa. |
| Customer cancelled in the CRM but still has the badge on Cativa | This is exactly what the removal sync fixes. The runSync flow above covers it. |
| Badge assigned by another source (paywall purchase) and the CRM doesn’t know | Cativa can grant badges via other paths (paywall purchase, course completion). If the CRM is not the source of truth for that specific badge, don’t remove the badge in the diff — add a filter on your side (e.g. only sync badges that came via a CRM tag). |
Common errors
The sync removed badges granted by other sources
The sync removed badges granted by other sources
When your worker is the sole authority on a badge and sees no reason for it in the CRM, it removes it. But if the badge was assigned by another source (paywall purchase, manual import), the sync wipes it out by mistake.Fix: keep a list of “CRM-managed badges” in your worker. Only assign and remove those. Badges outside the list are skipped by the diff.
Rate limit while processing many contacts
Rate limit while processing many contacts
On large syncs (thousands of contacts) you can hit rate limits on the CRM or on Cativa. Implement:
- Exponential backoff on 429 (Cativa respects
Retry-After). - Pagination on the CRM (e.g.
getPage(100, after, ...)on HubSpot). - Bounded parallelism (e.g.
p-limit(5)in Node).
user_created webhook never arrived — user missed the mapping table
user_created webhook never arrived — user missed the mapping table
If you only populate
crm_cativa_mapping via webhook, every lost delivery becomes a gap. That’s why we recommend:- A weekly reconcile job that pulls every CRM contact with an email and tries to match against known users.
- Logging the
X-Cativa-Execution-Idon every received webhook — if it’s missing later, you can open an investigation ticket.
Different email between CRM and Cativa for the same user
Different email between CRM and Cativa for the same user
Happens when the user buys with a personal email and joins the community with a corporate one (or vice-versa). No automatic fix — you need an extra property in the CRM (e.g.
community_email) and use that for the lookup instead of the primary email.Next steps
Subscribe to Cativa webhooks
Set up listeners for
user_created and user_received_badge to keep your mapping table fresh.Grant access via purchase
The specific case of “external purchase → badge” has its own patterns and gotchas.
