Skip to content

CRM Integration

HubSpot OAuth Integration — How Velocity X Bidirectional Syncs Contacts, Deals, and Companies in Real Time

Secrets, Webhooks, and Conflict Resolution

🔐 🔗 🔄

Most "HubSpot integrations" are read-only pantomimes. They pull your data once, display it, and call it a day. Velocity X does bidirectional OAuth: your reps edit deals on the map, the CRM updates in real-time. The CRM admin reassigns an account, the field map reflects it in 3 seconds. No polling, no reconciliation spreadsheets, no eventual consistency excuses.

This is how we built it.

OAuth Scoping and Initial Handshake

HubSpot's OAuth flow is straightforward but the scope string matters. Velocity X requests: crm.objects.contacts.read crm.objects.contacts.write crm.objects.deals.read crm.objects.deals.write crm.objects.companies.read crm.objects.companies.write plus crm.schemas.contacts.read (to understand custom fields). Optional: crm.objects.owners.read if the org uses team assignment.

On user approval, HubSpot redirects back with an auth code. We exchange it for an access token (expires in 1 hour) and a refresh token (expires in 30 days). The refresh token is the long-lived secret; we store it encrypted in Postgres using the bytea hex format.

Token Storage in Postgres Bytea Hex Format

Never store OAuth tokens as plaintext strings, even in a "secrets" column. Velocity X encrypts the refresh token with AES-256-GCM using a server-side key from `.env.local`, then stores the ciphertext in Postgres bytea:

INSERT INTO integrations (org_id, provider, encrypted_token)
VALUES (
  'org_123',
  'hubspot',
  '\x4a7b2244626167...'::bytea
)

The hex prefix \x tells Postgres this is raw binary. On read, we decrypt in-memory, never logging the plaintext. Access token gets refreshed silently before expiry so the user never sees a "reconnect your HubSpot account" prompt — the system handles it.

Webhook Subscriptions and Event Ingestion

Once connected, Velocity X creates subscriptions for contact.creation, contact.propertyChange, deal.creation, deal.propertyChange, and company.propertyChange. HubSpot delivers these as POST requests to a Netlify function (/.netlify/functions/hubspot-webhook).

Each webhook payload is written to a Supabase queue table with a processed = false flag. A background job (runs every 5 seconds) dequeues events, fetches the full object from HubSpot's REST API, and reconciles it into the local state. This two-stage approach isolates webhook latency from webhook reliability — if the API call fails, the event stays in the queue and retries.

Conflict Resolution with Last-Write-Wins

Both the rep and the CRM can edit the same field simultaneously. Velocity X uses last-write-wins: whichever system wrote the field most recently wins. Both systems sync to `now()` in Supabase (millisecond precision), so timestamps never collide unless a clock skew is >1 second — and we verify server time on app boot.

Example: at 14:30:01.234, the rep marks a deal as "Qualified" on their phone. The field app sends the update to Velocity X's backend, which writes it to the database with timestamp 14:30:01.234. At 14:30:01.567, the CRM admin edits the same field to "Unqualified" in HubSpot. The webhook arrives at 14:30:02.891. We compare timestamps: 14:30:01.567 is newer, so the admin's edit wins. Both systems converge. No user intervention, no UI warning.

Rate Limit Handling and Retry Logic

HubSpot rate-limits free/standard tier apps at 100 requests per 10 seconds. Velocity X tracks rate-limit headers in the webhook queue; if a fetch hits a 429 response, the event queues with retry_count = 0 and next_retry_at = now() + interval '30 seconds'. On the next job run, we retry. Exponential backoff caps at 10 minutes. After 5 failed retries, the org admin gets a Slack notification: "HubSpot sync is temporarily paused; last error was rate limit."

For high-volume teams, Velocity X recommends HubSpot's Professional plan (500 req/10s) or contacting HubSpot sales for rate-limit increases. The integration doesn't break on limits — it just delays by a few minutes.

Frequently Asked Questions

What custom properties does Velocity X sync?

All of them. On first connection, Velocity X fetches the schema of your contacts, deals, and companies. Any custom field (pipeline stage, custom territory ID, internal notes) syncs bidirectionally once you map it in the admin UI. The field UI shows only fields you've mapped, so you're not drowning in columns.

Can I run Velocity X with multiple HubSpot portals?

One portal per org connection right now. If your company has separate HubSpot instances for different markets, you'd need to create separate Velocity X orgs and switch between them. Multi-portal is on the roadmap.

What happens if the token expires or is revoked?

Velocity X tries to refresh the token on next sync. If refresh fails, the sync pauses and the admin gets a notification: "HubSpot connection lost; re-authorize." They click a button, go through OAuth again, and syncing resumes. Deals are never deleted from Velocity X — the local cache stays intact until reconnection.

How does Velocity X handle GDPR delete requests?

When a contact is deleted in HubSpot (via GDPR request), the webhook contact.deletion event fires. Velocity X marks the local contact as deleted and hides it from the map. The data is retained for 90 days, then purged. If you delete a contact in Velocity X, it syncs to HubSpot as a property update (status = deleted), not a hard delete.

Does the sync work offline?

No. Velocity X requires a live connection to push updates back to HubSpot. In-flight edits (while offline) are saved locally and synced once connectivity returns. The rep sees a small indicator: "Synced 45 seconds ago."

What's the typical end-to-end latency?

CRM change → webhook delivery = 1–3 seconds. Webhook processing = 1–2 seconds. Total = 2–5 seconds for the field map to reflect a HubSpot edit. Reverse (Velocity X → HubSpot) = sub-second if no rate limit applies.

The Bottom Line

HubSpot OAuth bidirectional sync is table stakes for field-sales tools. Velocity X bakes it in with zero manual configuration — org admin approves, we handle the rest. Token encryption, webhook queueing, conflict resolution, rate-limit retry logic, and transparent reconnection on token expiry all ship by default. If you're integrating with HubSpot, look for a partner that's built this depth, not just a read-only connector. Check the pricing or read our deeper bidirectional sync architecture post.

Let us make some quick suggestions?
Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.