---
title: "Recipe: Channel Partners, Matched and Plated at the Table"
description: Efficiently match channel partners to deals using HubSpot and Google Cloud with this detailed setup guide for automated partner assignments.
image: https://tomdelaney.us/hubfs/Gemini_Generated_Image_vpvzanvpvzanvpvz.png
---

[![White Modern Minimalist Signature Brand Logo (Website) (4)](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/White%20Modern%20Minimalist%20Signature%20Brand%20Logo%20(Website)%20(4).png)](https://tomdelaney.us)

- [Resume](https://tomdelaney.us/resume)
- [Blog](https://tomdelaney.us/blog)
- [Portfolio](https://tomdelaney.us/portfolio)

[Contact Me](https://tomdelaney.us/contact-me)

Search **the website**

# Recipe: **Channel Partners, Matched and Plated at the Table**

![](//24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/raw_assets/public/@marketplace/if_else_agency/Aesthetics/assets/images/icons/arrow-down.svg)

![Partner Router](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_vpvzanvpvzanvpvz.png)

## Share this **post**

# Recipe: Channel Partners, Matched and Plated at the Table

**Yield:** Partner assignments that live in HubSpot, not in someone's head

**Prep time:** A few hours to configure, plus HubSpot UI Extensions setup

**Active time:** One click per deal

**Difficulty:** Intermediate — two moving parts (Cloud Function + HubSpot UI card) that need to trust each other

[view the project brief](https://tomdelaney.us/partner-router-portfolio)

---

## Ingredients

**Google Cloud**

- Cloud Functions (gen2), Node.js 20
- GCP Secret Manager (three secrets: HubSpot access token, client secret, OpenRouter key)

**HubSpot**

- Operations Hub (required for UI Extensions)
- Custom association labels — one for partner company, one for partner contact
- Company properties identifying partner status, tier, type, and routing mode
- Contact properties identifying routing eligibility

**Configuration**

- `config.example.js` — all instance-specific values in one place (see below)

---

## Method

### 1. Define your partner data model in HubSpot

Before writing a line of code, the data model needs to be right. The router depends on company and contact properties that identify:

- **Which companies are partners** — a property (or multi-select) that marks a company as a partner type
- **Whether the partnership is active** — a status property distinguishing current from former partners
- **What kind of partner they are** — type property (Reseller, MSP, Distribution, Alliance, etc.)
- **Their tier** — if you use tiering (Elite, Gold, Platinum, etc.)
- **Their routing mode** — Single (one designated rep) or Multi (any active partner contact)

On the contact side, you need a way to identify the designated routing rep for Single-mode partners, and a status/lifecycle combination that identifies active contacts for Multi-mode partners.

None of these property names or values are hardcoded to a specific convention — they're all configured in `config.example.js`.

### 2. Create custom association labels

The router writes two associations on deal assignment:

- Deal → Partner Company (a custom label, not the default company association)
- Deal → Partner Contact (a custom label for the channel rep)

Create these in HubSpot under **Settings → Objects → \[Object\] → Associations**. Each label gets a `typeId` — note these down, they go into `config.example.js`.

Using custom labels keeps partner associations distinct from the end-customer associations already on the deal, which matters for filtering and reporting downstream.

### 3. Configure `config.example.js`

Copy `config.example.js` to `config.js` and fill in every value:

- Cloud Function URL (your GCP project ID)
- Association type IDs for both labels
- Property names and filter values for partner eligibility
- Contact routing property names and values
- Scoring weights (adjust to reflect what matters most in your program)
- Country list and partner type options for the UI dropdowns

Every value in this file maps to a specific location in `cloud/index.js` or `partner-recommender.jsx`. Comments in the file point to each location.

### 4. Deploy the Cloud Function

The function handles four actions from the UI card:

```
recommend       → scores all eligible partners and returns a ranked list assign          → creates the custom association labels on the deal findContacts    → returns eligible reps for a selected partner company getPropertyOptions → fetches partner type options dynamically for the UI 
```

All requests are authenticated via HubSpot v3 signature validation (HMAC-SHA256 on method + URL + body + timestamp). The client secret is stored in GCP Secret Manager and never exposed.

Deploy from `cloud/` using `cloudbuild.yaml` or the `gcloud` CLI command in the README. Set the three secrets in Secret Manager before deploying.

### 5. Deploy the HubSpot UI card

The card is a standard HubSpot UI Extensions project. Before deploying:

1. Replace `YOUR_GCP_PROJECT_ID` in `app-hsmeta.json` (permittedUrls) and in all `hubspot.fetch()` calls in `partner-recommender.jsx`
2. Update `app-hsmeta.json` with your support email
3. Deploy: `hs project upload` from the `hsproject/` directory
4. Add the card to deal record views in HubSpot: **Settings → Objects → Deals → Record customization**

### 6. Verify end-to-end

Open a deal that has an associated end-customer company. Open the Partner Router card. Click Find Partners — you should see a scored list of eligible partners. Select one, check that contacts load correctly, and assign. Verify the association labels appear on the deal record with the correct label names.

---

## Plating Notes

**The scoring engine is transparent by design.** Each partner row in the results table shows the reasons it scored the way it did — reps can see "Same country: Germany" and "Matches preferred type: Reseller" rather than just a number. This matters for adoption: people trust a recommendation they can read, not one that comes out of a black box.

**Single vs Multi routing mode** reflects how different partners actually want to work. Some have one person who owns all inbound deals; others have a whole team. The router handles both cases with a single property flag on the partner company record.

**Country normalization happens at scoring time, not at filter time.** Filtering by country would miss partners whose country is stored as "USA" when the customer is "United States." By normalizing both values before comparing, the router catches these matches without losing any candidates.

**The LLM re-ranking placeholder** (`llmTop: []` in the API response) is intentional. The rules engine handles the structural matching well. The next layer — understanding which partner has domain expertise in a specific vertical, or a relationship history with a particular type of customer — is a problem for an AI that knows the partner network, not a scoring table. That connection is planned.

---

**[github.com/Suixcity/partner-router](https://github.com/Suixcity/partner-router)**

*Part of a professional portfolio — [view the project brief](https://tomdelaney.us/partner-router-portfolio)*

![Thomas Delaney](https://app.hubspot.com/settings/avatar/6aa77e4b15fadd044100136646f8d3a4)

[**Thomas Delaney**](https://tomdelaney.us/blog/author/thomas-delaney)

People are talking about this blog post

### Related blog posts

[![Recipe: Ad Network Feedback, Finally Worth Serving](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_eyl073eyl073eyl0.png) Recipe: Ad Network Feedback, Finally Worth Serving on Mar 6, 2026 10:49:30 AM](https://tomdelaney.us/blog/recipe-ad-network-feedback-finally-worth-serving)

[![Recipe: Contracts to CRM, Delivered While Still Hot](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_lhgafjlhgafjlhga.png) Recipe: Contracts to CRM, Delivered While Still Hot on Mar 5, 2026 11:29:31 AM](https://tomdelaney.us/blog/recipe-contracts-delivered-while-still-hot)

[![Recipe: Sanctions Screening, Baked into Every Workflow](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_r3fopir3fopir3fo.png) Recipe: Sanctions Screening, Baked into Every Workflow on Mar 5, 2026 9:41:54 AM](https://tomdelaney.us/blog/recipe-sanctions-screening)

## Related **blog posts**

<https://tomdelaney.us/blog/recipe-ad-network-feedback-finally-worth-serving>

![](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_eyl073eyl073eyl0.png)

**Recipe: Ad Network Feedback, Finally Worth Serving**

 Recipe: Ad Network Feedback, Finally Worth Serving Yield: Google Ads and LinkedIn Ads that know what actually happened...

 Read more

<https://tomdelaney.us/blog/recipe-contracts-delivered-while-still-hot>

![](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_lhgafjlhgafjlhga.png)

**Recipe: Contracts to CRM, Delivered While Still Hot**

 Read more

<https://tomdelaney.us/blog/recipe-sanctions-screening>

![](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/Gemini_Generated_Image_r3fopir3fopir3fo.png)

**Recipe: Sanctions Screening, Baked into Every Workflow**

 Read more

![White Modern Minimalist Signature Brand Logo (Website) (5)](https://24279033.fs1.hubspotusercontent-na1.net/hubfs/24279033/White%20Modern%20Minimalist%20Signature%20Brand%20Logo%20(Website)%20(5).png)

This website is the personal project of Thomas Delaney and the views and thoughts expressed are strictly his own and do not represent any other entity. 

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Thomas Delaney",
    "url" : "https://tomdelaney.us/blog/author/thomas-delaney"
  },
  "dateModified" : "2026-03-06T16:54:35.127Z",
  "datePublished" : "2026-03-06T16:53:11.000Z",
  "headline" : "Recipe: Channel Partners, Matched and Plated at the Table",
  "image" : [ "https://tomdelaney.us/hubfs/Gemini_Generated_Image_vpvzanvpvzanvpvz.png" ],
  "mainEntityOfPage" : {
    "@id" : "https://tomdelaney.us/blog/recipe-channel-partners-matched",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://tomdelaney.us/hubfs/White%20Modern%20Minimalist%20Signature%20Brand%20Logo.png"
    }
  }
}
```