Todo lo que necesitas para conectar el adapter de HubSpot CRM al Core Service Bus y desplegarlo en
rtp-transversal-dev — arquitectura, payloads, secrets, logica de upsert y pipeline.
Liveness + readiness probes, ping a HubSpot + Secret Manager
Flujo de request — POST /sync
Cloud Tasks
→
SyncController
valida DTO
→
SyncService
orquesta
→
Transformer
envelope → props
→
HubSpotClient
upsert contact
→
ResultPublisher
Pub/Sub
Logica de upsert del HubSpotClient
El client intenta crear el contacto con POST /crm/v3/objects/contacts.
Si HubSpot responde 409 Conflict (el email ya existe), busca el contacto
por email con POST /contacts/search y lo actualiza con PATCH /contacts/{id}.
HubSpotClientService — upsert flow
// 1. Intento de creacionconst response = await fetch(`${baseUrl}/crm/v3/objects/contacts`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify(data),
});
// 2. Si 409 → buscar por email, luego PATCHif (response.status === 409) {
returnthis.updateByEmail(data, token);
// POST /contacts/search → filterGroups email=EQ// PATCH /contacts/{id} → actualiza propiedades
}
Propiedades mapeadas por el Transformer
Propiedad HubSpot
Campo del envelope.data
Notas
email
email
Requerido — llave de deduplicacion
firstname
first_name
—
lastname
last_name
—
phone
phone
—
company
company
—
csb_event_id
envelope.id
Trazabilidad hacia el evento original
csb_source
envelope.source
Origen del evento
csb_synced_at
(generado)
Timestamp ISO de sincronizacion
!
Las propiedades csb_event_id, csb_source y csb_synced_at
son propiedades custom que deben existir en HubSpot antes del primer sync.
Crearlas en Settings → Properties → Contact properties como tipo Single-line text.
02
Conectar el CSB con el adapter
1
Registrar el endpoint en el seed del CSB
En la configuracion de destinations del CSB, agrega la URL del Cloud Run como destino de Cloud Tasks.
rtp-csb — seed / config de destinations
// URL de Cloud Run (una vez desplegado)const CRM_ADAPTER_URL = 'https://rtp-transversal-dev-dev-crm-adapter-XXXX-uc.a.run.app';
// Rutas que Cloud Tasks debe enviar al adapterconst destinations = {
contact_sync: `${CRM_ADAPTER_URL}/sync`,
// Endpoints planeados (ver seccion 07):// contact_resolve: `${CRM_ADAPTER_URL}/contacts/resolve`,// deals_transform: `${CRM_ADAPTER_URL}/deals/transform`,// deals_sync: `${CRM_ADAPTER_URL}/deals/sync`,
};
2
Crear la Cloud Tasks queue apuntando al adapter
La queue necesita la service account del adapter como oidcToken.serviceAccountEmail
para que Cloud Tasks pueda llamar al Cloud Run con IAM.
La queue se crea via Terraform (modulo crm-compute), pero si necesitas crearla
manualmente antes del primer deploy:
El campo envelope.data.email es obligatorio.
Si no viene, el Transformer lanza Error: Missing required field: email
y Cloud Tasks reintentara segun la configuracion de la cola.
4
Resultado que el adapter publica al CSB via Pub/Sub
Tras sincronizar con HubSpot, el adapter publica un CloudEvent v1.0 en el topic configurado.
El CSB lo consume con una suscripcion push.
El CSB necesita una suscripcion al topic csb-crm-results-dev.
Terraform crea el topic y una suscripcion base, pero si el CSB necesita una suscripcion push
apuntando a su propio endpoint:
El adapter usa un solo secret para autenticarse con HubSpot. El patron es csb-{env}-{name}:
Secret
Descripcion
Donde obtenerlo
csb-dev-hubspot-api-token
Private App token de HubSpot
HubSpot → Settings → Integrations → Private Apps
gcloud — crear el secret
# Crear el secret (reemplaza con el token real de la Private App)echo -n "pat-na1-XXXXX-XXXX-XXXX" | \
gcloud secrets create csb-dev-hubspot-api-token \
--data-file=- \
--project=rtp-transversal-dev
# Verificar que se creo correctamentegcloud secrets versions access latest \
--secret=csb-dev-hubspot-api-token \
--project=rtp-transversal-dev
!
La Private App en HubSpot necesita los siguientes scopes:
crm.objects.contacts.read,
crm.objects.contacts.write.
Para endpoints futuros (deals): crm.objects.deals.read, crm.objects.deals.write.
Variables de entorno
Si USE_SECRET_MANAGER=false (desarrollo local), el adapter lee directamente de env vars.
En Cloud Run, Terraform configura USE_SECRET_MANAGER=true automaticamente.
Variable
Default
Descripcion
USE_SECRET_MANAGER
false
Si true, lee token de Secret Manager
HUBSPOT_API_TOKEN
(vacio)
Token directo (solo si USE_SECRET_MANAGER=false)
HUBSPOT_BASE_URL
https://api.hubapi.com
URL base de HubSpot API
GOOGLE_CLOUD_PROJECT
(vacio)
Proyecto GCP
GCP_PROJECT_ID
rtp-transversal-dev
Alias del proyecto (usado por ResultPublisher)
PUBSUB_RESULT_TOPIC
csb-crm-results
Topic de Pub/Sub para resultados
RESULT_TOPIC
hubspot.contact.synced.v1
Tipo del CloudEvent (config)
NODE_ENV
dev
Ambiente (dev/qa/production)
PORT
8080
Puerto del servidor
Permisos IAM minimos
Service Account
Rol
Para que
csb-crm-adapter-dev
roles/secretmanager.secretAccessor
Leer el token de HubSpot en runtime
csb-crm-adapter-dev
roles/pubsub.publisher
Publicar resultados de sync
csb-run-sa
roles/run.invoker
Cloud Tasks (via CSB) puede llamar al Cloud Run
Propiedades custom en HubSpot
Antes del primer sync, crea estas propiedades en HubSpot → Settings → Properties → Contact:
Internal name
Label
Type
csb_event_id
CSB Event ID
Single-line text
csb_source
CSB Source
Single-line text
csb_synced_at
CSB Synced At
Single-line text
04
Deploy manual a Cloud Run (primer deploy)
Para el primer deploy antes de tener el pipeline CI/CD conectado al
trigger.
El directorio infra/ esta creado con dos modulos y tres ambientes (dev,
qa, prod).
El pipeline de Cloud Build ejecuta terraform init → plan → apply automaticamente.
Estructura de infra/
arbol de directorios
infra/
envs/
dev/
main.tf # SA + modulos + outputs
variables.tf # project_id, region, image_tag, csb_invoker_sa_email
backend.tf # GCS backend
terraform.tfvars # rtp-transversal-dev, us-central1
qa/ # espejo de dev con tfvars de qa
prod/ # espejo de dev con tfvars de prod
modules/
crm-compute/ # Cloud Run + Pub/Sub + Cloud Tasks + IAM invoker
crm-secrets/ # Secret Manager secret + IAM secretAccessor
Recursos declarados
Modulo / Archivo
Recurso Terraform
Descripcion
crm-compute
google_cloud_run_v2_service
Cloud Run del adapter (ingress: INTERNAL_ONLY)
crm-compute
google_pubsub_topic
Topic csb-crm-results-{env} (retencion 7d)
crm-compute
google_pubsub_subscription
Suscripcion base para el CSB
crm-compute
google_cloud_tasks_queue
Cola csb-hubspot-adapter-queue-{env}
crm-compute
google_cloud_run_v2_service_iam_member
CSB SA como run.invoker
crm-secrets
google_secret_manager_secret
Secret csb-{env}-hubspot-api-token
crm-secrets
google_secret_manager_secret_iam_member
Adapter SA como secretAccessor
envs/dev/main.tf
google_service_account
SA csb-crm-adapter-{env}
envs/dev/main.tf
google_project_iam_member
SA con pubsub.publisher
Outputs de Terraform
Output
Uso
service_url
Registrar en CSB como CRM_ADAPTER_URL para Cloud Tasks
queue_name
Nombre de la cola de Cloud Tasks
result_topic_id
Topic de Pub/Sub — CSB consume resultados de aqui
crm_adapter_sa
Email del Service Account del adapter
06
Pipeline CI/CD — cloudbuild.yaml listo
✓
cloudbuild.yaml esta en la raiz del repo con un pipeline de 7 pasos.
Usa node:20-alpine para dependencias/lint/test y hashicorp/terraform:1.7 para infra.
Cobertura minima: 65% en lineas y funciones.
1
Security audit + Lint + Tests (en paralelo)
Tres pasos que corren en paralelo (waitFor: ['-']):
npm audit --audit-level=high,
npm run lint, y
npm run test:cov con threshold de 65%.
2
Docker build
Construye imagen con tags $SHORT_SHA y $_ENV-latest.
Usa --cache-from para builds incrementales.
3
Docker push
Push con --all-tags a Artifact Registry (csb-adapters).
4
Terraform init → plan → apply
Ejecuta en infra/envs/$_ENV, pasa -var=image_tag=$SHORT_SHA
y -var=csb_invoker_sa_email=$_CSB_INVOKER_SA al plan.
5
Deploy --no-traffic
Despliega nueva revision sin recibir trafico.
6
Smoke test via gcloud proxy
Usa gcloud run services proxy (porque ingress es INTERNAL_ONLY).
Hasta 12 intentos cada 5s contra /health.
7
Promover trafico a latest
Solo si el smoke test pasa:
gcloud run services update-traffic --to-latest.
Transformar datos de negocio a propiedades de deal HubSpot
planeado
order-to-hubspot-saga
POST /deals/sync
Crear/actualizar deal en HubSpot asociado a un contacto
planeado
order-to-hubspot-saga
!
Los endpoints /contacts/resolve, /deals/transform y /deals/sync
estan referenciados por la saga order-to-hubspot-saga.yaml en el CSB.
Deben implementarse antes de activar esa saga en produccion.
09
Orden de ejecucion recomendado
#
Tarea
Estado
1
Crear propiedades custom en HubSpot (csb_event_id, csb_source,
csb_synced_at)
pendiente
2
Crear Private App en HubSpot con scopes de contacts
pendiente
3
Crear secret csb-dev-hubspot-api-token en Secret Manager
pendiente
4
Crear directorio infra/ con Terraform
listo
5
Crear cloudbuild.yaml
listo
6
Crear Dockerfile (multi-stage, non-root)
listo
7
Crear Artifact Registry repo csb-adapters (si no existe)
pendiente
8
Deploy manual (seccion 04) para validar imagen
pendiente
9
Registrar URL del Cloud Run en seed del CSB
pendiente
10
Crear Cloud Tasks queue (via Terraform o manual)
pendiente
11
Crear suscripcion Pub/Sub push en CSB
pendiente
12
Smoke test E2E (seccion 07)
pendiente
13
Implementar endpoints planeados (seccion 08)
post-deploy
14
Eliminar apps/hubspot-adapter de rtp-csb
post-migracion
✓
Codigo fuente, infra Terraform, Dockerfile y pipeline CI/CD listos en rama dev.
Lo pendiente es configuracion GCP (secret del token HubSpot, registrar trigger de Cloud Build),
configuracion en HubSpot (Private App + propiedades custom) y el primer deploy.