Use HubSpot CRM records as evidence for sales, pipeline, and customer analysis. — Claude Skill
Claude Code용 Claude 스킬 · 제공: FunnelEnvy · 실행: /hubspot-crm (Claude 내)·업데이트: 2026년 6월 14일·vmain@1804741
HubSpot 연락처, 회사, 목록, 거래 필드를 비즈니스 워크플로와 연결해 분석 전에 CRM 근거를 가져오고 필드를 점검하며 깨끗하게 동기화합니다.
- HubSpot 연락처, 회사, 목록, 거래 맥락을 분석 워크플로로 가져옵니다.
- 영업 또는 경쟁 질문에 답하기 전에 필요한 CRM 필드를 설명합니다.
- 소유자, 라이프사이클 단계, 거래 사유, 세그먼트 누락 때문에 생기는 잘못된 분석을 줄입니다.
- CRM 연결을 운영하는 담당자에게 API와 동기화 제한을 보이게 합니다.
팀이 핵심 필드 완전성을 확인하기 전에 HubSpot 데이터를 내보내고 분석을 시작합니다.
/hubspot-crm을 실행해 분석 전에 필요한 기록, 필드, 필터, 위생 점검을 정의합니다.
대상
기능
HubSpot 거래 메모와 필드를 경쟁 분석 근거로 사용합니다.
기회 항목을 세그먼트, 경쟁사, 원천, 라이프사이클 단계로 필터링합니다.
보고서를 왜곡할 수 있는 누락된 소유자, 사유, 단계, 필드를 찾습니다.
작동 방식
비즈니스 질문과 답변에 필요한 HubSpot 기록을 정의합니다.
필요한 객체, 필터, 필드, 소유자 데이터, 라이프사이클 단계, 날짜 범위를 식별합니다.
내보낸 HubSpot 근거를 분석 워크플로에 가져오거나 붙여넣습니다.
데이터를 사용하기 전에 누락, 중복, 오래된 필드를 점검합니다.
비즈니스용 요약과 필요한 CRM 정리 작업을 함께 반환합니다.
입력 옵션
답해야 할 영업, 파이프라인, 고객, 경쟁 질문입니다.
예시
Q2 경쟁 패배를 HubSpot에서 분석해야 합니다. 사용 가능한 객체: deals, companies, contacts. 필드: - dealstage - closedate - competitor_name - closed_lost_reason - amount - 담당자 - company_size - segment 알려진 문제: 일부 거래는 competitor_name이 비어 있지만 메모에 LearnPro 또는 GuidePilot이 언급됩니다. 필요: 경쟁 대응 카드 갱신 전에 필드 체크리스트, 필터, 내보내기 계획, 경고.
Q2 실패 종료 거래 중 competitor_name이 LearnPro 또는 GuidePilot인 항목과 메모에 해당 이름이 언급된 항목을 사용하세요. 결론 전에 company_size와 담당자로 세그먼트화합니다.
| 필드 | 중요한 이유 | 누락 시 위험 | |---|---|---| | competitor_name | 대응 카드 갱신의 기본 그룹 | 패배 건이 과소 집계될 수 있음 | | closed_lost_reason | 패배 이유 설명 | 결과가 일화 수준으로 남음 | | segment/company_size | 영향을 받는 시장을 보여 줌 | 엔터프라이즈와 SMB 패턴이 섞임 | | amount | 고가치 패배 우선순위화 | 작은 거래이 이야기를 지배할 수 있음 | | 담당자 | 영업 담당자 후속 확인 가능 | 근거 검증이 어려움 |
필수 필드가 있는 Q2 실패 종료 거래를 내보낸 뒤 LearnPro와 GuidePilot에 대한 메모 키워드 점검을 추가합니다. 추정된 발견과 구조화된 competitor_name을 분리해 표시하세요.
빈 competitor_name 필드를 검토하기 전에는 정확한 경쟁사 패배율을 보고하지 마세요. closed_lost_reason이 일반적이면 통화 메모나 담당자 후속 확인을 거친 뒤 대응 카드 지침으로 바꾸세요.
$25k ARR 이상의 경쟁 실패 종료 거래에는 competitor_name과 closed_lost_reason을 필수로 만드는 CRM 정리 작업을 만듭니다.
개선되는 지표
지원 도구
HubSpot CRM 활용을(를) 사용해 보시겠어요?
시작 방법을 선택하세요.
이 스킬을 컴퓨터에 로컬로 설치하고 실행합니다.
컴퓨터에서 터미널을 열고 이 명령을 붙여넣으세요:
이 명령은 스킬과 모든 파일을 컴퓨터에 다운로드합니다:
모든 프로젝트에서 사용하려면 끝에 -g를 추가하세요.
Claude Code를 시작한 다음 명령을 입력하세요:
HubSpot CRM 연동
REST API를 사용해 연락처와 목록을 HubSpot에 동기화합니다.
환경 변수
HUBSPOT_API_TOKEN="pat-na1-..." # HubSpot의 비공개 앱 토큰
빠른 시작
import os
import json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
class HubSpotClient:
"""간단한 HubSpot API 클라이언트."""
def __init__(self):
self.token = os.environ.get('HUBSPOT_API_TOKEN')
if not self.token:
raise ValueError("HUBSPOT_API_TOKEN 환경 변수가 설정되지 않았습니다")
self.base_url = "https://api.hubapi.com"
def _request(self, method: str, endpoint: str, data: dict = None) -> dict:
url = f"{self.base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
body = json.dumps(data).encode('utf-8') if data else None
request = Request(url, data=body, headers=headers, method=method)
with urlopen(request, timeout=30) as response:
return json.loads(response.read().decode('utf-8'))
정적 목록 생성
def create_static_list(client, name: str) -> str:
"""연락처용 정적 목록을 생성합니다. 목록 ID를 반환합니다."""
payload = {
"name": name,
"objectTypeId": "0-1", # 필수: 0-1 = 연락처
"processingType": "MANUAL"
}
result = client._request("POST", "/crm/v3/lists", payload)
# 응답은 중첩 구조입니다: {"list": {"listId": "..."}}
list_data = result.get("list", result)
list_id = list_data.get("listId")
print(f"✅ 목록 생성됨: {name} (ID: {list_id})")
return list_id
이메일로 연락처 검색
def search_contact(client, email: str) -> str | None:
"""이메일로 연락처를 찾습니다. 연락처 ID 또는 None을 반환합니다."""
payload = {
"filterGroups": [{
"filters": [{
"propertyName": "email",
"operator": "EQ",
"value": email
}]
}],
"properties": ["email"],
"limit": 1
}
result = client._request("POST", "/crm/v3/objects/contacts/search", payload)
results = result.get("results", [])
return results[0]["id"] if results else None
연락처 생성
def create_contact(client, email: str, firstname: str = None, lastname: str = None) -> str:
"""이메일만으로 새 연락처를 생성합니다(기본 업로드).
대상 계정에 사용자 지정 속성이 없어서 생기는 오류를 피하려고
표준 HubSpot 속성(email, firstname, lastname)만 사용합니다.
"""
properties = {"email": email}
if firstname:
properties["firstname"] = firstname
if lastname:
properties["lastname"] = lastname
result = client._request("POST", "/crm/v3/objects/contacts", {"properties": properties})
return result["id"]
중요: 임의의 CSV 열을 속성으로 전달하지 마세요. HubSpot은 대상 계정에 존재하지 않는 속성 이름을 거부합니다. 사용자 지정 속성이 존재한다고 확인하지 않았다면 표준 필드 (email, firstname, lastname)만 사용합니다.
목록에 연락처 추가
def add_to_list(client, list_id: str, contact_ids: list[str]):
"""정적 목록에 연락처를 추가합니다. 100개씩 묶어 처리합니다."""
endpoint = f"/crm/v3/lists/{list_id}/memberships/add"
batch_size = 100
total_added = 0
for i in range(0, len(contact_ids), batch_size):
batch = contact_ids[i:i + batch_size]
# 중요: payload는 {"recordIdsToAdd": [...]}가 아니라 단순 배열입니다.
result = client._request("PUT", endpoint, batch)
added = len(result.get("recordsIdsAdded", []))
total_added += added
print(f" 배치 추가됨: 연락처 {added}개")
print(f"✅ 목록에 총 연락처 {total_added}개 추가됨")
전체 업로드 흐름
def upload_users_to_hubspot(emails: list[str], list_name: str) -> str:
"""이메일 주소 목록을 HubSpot에 업로드합니다."""
client = HubSpotClient()
# 목록 생성
list_id = create_static_list(client, list_name)
# 연락처 찾기 또는 생성
contact_ids = []
for email in emails:
contact_id = search_contact(client, email)
if not contact_id:
contact_id = create_contact(client, email)
contact_ids.append(contact_id)
# 목록에 추가
add_to_list(client, list_id, contact_ids)
print(f"\n✅ 완료!")
print(f" List: https://app.hubspot.com/contacts/lists/{list_id}")
return list_id
사용 예시
# 분석에서 나온 이탈 위험 사용자를 업로드
at_risk_emails = [
"[email protected]",
"[email protected]",
"[email protected]"
]
list_id = upload_users_to_hubspot(
emails=at_risk_emails,
list_name="이탈 위험 체험판 사용자 - 2024년 12월"
)
연동 스크립트 사용
CSV 파일에는 제공된 스크립트를 사용합니다:
.venv/bin/python demos/04-trial-to-paid/scripts/hubspot_integration.py \
path/to/users.csv \
"여기에 목록 이름"
CSV에는 email 열이 있어야 합니다.
API 주의 사항
- 목록 생성에는
objectTypeId가 필요: 연락처에는 항상"objectTypeId": "0-1"을 포함합니다. - 응답은 중첩 구조: 목록 ID는
result["list"]["listId"]에 있으며result["listId"]가 아닙니다. - 목록 추가 payload 형식:
{"recordIdsToAdd": [...]}가 아니라 단순 배열["id1", "id2"]를 사용합니다. - 연락처 ID는 문자열: 숫자처럼 보이더라도 문자열로 다룹니다.
- 배치 제한: 연락처는 최대 100개씩 묶어 추가합니다.
오류 처리
from urllib.error import HTTPError
try:
result = client._request("POST", endpoint, payload)
except HTTPError as e:
error_body = e.read().decode('utf-8')
print(f"HubSpot API error: {e.code}")
print(f" {error_body}")
HubSpot 비공개 앱 토큰 가져오기
- HubSpot에서 Settings(기어 아이콘)로 이동합니다.
- Integrations > Private Apps로 이동합니다.
- Create a private app을 클릭합니다.
- 이름을 지정합니다(예: "AI 에이전트 연동").
- Scopes 아래에서 다음을 활성화합니다:
crm.lists.readcrm.lists.writecrm.objects.contacts.readcrm.objects.contacts.write
- Create app을 클릭하고 토큰을 복사합니다.
HUBSPOT_API_TOKEN환경 변수로 설정합니다.
참조 문서
name: hubspot-crm description: 연락처나 목록을 HubSpot CRM에 동기화할 때 사용합니다. 환경에서 HUBSPOT_API_TOKEN을 자동으로 사용합니다.
HubSpot CRM 연동
REST API를 사용해 연락처와 목록을 HubSpot에 동기화합니다.
환경 변수
HUBSPOT_API_TOKEN="pat-na1-..." # HubSpot의 비공개 앱 토큰
빠른 시작
import os
import json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
class HubSpotClient:
"""간단한 HubSpot API 클라이언트."""
def __init__(self):
self.token = os.environ.get('HUBSPOT_API_TOKEN')
if not self.token:
raise ValueError("HUBSPOT_API_TOKEN 환경 변수가 설정되지 않았습니다")
self.base_url = "https://api.hubapi.com"
def _request(self, method: str, endpoint: str, data: dict = None) -> dict:
url = f"{self.base_url}{endpoint}"
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
body = json.dumps(data).encode('utf-8') if data else None
request = Request(url, data=body, headers=headers, method=method)
with urlopen(request, timeout=30) as response:
return json.loads(response.read().decode('utf-8'))
정적 목록 생성
def create_static_list(client, name: str) -> str:
"""연락처용 정적 목록을 생성합니다. 목록 ID를 반환합니다."""
payload = {
"name": name,
"objectTypeId": "0-1", # 필수: 0-1 = 연락처
"processingType": "MANUAL"
}
result = client._request("POST", "/crm/v3/lists", payload)
# 응답은 중첩 구조입니다: {"list": {"listId": "..."}}
list_data = result.get("list", result)
list_id = list_data.get("listId")
print(f"✅ 목록 생성됨: {name} (ID: {list_id})")
return list_id
이메일로 연락처 검색
def search_contact(client, email: str) -> str | None:
"""이메일로 연락처를 찾습니다. 연락처 ID 또는 None을 반환합니다."""
payload = {
"filterGroups": [{
"filters": [{
"propertyName": "email",
"operator": "EQ",
"value": email
}]
}],
"properties": ["email"],
"limit": 1
}
result = client._request("POST", "/crm/v3/objects/contacts/search", payload)
results = result.get("results", [])
return results[0]["id"] if results else None
연락처 생성
def create_contact(client, email: str, firstname: str = None, lastname: str = None) -> str:
"""이메일만으로 새 연락처를 생성합니다(기본 업로드).
대상 계정에 사용자 지정 속성이 없어서 생기는 오류를 피하려고
표준 HubSpot 속성(email, firstname, lastname)만 사용합니다.
"""
properties = {"email": email}
if firstname:
properties["firstname"] = firstname
if lastname:
properties["lastname"] = lastname
result = client._request("POST", "/crm/v3/objects/contacts", {"properties": properties})
return result["id"]
중요: 임의의 CSV 열을 속성으로 전달하지 마세요. HubSpot은 대상 계정에 존재하지 않는 속성 이름을 거부합니다. 사용자 지정 속성이 존재한다고 확인하지 않았다면 표준 필드 (email, firstname, lastname)만 사용합니다.
목록에 연락처 추가
def add_to_list(client, list_id: str, contact_ids: list[str]):
"""정적 목록에 연락처를 추가합니다. 100개씩 묶어 처리합니다."""
endpoint = f"/crm/v3/lists/{list_id}/memberships/add"
batch_size = 100
total_added = 0
for i in range(0, len(contact_ids), batch_size):
batch = contact_ids[i:i + batch_size]
# 중요: payload는 {"recordIdsToAdd": [...]}가 아니라 단순 배열입니다.
result = client._request("PUT", endpoint, batch)
added = len(result.get("recordsIdsAdded", []))
total_added += added
print(f" 배치 추가됨: 연락처 {added}개")
print(f"✅ 목록에 총 연락처 {total_added}개 추가됨")
전체 업로드 흐름
def upload_users_to_hubspot(emails: list[str], list_name: str) -> str:
"""이메일 주소 목록을 HubSpot에 업로드합니다."""
client = HubSpotClient()
# 목록 생성
list_id = create_static_list(client, list_name)
# 연락처 찾기 또는 생성
contact_ids = []
for email in emails:
contact_id = search_contact(client, email)
if not contact_id:
contact_id = create_contact(client, email)
contact_ids.append(contact_id)
# 목록에 추가
add_to_list(client, list_id, contact_ids)
print(f"\n✅ 완료!")
print(f" List: https://app.hubspot.com/contacts/lists/{list_id}")
return list_id
사용 예시
# 분석에서 나온 이탈 위험 사용자를 업로드
at_risk_emails = [
"[email protected]",
"[email protected]",
"[email protected]"
]
list_id = upload_users_to_hubspot(
emails=at_risk_emails,
list_name="이탈 위험 체험판 사용자 - 2024년 12월"
)
연동 스크립트 사용
CSV 파일에는 제공된 스크립트를 사용합니다:
.venv/bin/python demos/04-trial-to-paid/scripts/hubspot_integration.py \
path/to/users.csv \
"여기에 목록 이름"
CSV에는 email 열이 있어야 합니다.
API 주의 사항
- 목록 생성에는
objectTypeId가 필요: 연락처에는 항상"objectTypeId": "0-1"을 포함합니다. - 응답은 중첩 구조: 목록 ID는
result["list"]["listId"]에 있으며result["listId"]가 아닙니다. - 목록 추가 payload 형식:
{"recordIdsToAdd": [...]}가 아니라 단순 배열["id1", "id2"]를 사용합니다. - 연락처 ID는 문자열: 숫자처럼 보이더라도 문자열로 다룹니다.
- 배치 제한: 연락처는 최대 100개씩 묶어 추가합니다.
오류 처리
from urllib.error import HTTPError
try:
result = client._request("POST", endpoint, payload)
except HTTPError as e:
error_body = e.read().decode('utf-8')
print(f"HubSpot API error: {e.code}")
print(f" {error_body}")
HubSpot 비공개 앱 토큰 가져오기
- HubSpot에서 Settings(기어 아이콘)로 이동합니다.
- Integrations > Private Apps로 이동합니다.
- Create a private app을 클릭합니다.
- 이름을 지정합니다(예: "AI 에이전트 연동").
- Scopes 아래에서 다음을 활성화합니다:
crm.lists.readcrm.lists.writecrm.objects.contacts.readcrm.objects.contacts.write
- Create app을 클릭하고 토큰을 복사합니다.
HUBSPOT_API_TOKEN환경 변수로 설정합니다.