Sincronizar HubSpot y Stripe sin que los datos se contradigan
Demo propia de dothecode: un SaaS ficticio por suscripción donde HubSpot y Stripe se sincronizan en los dos sentidos, con dueño claro por campo, eventos que no se pierden y correos del ciclo de vida generados por código.
Demo propia — SaaS por suscripción con HubSpot y Stripe
El encargo
Un SaaS por suscripción vive partido en dos. Stripe sabe quién paga, quién está en prueba y a quién se le rechazó la tarjeta. HubSpot sabe quién es el contacto, en qué etapa comercial está y qué correos recibió. Cuando los dos no se hablan, ventas llama a un cliente moroso como si fuera un prospecto y marketing le manda una oferta de bienvenida a alguien que ya canceló.
Queríamos mostrar cómo encaramos una integración de este tipo con algo que se pueda tocar: explicarlo con un diagrama sirve hasta cierto punto; lo que convence es verlo andar. Así que construimos la demo: una sincronización en los dos sentidos donde cada campo tiene un dueño claro, donde un webhook perdido no deja datos inconsistentes, y donde alguien de ventas puede disparar una acción de cobro desde el CRM sin tocar Stripe.
También queríamos mostrar la parte de marketing: que los correos del ciclo de vida —bienvenida, fin de la prueba, cobro rechazado, recuperación— salieran de HubSpot a partir de datos reales de facturación, con el consentimiento respetado en los dos sistemas.
Como no hay cliente, construimos el SaaS ficticio entero: alta con doble confirmación por correo, prueba gratuita de catorce días, pago con Stripe y un panel donde el visitante ve en vivo cada llamada que viajó entre los tres sistemas. La cuenta de HubSpot es de desarrollador y Stripe está en modo de prueba.
Cómo lo construimos
Un registro de campos con dueño declarado
Antes de escribir sincronización, decidimos quién manda sobre cada dato. La facturación —estado, plan, fin de la prueba— la escribe Stripe a través de Django y en HubSpot queda de solo lectura. La oferta de retención la escribe una persona en HubSpot y viaja a Stripe. Ese registro único genera después el esquema de HubSpot, el mapeo y las suscripciones de webhook, así no hay tres listas que alguien tenga que mantener a mano y que tarde o temprano se desalinean. Del lado del CRM creamos un objeto propio, 'Suscripción de Stripe', asociado al contacto, más una propiedad resumen para que los flujos de trabajo puedan reaccionar.
Una bandeja de entrada para los eventos
Los webhooks no procesan nada. Cada evento de Stripe o de HubSpot se verifica por firma —HMAC con ventana de cinco minutos y comparación en tiempo constante—, se guarda una sola vez por identificador externo y se encola. De ahí en más, hasta ocho reintentos con espera exponencial, respeto del Retry-After cuando llega un 429, y un barredor cada cinco minutos que reencola lo que el broker haya perdido. Si HubSpot no está conectado o está roto, los eventos quedan en espera en lugar de fallar o morir, y se reanudan solos al reconectar. Un problema con el consentimiento de marketing nunca frena la sincronización de facturación.
La vuelta: del CRM a Stripe
Cuando alguien cambia la oferta de retención desde HubSpot, el CRM avisa y Django aplica o quita un cupón en Stripe. Es idempotente por estado: no vuelve a otorgar lo que ya venció ni pisa descuentos que ya existían en la suscripción.
Reconciliación como red de seguridad
Los permisos de la interfaz de HubSpot bloquean la pantalla, no la realidad: siempre hay una API, una importación o un usuario con permisos de más. Un proceso periódico, con cerrojo de una sola corrida y un cursor con margen, busca eventos de Stripe que no llegaron, ediciones manuales sobre campos que no le pertenecen a HubSpot —las revierte y deja registrado quién las hizo— y bajas de suscripción a correos.
Credenciales, consentimiento y logs
Los tokens de OAuth van cifrados en reposo con claves rotables y se guardan en una conexión de base de datos propia, para que el refresco quede confirmado aunque quien lo pidió esté dentro de una transacción; el refresco corre bajo bloqueo de fila y hay un freno de cinco minutos cuando HubSpot rechaza la configuración. El código de autorización nunca viaja en redirecciones ni en logs. El consentimiento va en los dos sentidos: la casilla de la página de confirmación se refleja en HubSpot, y una baja hecha en HubSpot siempre gana. Y los logs enmascaran correos, tokens y trazas, también en la librería HTTP, que si no escribía direcciones de correo dentro de las URLs.
Marketing definido como código
Una plantilla, dos listas dinámicas, cuatro correos y cuatro flujos —bienvenida, fin de la prueba, cobro rechazado con salida al recuperar, recuperación a los siete días— los crea y repara un comando idempotente. Nada se arma clickeando. Eso significa que todo lo que la demo monta en HubSpot se puede recrear en otra cuenta sin rehacer el trabajo a mano.
Resultados
La demo está en línea desde el 12 de octubre de 2026, detrás de una clave de acceso: visible para quien la pida, invisible para el resto.
Los dos flujos quedaron verificados con pagos de prueba: un alta termina como contacto en prueba con su suscripción asociada en HubSpot, y un cambio de oferta en el CRM termina como cupón en Stripe.
Con HubSpot desconectado, los eventos de Stripe esperan en vez de perderse y se reanudan solos al reconectar; se verificó con dos eventos reales.
La base tiene cerca de 370 tests automatizados, con dos rondas de revisión de seguridad y de código por etapa.
Todo lo que la demo crea en HubSpot —objeto propio, propiedades, listas, correos y flujos— se genera por código y se puede recrear en otra cuenta con dos comandos.
Tecnologías
- Python 3.13
- Django 6.1
- Celery 5.6 + Redis
- PostgreSQL 18
- HubSpot (OAuth, CRM API, objetos propios, webhooks, listas, correos y flujos por API)
- Stripe (Checkout, suscripciones, cupones, webhooks)
- Resend (correo transaccional)
- httpx
- pytest
- Docker
