Claude-code-plugins-plus-skills hubspot-data-handling
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/hubspot-pack/skills/hubspot-data-handling" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-hubspot-data-handling && rm -rf "$T"
manifest:
plugins/saas-packs/hubspot-pack/skills/hubspot-data-handling/SKILL.mdsource content
HubSpot Data Handling
Overview
Handle GDPR/CCPA compliance with HubSpot's built-in privacy APIs: GDPR delete, data export, consent management, and PII handling for CRM data.
Prerequisites
- HubSpot account with GDPR features enabled
- Scope:
(for GDPR delete)crm.objects.contacts.write - Understanding of GDPR/CCPA requirements
Instructions
Step 1: GDPR Contact Deletion
HubSpot provides a dedicated GDPR delete endpoint that permanently removes all contact data and communications:
import * as hubspot from '@hubspot/api-client'; const client = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN!, }); // GDPR delete: permanently removes contact and all associated data // POST /crm/v3/objects/contacts/gdpr-delete async function gdprDeleteContact(email: string): Promise<void> { // First, find the contact const search = await client.crm.contacts.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: email }], }], properties: ['email'], limit: 1, after: 0, sorts: [], }); if (search.results.length === 0) { console.log(`Contact not found: ${email}`); return; } const contactId = search.results[0].id; // GDPR delete via API await client.apiRequest({ method: 'POST', path: '/crm/v3/objects/contacts/gdpr-delete', body: { objectId: contactId, idProperty: 'hs_object_id', }, }); // Also delete from your local systems await deleteLocalContactData(email); // Audit log (keep for compliance -- do NOT delete audit records) await auditLog({ action: 'GDPR_DELETE', email: '[REDACTED]', // don't store the email in audit contactId, timestamp: new Date().toISOString(), reason: 'Data subject deletion request', }); console.log(`GDPR deleted contact ${contactId}`); }
Step 2: Data Subject Access Request (DSAR)
Export all data HubSpot holds about a contact:
async function exportContactData(email: string): Promise<ContactDataExport> { // Find contact const search = await client.crm.contacts.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: email }], }], properties: [], // get all default properties limit: 1, after: 0, sorts: [], }); if (search.results.length === 0) { return { found: false, data: null }; } const contact = search.results[0]; // Get all properties for complete export const fullContact = await client.crm.contacts.basicApi.getById( contact.id, undefined, // all properties undefined, ['companies', 'deals', 'tickets'] // include associations ); // Get associated deals const deals = await client.crm.deals.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'associations.contact', operator: 'EQ', value: contact.id, }], }], properties: ['dealname', 'amount', 'dealstage', 'createdate'], limit: 100, after: 0, sorts: [], }); // Get engagement history (notes, emails, calls) const notes = await client.crm.objects.notes.basicApi.getPage( 100, undefined, ['hs_note_body', 'hs_timestamp'] ); return { found: true, data: { exportedAt: new Date().toISOString(), source: 'HubSpot CRM', contact: fullContact.properties, associations: fullContact.associations, deals: deals.results.map(d => d.properties), notes: notes.results.map(n => n.properties), }, }; } interface ContactDataExport { found: boolean; data: { exportedAt: string; source: string; contact: Record<string, string>; associations?: any; deals: Record<string, string>[]; notes: Record<string, string>[]; } | null; }
Step 3: PII Redaction for Logging
// Never log PII from HubSpot responses const PII_FIELDS = new Set([ 'email', 'firstname', 'lastname', 'phone', 'mobilephone', 'address', 'city', 'state', 'zip', 'country', 'date_of_birth', 'ip_city', 'ip_state', 'ip_country', ]); function redactContactForLogging(properties: Record<string, string>): Record<string, string> { const redacted: Record<string, string> = {}; for (const [key, value] of Object.entries(properties)) { redacted[key] = PII_FIELDS.has(key) ? '[REDACTED]' : value; } return redacted; } // Usage const contact = await client.crm.contacts.basicApi.getById(id, ['email', 'lifecyclestage']); console.log('Contact data:', redactContactForLogging(contact.properties)); // Output: { email: "[REDACTED]", lifecyclestage: "customer" }
Step 4: Consent Tracking
// Track consent using HubSpot's communication preferences // POST /crm/v3/objects/contacts (with consent properties) async function createContactWithConsent( email: string, properties: Record<string, string>, consent: { marketing: boolean; sales: boolean } ): Promise<void> { await client.crm.contacts.basicApi.create({ properties: { ...properties, email, hs_legal_basis: consent.marketing ? 'Legitimate interest - existing customer' : 'Not applicable', }, associations: [], }); // Set communication preferences via the subscriptions API if (consent.marketing) { await client.apiRequest({ method: 'POST', path: `/communication-preferences/v3/subscribe`, body: { emailAddress: email, subscriptionId: process.env.HUBSPOT_MARKETING_SUBSCRIPTION_ID!, legalBasis: 'CONSENT_WITH_NOTICE', legalBasisExplanation: 'User opted in via signup form', }, }); } }
Step 5: Data Minimization
// Only request the properties you actually need // BAD: Fetches all default properties including PII const bad = await client.crm.contacts.basicApi.getById(id); // GOOD: Only fetch non-PII fields for analytics const good = await client.crm.contacts.basicApi.getById(id, [ 'lifecyclestage', // not PII 'hs_lead_status', // not PII 'createdate', // not PII 'num_associated_deals', // not PII ]);
Output
- GDPR delete endpoint permanently removing contact data
- Data export for Subject Access Requests
- PII redaction utility for safe logging
- Consent tracking with communication preferences
- Data minimization patterns for non-PII analytics
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| GDPR delete returns 404 | Contact already deleted | Idempotent -- log and continue |
| Export missing associations | Scope not granted | Add scope |
| Consent API returns 400 | Invalid subscription ID | Check Settings > Marketing > Email > Subscriptions |
| PII in logs | Missing redaction | Wrap all logging with |
Resources
Next Steps
For enterprise access control, see
hubspot-enterprise-rbac.