Connect HubSpot & Salesforce end to end
Orbit’s CRM integrations are a bidirectional loop, not a one-way import:- Inbound — a contact changes in HubSpot or Salesforce, Orbit’s contact book picks up the change (we poll + receive webhooks), and campaigns that reference the lifecycle stage fire in real time.
- Outbound — a contact changes in Orbit (lifecycle stage, phone, email, your own custom attributes), Orbit pushes the update back to HubSpot, and Orbit events can be delivered into HubSpot as timeline events.
/api-reference/integrations — this page is the “do this, then this” walkthrough for the same surface.
HubSpot is the primary example below because it exercises the full surface (OAuth connect, inbound webhook, outbound writeback, timeline events). Salesforce is covered in the Provider differences section — same shape, a few different property names.
What you’ll set up
- A Nango OAuth connection from Orbit to your HubSpot portal (or Salesforce org).
- Inbound sync — Orbit polls HubSpot/Salesforce for contact + company changes, and receives webhook events for real-time updates.
- Outbound writeback — when a contact changes in Orbit, a queued push updates the corresponding HubSpot contact.
- Event delivery — Orbit events can flow into HubSpot as timeline/webhook events if you’ve configured event subscriptions.
- Signature-verified webhook endpoints — inbound HubSpot events are verified with your app client secret; Salesforce uses an HMAC key you provision.
Step 1 — Connect via OAuth (admin only)
Initiate the connect flow from the Integrations API. Owner or admin role required — this is the only step that touches provider credentials, so Orbit restricts it to admins."salesforce" for Salesforce. The response returns a JSON envelope with an auth_url.
Redirect the browser to
auth_url. After OAuth consent, the portal is linked and the connection is live — check GET /api/v1/integrations for the provider’s row.integration_id. The connection is the same object either way.
Step 2 — Understand what syncs inbound
Once connected, Orbit runs two inbound paths: Poller — a scheduled sync (typically every few minutes) pulls contact, company, and deal changes via Nango. It reads the set of syncs registered under your integration template and upserts them into your Orbit contact book. Webhook receiver — real-time events from HubSpot/Salesforce hitPOST /api/v1/integrations/webhooks/{provider} and are written as soon as they arrive.
For HubSpot these are the Orbit contacts fields that get populated (mapped from the HubSpot property set): email, phone, firstname, lastname, lifecyclestage, plus the custom hubspot: namespace attributes if you’ve created them.
The subscription set Orbit installs on first connect covers contact creation, field changes on the core properties, and deal/ticket events. Operator-side re-registration via the HubSpot UI is rarely needed — the service handles the canonical set.
Contact field changes flow in real time.
Step 3 — Outbound: push Orbit changes back to the CRM
When a contact changes in Orbit, a queued job propagates the diff upstream:- The
contact.updatedevent in Orbit enqueues ahubspot-contact-pushjob (BullMQ). - The worker re-reads the contact row fresh at run time (so retries see the latest state), maps the fields into a HubSpot PATCH body, and pushes it via the Nango proxy.
- Idempotency: if the pushed payload hash matches the last-pushed stamp, the push short-circuits without a network call.
- Fail-open: any failure (HubSpot 4xx/5xx, Nango outage, missing connection) is logged + audited but never blocks the primary operation — the contact change in Orbit always completes. Three BullMQ retries with exponential backoff absorb transient blips.
attributes.hubspot_id on the Orbit contact (set during the inbound link). If that attribute is missing, the push is skipped as not_linked — it means Orbit has no upstream counterpart to update. Field-mapped properties are email, phone, firstname, lastname, lifecyclestage, and your hubspot:-prefixed custom attributes.
After updating a contact’s lifecycle stage in Orbit, watch your audit log for
crm.contact.pushed_to_hubspot. That’s the success marker.Step 4 — Wire the webhook correctly (signature verification)
Both providers push webhooks to an endpoint on your domain. Orbit verifies the sender before doing anything with the payload — the check fails closed when a secret is set. HubSpot — verification usesX-HubSpot-Signature-v3:
clientSecret from its environment; you supply the same value when you add the webhook URL to your HubSpot App in the developer dashboard. The endpoint is POST /api/v1/integrations/webhooks/hubspot and expects 2xx within 5s (HubSpot retries up to 10 times over 24 hours).
Salesforce — verification uses X-Devotel-Sf-Signature:
hmacSecret in your Salesforce org’s Apex trigger or Flow HTTP action. The endpoint is POST /api/v1/integrations/webhooks/salesforce.
Step 5 — Failure modes and monitoring
CRM integrations are fail-open by design — a HubSpot outage or a revoked OAuth token never blocks a contact write in Orbit, it only means the push to the CRM will land when the connection is restored. What to monitor:- Queue depth — if the BullMQ
hubspot-contact-pushqueue backs up, pushes are accumulating but not dispatching. Check Redis connectivity and the webhook-worker for stuck jobs. - Connection status — poll
GET /api/v1/integrations/{id}/status. Adisconnectedstatus or a stalelast_synctimestamp means the OAuth connection needs re-auth. - Audit log events —
crm.contact.push_failed_hubspotis the push-failure record;crm.contact.pushed_to_hubspotis the success record. Both surface in your org audit timeline. - Sentry — any transport error from the push path is captured with
service: hubspot-contact-push+ reason tag.
DEVOTEL_HUBSPOT_CLIENT_SECRET matches your portal’s client secret. A mismatched secret returns 401 from the endpoint — correct it and the next delivery will verify.
Step 6 — Troubleshooting common issues
Provider differences
For a mostly-automated path, put your effort into HubSpot. For Salesforce, budget one manual step (pasting the Apex trigger or configuring the Flow) after OAuth connect before outbound events flow.
Related reading
- Integrations API reference — endpoint map for the connect/sync/status/ disconnect surface.
- On-call alerting guide — the same queue-backpressure + Sentry pattern applies to the CRM sync queues.
- Contact external-sync contract — how Orbit decides whether an inbound event is a new contact or an update to an existing one.