Integraciones nativas
Google Sheets, Google Calendar, Notion y HubSpot, conectados por OAuth desde el panel y movidos por el mismo catálogo de eventos que los webhooks.
El modelo
Una conexión es una concesión OAuth a una cuenta externa. Un vínculo dice “cuando ocurra order.created, ejecuta sheets.append_rowcon esta configuración”. Cada evento que casa con un vínculo produce una sincronización, y su historial es consultable.
curl https://api.zapyflow.com.br/api/integrations \
-H "Authorization: Bearer $ZAPYFLOW_API_KEY"Google — una conexión, dos capacidades
Sheets y Calendar aparecen como dos capacidades de una únicaconexión de Google, no dos conexiones. Revocar cualquier token de una concesión de Google revoca la concesión entera, así que dos conexiones separadas harían que “desconectar Sheets” matara en silencio la sincronización de tu Calendar. Añadir la segunda capacidad dispara consentimiento incremental.
Para Sheets pedimos drive.file, no acceso completo a hojas de cálculo: alcanza solo los archivos que eliges explícitamente, que es suficiente para añadir una fila.
Un reintento no duplica tus datos
Esta es la propiedad que decide si una integración es usable, y cada proveedor recibe el mecanismo más fuerte que de verdad ofrece — no uno genérico.
| Acción | Cómo se hace seguro el reintento |
|---|---|
calendar.upsert_event | Google acepta un id de evento provisto por el cliente, así que lo derivamos de la cita. Una inserción duplicada devuelve 409 y cambiamos a patch. Creación idempotente de verdad, sin lectura extra. |
hubspot.upsert_contact / upsert_deal | Creamos las propiedades personalizadas zapyflow_lead_id y zapyflow_order_id con hasUniqueValue y usamos el upsert nativo por lotes de HubSpot. También casa el lead de WhatsApp que tiene teléfono y no correo — la mayoría aquí. |
sheets.append_row | Sheets no tiene upsert, así que la hoja necesita una columna zapyflow_event_id. El camino feliz es un solo append; solo recorremos esa columna en un reintento ambiguo (un fallo después de que la petición salió). |
notion.create_page | Misma forma: una propiedad ZapyFlow ID en la base, consultada solo en un reintento ambiguo. |
La actualización encuentra el registro que creó su creación: la referencia se indexa por la entidad (pedido, lead, cita), no por el evento. Sin eso, order.updated abriría un negocio nuevo en tu embudo cada vez.
Tratamiento de fallos
Las sincronizaciones reintentan con el mismo calendario que los webhooks — seis intentos a lo largo de ~31 horas — pero solo para fallos que el tiempo arregla. Una concesión revocada o un destino borrado son terminales.
| Estado | Significado |
|---|---|
| SUCCEEDED | Escrito en el proveedor. externalRef guarda el id de su lado. |
| SKIPPED | Deliberadamente no escrito — por ejemplo, la hoja no tiene la columna de id. El motivo queda en la sincronización. |
| FAILED | Fallo terminal. Arregla la causa y reintenta. |
| EXHAUSTED | Los seis intentos se agotaron. |
Tras 20 fallos consecutivos se apaga el vínculo — no la conexión. Una hoja rota no puede tumbar una sincronización de Calendar que funciona en la misma cuenta.
Conexiones revocadas
Cuando alguien revoca el acceso en el proveedor, nada nos avisa. Una sonda diaria lo detecta en menos de 24 horas y mueve la conexión a REVOKED, lo que muestra la petición de reconexión en el panel y pausa sus vínculos en vez de quemar reintentos.
Desconectar
DELETE /api/integrations/{installationId} borra la credencial que guardamos y desactiva los vínculos. La respuesta dice si además pudimos revocar en el proveedor — Notion no tiene endpoint de revocación, y devolvemos revokedAtProvider: false en vez de dar a entender un corte de acceso que no hicimos.