--- title: Cal.com → Odoo CRM url: https://blog.guigpap.com/en/workflows/cal-rdv-odoo/ url_md: https://blog.guigpap.com/en/workflows/cal-rdv-odoo.md category: automation date: '2026-01-31' maturite: production techno: - n8n - odoo - telegram application: - automation - business --- # Cal.com → Odoo CRM > Full Cal.com appointment sync with the Odoo CRM and Telegram notifications ## 1. What? — Definition and context The **Cal.com → Odoo CRM** workflow automatically syncs Cal.com events with Odoo CRM. It manages the full appointment lifecycle: creation, cancellation, rescheduling and meeting end. > **Note - Cal.com** > > **Cal.com** is an open-source alternative to Calendly for booking appointments. This workflow links each appointment to a CRM opportunity in Odoo, allowing prospects to be tracked from first contact to conversion. ### Handled events | Cal.com event | Odoo action | Notification | |---------------|-------------|--------------| | `BOOKING_CREATED` | Create opportunity | Message + iCal file | | `BOOKING_CANCELLED` | Mark as lost | Cancellation message | | `BOOKING_RESCHEDULED` | Update dates | Message + new iCal | | `MEETING_ENDED` | Increase probability | Meeting-end message | --- ## 2. Why? — Stakes and motivations ### Problems solved | Problem | Without sync | With sync | |---------|--------------|-----------| | **Double entry** | Manually create the opportunity after the meeting | Automatic | | **Missed follow-up** | No reminder after the meeting | CRM stage updated | | **Scattered calendar** | Cal.com appointments ≠ work calendar | iCal file sent | | **Untracked cancellations** | Lose the cancellation info | Lead marked as lost | ### Lead lifecycle ```mermaid flowchart TD Created["BOOKING_CREATED · probability 10%"] Ended["MEETING_ENDED · probability 30%"] Qual["Manual qualification · probability 50%+"] Won["Won"] Lost["Lost"] Created --> Ended --> Qual Qual --> Won Qual --> Lost ``` > **Tip - iCal file** > > Each notification includes a `.ics` file that admins can add directly to their calendar (Google Calendar, Outlook, etc.). --- ## 3. How? — Technical implementation ### Architecture ```mermaid flowchart TD Cal["Cal.com webhook"] Switch["Switch · triggerEvent"] Create["BOOKING_CREATED → Create opportunity"] Cancel["BOOKING_CANCELLED → Mark Lost"] Reschedule["BOOKING_RESCHEDULED → Update dates"] Ended["MEETING_ENDED → Update stage"] NotifyICal["Notify + iCal"] NotifyOnly["Notify"] Cal --> Switch Switch --> Create --> NotifyICal Switch --> Cancel --> NotifyOnly Switch --> Reschedule --> NotifyICal Switch --> Ended --> NotifyOnly ``` ### Flow: appointment creation ```mermaid flowchart TD CB["BOOKING_CREATED"] CO["Create Opportunity Odoo · probability 10"] Admins["Load All Admins · Data Table"] Format["Format Booking Notification"] Loop["Loop · for each admin"] Gen["Generate iCal File"] Send["Send Calendar File · Telegram"] CB --> CO --> Admins --> Format --> Loop --> Gen --> Send ``` ### Sample notification ``` 📅 New Cal.com appointment 👤 Client: Jean Dupont 📧 Email: jean.dupont@example.com 🗓️ Date: Thursday 20 January 2026 ⏰ Time: 14:00 - 15:00 📋 Type: Discovery call 🔗 Opportunity: https://odoo.guigpap.com/odoo/crm/123 ``` ### N8N configuration **Cal.com Trigger:** ```yaml Events: - BOOKING_CREATED - BOOKING_CANCELLED - BOOKING_RESCHEDULED - MEETING_ENDED ``` **Switch Node (Route by Event):** ```yaml Type: Switch Mode: Rules Value: {{ $json.triggerEvent }} Rules: - Output 0: BOOKING_CREATED - Output 1: BOOKING_CANCELLED - Output 2: BOOKING_RESCHEDULED - Output 3: MEETING_ENDED ``` **Find Opportunity (Odoo):** ```yaml Resource: Custom Custom Resource: crm.lead Operation: Get Many Limit: 1 Filter: - Field: email_from Operator: = Value: {{ $json.attendees[0].email }} ``` ### iCal generation ```javascript const booking = $json; const attendee = booking.attendees[0]; const formatICalDate = (date) => { return new Date(date).toISOString() .replace(/[-:]/g, '').split('.')[0] + 'Z'; }; const icsContent = `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//N8N//Booking//EN BEGIN:VEVENT UID:${booking.uid}@cal.com DTSTART:${formatICalDate(booking.startTime)} DTEND:${formatICalDate(booking.endTime)} SUMMARY:Cal.com appointment - ${attendee.name} DESCRIPTION:Appointment with ${attendee.name} LOCATION:${booking.location || 'TBD'} STATUS:CONFIRMED END:VEVENT END:VCALENDAR`; return [{ json: { fileName: `appt-${attendee.name}.ics` }, binary: { calendar: { data: Buffer.from(icsContent).toString('base64'), mimeType: 'text/calendar' } } }]; ``` ### Note on Odoo types > **Caution - Odoo boolean types** > > The `active` and `probability` fields must be in **expression mode** (fx icon) inside N8N to send the right types to Odoo. Without this, N8N sends strings and Odoo returns the error: `'>=' not supported between instances of 'str' and 'int'`. ```yaml # Correct configuration Fields: active: {{ false }} # Expression mode! probability: {{ 0 }} # Expression mode! ``` --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |-------|--------|------------| | **Email-based matching** | Duplicates if emails differ | Future broader search | | **No reverse sync** | Odoo → Cal.com not implemented | Telegram actions | | **Single attendee** | Multi-participants not handled | Planned if needed | ### Evolution scenarios **If reverse sync is needed**: - Webhook Odoo → N8N when an opportunity is updated - Update the Cal.com booking - Watch out for infinite loops **If multi-type appointments**: - Create different opportunity types depending on the Cal.com type - Auto-assign by type - Differentiated initial probability **If appointment volume grows**: - Daily digest instead of individual notifications - Filter by appointment type - Round-robin assignment ### Troubleshooting | Problem | Check | |---------|-------| | No trigger | Cal.com: webhook configured? N8N: workflow active? | | Opportunity not found | Attendee email = email_from in Odoo? | | Update fails | Check Odoo credential permissions | | iCal not generated | Generate iCal node connected? Binary data present? | --- ## Related pages ### Workflows - [Notification Hub](/en/workflows/notification-hub/) — Centralised routing - [Website Lead Notification](/en/workflows/website-lead-notification/) — Web-form leads ### Infrastructure - [Odoo 18 on Docker](/en/infrastructure/odoo-18-setup/) — ERP and CRM ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0