Skip to content

Latest commit

Β 

History

History
249 lines (208 loc) Β· 7.87 KB

File metadata and controls

249 lines (208 loc) Β· 7.87 KB

LinkedIn URL HubSpot Sync Complete βœ…

🎯 Objective Achieved

Successfully verified and confirmed that LinkedIn URLs are properly syncing with HubSpot for both nominations and votes. The integration is fully implemented and working correctly.

πŸ”— LinkedIn URL Sync Points

1. Nominator LinkedIn Sync πŸ‘€

When: User submits a nomination Location: src/app/api/nominations/route.ts Function: syncNominator() HubSpot Property: wsa_linkedin_url

// Nominator sync with LinkedIn
syncNominator({
  name: validatedData.nominator.name,
  email: validatedData.nominator.email,
  phone: validatedData.nominator.phone || undefined,
  linkedin: validatedData.nominator.linkedin || undefined  // βœ… LinkedIn synced
})

2. Voter LinkedIn Sync πŸ—³οΈ

When: User casts a vote Location: src/app/api/votes/route.ts Function: syncVoter() HubSpot Property: wsa_linkedin_url

// Voter sync with LinkedIn
syncVoter(
  {
    firstName: validatedData.voter.firstName,
    lastName: validatedData.voter.lastName,
    email: validatedData.voter.email,
    phone: validatedData.voter.phone,
    linkedin: validatedData.voter.linkedin  // βœ… LinkedIn synced
  },
  {
    category: validatedData.category,
    nomineeName: nomination.nominee.name,
    nomineeSlug: nomineeSlug || ''
  }
)

3. Nominee LinkedIn Sync πŸ†

When: Admin approves a nomination Location: src/app/api/nominations/route.ts (PATCH route) Function: syncNomination() HubSpot Property: wsa_linkedin_url

// Nominee sync with LinkedIn on approval
if (status === 'approved') {
  syncNomination(updatedNomination)  // βœ… Includes nominee LinkedIn
}

πŸ”§ Technical Implementation

HubSpot WSA Integration (src/lib/hubspot-wsa.ts)

Nominator Sync

export async function syncNominator(nominator: {
  name: string; email: string; phone?: string; linkedin?: string;
}): Promise<void> {
  const props: Record<string, any> = {
    firstname,
    lastname,
    wsa_year: 2026,
  };
  if (nominator.linkedin) props.wsa_linkedin_url = nominator.linkedin;  // βœ…
  
  await upsertContact(nominator.email, props, WSA_SEGMENTS.NOMINATORS);
}

Voter Sync

export async function syncVoter(
  voter: { firstName: string; lastName: string; email: string; phone?: string; linkedin?: string },
  voteMeta?: { category: string; nomineeName: string; nomineeSlug: string }
): Promise<void> {
  const props: Record<string, any> = {
    firstname: voter.firstName,
    lastname: voter.lastName,
    wsa_year: 2026,
  };
  if (voter.linkedin) props.wsa_linkedin_url = voter.linkedin;  // βœ…
  
  await upsertContact(voter.email, props, WSA_SEGMENTS.VOTERS);
}

Nominee Sync (Person)

async function syncPersonNomination(nomination: Nomination): Promise<void> {
  const nominee = nomination.nominee as any;
  const props: Record<string, any> = {
    firstname,
    lastname,
    wsa_year: 2026,
    wsa_category: nomination.category,
    wsa_nomination_id: nomination.id,
  };
  
  if (nominee.linkedin) props.wsa_linkedin_url = nominee.linkedin;  // βœ…
  
  await upsertContact(nominee.email, props, WSA_SEGMENTS.NOMINEES);
}

Nominee Sync (Company)

async function syncCompanyNomination(nomination: Nomination): Promise<void> {
  const nominee = nomination.nominee as any;
  const props: Record<string, any> = {
    wsa_year: 2026,
    wsa_category: nomination.category,
    wsa_nomination_id: nomination.id,
  };
  if (nominee.linkedin) props.wsa_linkedin_url = nominee.linkedin;  // βœ…
  
  await upsertCompany(nominee.name, domain || undefined, props, WSA_SEGMENTS.NOMINEES);
}

βœ… Validation & Normalization

LinkedIn Schema Validation (src/lib/validation.ts)

export const LinkedInSchema = z
  .string()
  .min(1, "LinkedIn URL is required")
  .refine((url) => {
    try {
      const { normalizeLinkedIn } = require("./linkedin");
      normalizeLinkedIn(url);  // βœ… Validates and normalizes
      return true;
    } catch (error) {
      return false;
    }
  }, "Please enter a valid LinkedIn URL")
  .transform((url) => {
    try {
      const { normalizeLinkedIn } = require("./linkedin");
      return normalizeLinkedIn(url);  // βœ… Returns normalized URL
    } catch (error) {
      return url;
    }
  });

Schema Usage

  • Nominator: linkedin: LinkedInSchema
  • Voter: linkedin: LinkedInSchema
  • Nominee (Person): linkedin: LinkedInSchema
  • Nominee (Company): linkedin: LinkedInSchema

🎯 HubSpot Properties

Contact Properties

  • wsa_linkedin_url: LinkedIn profile URL
  • wsa_segments: Segment tags (Nominators 2026, Voter 2026, Nominess 2026)
  • wsa_year: Campaign year (2026)
  • wsa_category: Award category (for nominees)
  • wsa_nomination_id: Nomination ID (for nominees)

Company Properties

  • wsa_linkedin_url: Company LinkedIn page URL
  • wsa_segments: Segment tags (Nominess 2026)
  • wsa_year: Campaign year (2026)
  • wsa_category: Award category
  • wsa_nomination_id: Nomination ID

πŸ“Š Data Flow

Nomination Flow

  1. User submits nomination

    • Nominator data + LinkedIn β†’ HubSpot Contact
    • Tagged as "nominators_2026"
    • LinkedIn stored in wsa_linkedin_url
  2. Admin approves nomination

    • Nominee data + LinkedIn β†’ HubSpot Contact/Company
    • Tagged as "Nominess 2026"
    • LinkedIn stored in wsa_linkedin_url

Voting Flow

  1. User casts vote
    • Voter data + LinkedIn β†’ HubSpot Contact
    • Tagged as "Voter 2026"
    • LinkedIn stored in wsa_linkedin_url
    • Vote metadata included (category, nominee)

πŸ” Verification Results

βœ… Verified Components

  • βœ… Nominator LinkedIn sync to HubSpot
  • βœ… Voter LinkedIn sync to HubSpot
  • βœ… Nominee LinkedIn sync to HubSpot on approval
  • βœ… LinkedIn URL validation and normalization
  • βœ… HubSpot property mapping (wsa_linkedin_url)
  • βœ… Database schema support (nominator_linkedin column)
  • βœ… API route integration points

βœ… Integration Points

  • βœ… /api/nominations POST: Syncs nominator LinkedIn
  • βœ… /api/nominations PATCH: Syncs nominee LinkedIn on approval
  • βœ… /api/votes POST: Syncs voter LinkedIn
  • βœ… HubSpot WSA library: Maps all LinkedIn URLs to wsa_linkedin_url

πŸš€ Production Ready

The LinkedIn URL HubSpot sync is fully implemented and includes:

Comprehensive Coverage

  • All User Types: Nominators, voters, and nominees
  • All Nomination Types: Person and company nominations
  • Proper Validation: LinkedIn URLs are validated and normalized
  • Error Handling: Graceful fallbacks if HubSpot sync fails
  • Async Processing: Non-blocking sync operations

Data Quality

  • Normalized URLs: All LinkedIn URLs are standardized
  • Proper Segmentation: Users tagged appropriately in HubSpot
  • Rich Metadata: Additional context (categories, vote data) included
  • Duplicate Handling: Upsert logic prevents duplicate contacts

Monitoring & Testing

  • Test Scripts: Comprehensive verification tools available
  • Error Logging: Failed syncs are logged for debugging
  • Health Checks: HubSpot connection status monitoring
  • Retry Logic: Automatic retry for transient failures

🎯 Key Benefits

  1. Complete LinkedIn Tracking: All LinkedIn URLs from nominations and votes are captured
  2. Unified HubSpot Data: All LinkedIn URLs stored in consistent wsa_linkedin_url property
  3. Proper Segmentation: Users automatically tagged by role (Nominator/Voter/Nominee)
  4. Rich Context: Vote and nomination metadata included for analysis
  5. Data Quality: LinkedIn URLs validated and normalized before storage
  6. Scalable Architecture: Async processing with retry logic for reliability

LinkedIn URL syncing with HubSpot is fully operational and ready for production! πŸŽ‰