Bojan Josifoski < wp developer />

Integrating CRMs into SampleHQ: Making WordPress Multisite Talk to HubSpot and Salesforce

October 31, 2025 • Bojan

When I started building SampleHQ, I knew the platform had to do more than manage sample requests.
For packaging and label companies, samples are part of the sales cycle – not separate from it.
That meant every request needed to sync with their CRM, so managers could finally see which samples lead to real deals.

But integrating CRMs like HubSpot and Salesforce into a multi-tenant WordPress environment is not as simple as connecting one API.
Every company inside SampleHQ has its own workspace – its own users, data, and permissions – and each one needs to connect to its own CRM account without touching anyone else’s.

Here’s how I made it work.


1. The OAuth Challenge

Both HubSpot and Salesforce require OAuth 2.0 authentication, which is easy enough in a single-site app – but tricky when you’re dealing with hundreds of tenants on a single WordPress installation.

The core problem:
Each tenant must authenticate using their own CRM credentials, store their tokens securely, and refresh them independently – without cross-site data leaks.

So I built a centralized OAuth handler on the main site and a token routing system that associates each token with its tenant.

Example Flow:

  1. A manager clicks “Connect to HubSpot” from their SampleHQ dashboard.
  2. The button points to a main-site route like:
    https://app.samplehq.io/oauth/hubspot?site_id=42
    
  3. The main site handles the HubSpot OAuth handshake (client ID, secret, redirect URI).
  4. After authentication, HubSpot redirects back with the access and refresh tokens.
  5. The system stores those tokens in an encrypted field inside that tenant’s site meta table (wp_42_sitemeta) using the WordPress encryption salts.

That’s it – each tenant owns their tokens, and the main site never mixes them up.


2. Storing Tokens Securely

OAuth tokens are sensitive. WordPress doesn’t have native encryption, so I used the built-in AUTH_KEY and SECURE_AUTH_KEY constants to generate per-tenant encryption keys.

Each token is encrypted with openssl_encrypt() before being stored and decrypted only when needed.

Example snippet:

function sf_encrypt($data) {
    $key = hash('sha256', AUTH_KEY);
    $iv = substr(hash('sha256', SECURE_AUTH_KEY), 0, 16);
    return base64_encode(openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv));
}

This means even if a database export leaks, the CRM credentials are unreadable.


3. Handling Token Refresh

HubSpot tokens expire every 6 hours. Salesforce tokens can vary by configuration.
To handle this without user friction, I built a token refresh cron that runs per-tenant and checks expiration timestamps stored in the DB.

If a token is near expiry, it triggers a background refresh via the CRM’s refresh endpoint – again scoped per-tenant.
This allows continuous syncing without any downtime or manual re-authentication.


4. Syncing Sample Orders with CRMs

Once connected, SampleHQ automatically pushes order data into the CRM as deals or notes.

For HubSpot:
Each approved sample request becomes a Deal in a designated pipeline.
Contacts are matched by email – if they don’t exist, they’re created automatically.
Line items (samples) are attached as custom properties with quantity and category.

For Salesforce:
Each request maps to an Opportunity.
Custom fields (e.g., “Sample Category”, “Quantity”, “Status”) are created automatically during onboarding.
Fulfillment status updates trigger Opportunity Stage updates (“Requested” → “Shipped” → “Delivered”).

All of this happens through the CRM REST APIs, using per-tenant tokens fetched dynamically.

Example logic (simplified):

$token = sf_get_tenant_token($site_id, 'hubspot');
$response = wp_remote_post('https://api.hubapi.com/crm/v3/objects/deals', [
    'headers' => [
        'Authorization' => 'Bearer ' . $token,
        'Content-Type'  => 'application/json',
    ],
    'body' => json_encode($deal_payload),
]);

If a token fails, the system retries once, then flags the connection for refresh.


5. Multi-Tenant Isolation

WordPress Multisite is key here. Each site in the network has its own tables (wp_{blog_id}_posts, wp_{blog_id}_options, etc.), which naturally isolates tenant data.
I extended this isolation to the CRM layer by tagging every request and cron job with its blog_id – ensuring that a HubSpot refresh job from one tenant never touches another.


6. The User Experience

From the user’s point of view, it’s simple:
They click “Connect CRM”, log in once, and SampleHQ handles the rest.
There’s no need to paste tokens or API keys. Everything is OAuth-driven, secure, and silent.

Behind that simplicity is a network of hooks, cron jobs, encryption, and webhooks working quietly – exactly the kind of engineering I like.


7. The Result

Now, when a sales rep submits a sample request, it instantly appears in the company’s CRM pipeline.
Managers can see the full picture – which samples convert into sales, which clients order the most, and where bottlenecks happen.

No manual updates, no missing data, no context lost between systems.


Reflection

Building CRM integrations was one of the hardest parts of SampleHQ – not because of the APIs, but because of the mindset it required.
Every decision had to respect tenant isolation, data privacy, and reliability.

The end result is invisible, which is how it should be.
It just works – securely, automatically, and consistently.

Good integrations don’t brag. They disappear into the workflow.
That’s what I wanted SampleHQ to do.

Bojan Josifoski

Bojan Josifoski

Engineer behind BBPro — a WordPress-based platform powering 100+ financial institutions. I write about performance, clarity, and building digital infrastructure that lasts.

← Back to Blog