Inapoi la Integrari
🗓️

Calendly Integration

Integrează VAI Portal cu Calendly pentru programare inteligentă a întâlnirilor, management automat al disponibilității și fluxuri personalizate de confirmare.

Smart Booking

Programează automat întâlniri folosind tipurile de evenimente Calendly.

Availability Sync

Sincronizează disponibilitatea în timp real cu calendarul principal.

Time Zone Detection

Detectează automat fusul orar al participantilor pentru programări corecte.

Custom Workflows

Creează fluxuri personalizate pentru confirmări și reminder-e.

Arhitectura Integrării

VAI Portal

AI Agents

Calendly API

Scheduling Engine

Calendly

Booking Platform

REST API & Webhooks

Tipuri de Evenimente Automate

Sales Demo

30 min

Demonstrație produs pentru clienți potențiali

Buffer: 15 min

Technical Support

45 min

Sesiune de suport tehnic personalizat

Buffer: 10 min

Strategy Call

60 min

Discuție strategică despre proiecte

Buffer: 15 min

Quick Consultation

15 min

Consultanță rapidă și întrebări generale

Buffer: 5 min

Cazuri de Utilizare

Sales Demos

Programează demo-uri de vânzare automat din conversații AI.


      VAI Agent → Lead Qualification → Calendly API → Meeting Scheduled
           ↓              ↓              ↓
      Interest Check   Event Type     Confirmation
    

Support Sessions

Configurează sesiuni de suport tehnic cu programare flexibilă.


      VAI Agent → Issue Analysis → Calendly Booking → Support Session
           ↓              ↓              ↓
      Problem Type   Time Slot     Calendar Update
    

Consulting Calls

Organizează consultații specializate bazate pe expertiză necesară.


      VAI Agent → Expertise Match → Calendly Event → Consultation
           ↓              ↓              ↓
      Topic Analysis   Consultant     Calendar Sync
    

Exemple de Implementare

Calendly API Integration

// Calendly API Integration
const axios = require('axios');

class CalendlyIntegration {
  constructor() {
    this.apiKey = process.env.CALENDLY_API_KEY;
    this.baseURL = 'https://api.calendly.com';
    this.client = axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async scheduleEventFromVAI(bookingData) {
    const {
      eventType,
      inviteeEmail,
      inviteeName,
      preferredTime,
      conversationId,
      customAnswers,
      notes
    } = bookingData;

    try {
      // Get event type details
      const eventTypeData = await this.getEventType(eventType);
      
      // Find available time slots
      const availableSlots = await this.findAvailableSlots(
        eventTypeData.uri,
        preferredTime
      );

      if (availableSlots.length === 0) {
        return {
          success: false,
          message: 'Nu am găsit intervale orare disponibile pentru acest tip de eveniment.',
          suggestions: await this.suggestAlternativeSlots(eventTypeData.uri, preferredTime)
        };
      }

      // Book the event
      const booking = await this.createBooking({
        event_type: eventTypeData.uri,
        start_time: availableSlots[0].start_time,
        end_time: availableSlots[0].end_time,
        invitee: {
          email: inviteeEmail,
          name: inviteeName
        },
        custom_answers: customAnswers || [],
        location: {
          type: 'online',
          location_url: 'https://meet.google.com/vai-portal'
        }
      });

      // Add VAI metadata
      await this.addVAIMetadata(booking.uri, {
        conversation_id: conversationId,
        agent_type: bookingData.agentType,
        booking_source: 'vai_agent',
        notes: notes
      });

      // Send confirmation to VAI
      await this.notifyVAIBooking(booking, bookingData);

      return {
        success: true,
        booking: {
          id: booking.uri.split('/').pop(),
          eventType: eventTypeData.name,
          startTime: booking.start_time,
          endTime: booking.end_time,
          location: booking.location.location_url,
          invitee: {
            email: booking.invitee.email,
            name: booking.invitee.name
          },
          meetingUrl: booking.location.location_url
        },
        message: 'Întâlnirea a fost programată cu succes în Calendly!'
      };

    } catch (error) {
      console.error('Error scheduling Calendly event:', error);
      return {
        success: false,
        message: 'A apărut o eroare la programarea întâlnirii.',
        error: error.response?.data || error.message
      };
    }
  }

  async getEventType(eventTypeIdentifier) {
    try {
      // Search for event type by name or URI
      const response = await this.client.get('/event_types', {
        params: {
          active: true,
          count: 100
        }
      });

      const eventType = response.data.collection.find(
        type => type.name === eventTypeIdentifier || 
                type.uri.includes(eventTypeIdentifier) ||
                type.slug === eventTypeIdentifier
      );

      if (!eventType) {
        throw new Error(`Event type '${eventTypeIdentifier}' not found`);
      }

      return eventType;
    } catch (error) {
      console.error('Error getting event type:', error);
      throw error;
    }
  }

  async findAvailableSlots(eventTypeUri, preferredTime) {
    try {
      const startDate = new Date(preferredTime);
      const endDate = new Date(preferredTime.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days ahead

      const response = await this.client.get('/schedule_availability/times', {
        params: {
          event_type: eventTypeUri,
          start_time: startDate.toISOString(),
          end_time: endDate.toISOString()
        }
      });

      return response.data.collection.map(slot => ({
        start_time: slot.start_time,
        end_time: slot.end_time,
        availability: 'available'
      }));

    } catch (error) {
      console.error('Error finding available slots:', error);
      return [];
    }
  }

  async suggestAlternativeSlots(eventTypeUri, preferredTime) {
    const alternatives = [];
    const daysAhead = 14;

    for (let day = 1; day <= daysAhead; day++) {
      const alternativeDate = new Date(preferredTime);
      alternativeDate.setDate(alternativeDate.getDate() + day);
      alternativeDate.setHours(9, 0, 0, 0);

      try {
        const slots = await this.findAvailableSlots(eventTypeUri, alternativeDate);

        if (slots.length > 0) {
          alternatives.push({
            date: alternativeDate.toLocaleDateString('ro-RO'),
            slots: slots.slice(0, 3)
          });
        }
      } catch (error) {
        console.error('Error getting alternative slots:', error);
      }
    }

    return alternatives;
  }

  async createBooking(bookingData) {
    try {
      const response = await this.client.post('/scheduled_events', bookingData);
      return response.data.resource;
    } catch (error) {
      console.error('Error creating booking:', error);
      throw error;
    }
  }

  async addVAIMetadata(bookingUri, metadata) {
    try {
      // Add custom metadata to the booking
      await this.client.patch(`${bookingUri}/guests`, {
        guests: [{
          email: process.env.VAI_SYSTEM_EMAIL,
          name: 'VAI System'
        }]
      });

      // Store additional metadata in VAI system
      await fetch(`${process.env.VAI_API_URL}/calendly/metadata`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.VAI_API_KEY}`
        },
        body: JSON.stringify({
          booking_uri: bookingUri,
          metadata: metadata,
          timestamp: new Date().toISOString()
        })
      });

    } catch (error) {
      console.error('Error adding VAI metadata:', error);
      // Don't throw error here as booking was successful
    }
  }

  async notifyVAIBooking(booking, originalData) {
    try {
      await fetch(`${process.env.VAI_API_URL}/calendly/notify`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.VAI_API_KEY}`
        },
        body: JSON.stringify({
          booking_id: booking.uri.split('/').pop(),
          conversation_id: originalData.conversationId,
          user_id: originalData.userId,
          event_type: originalData.eventType,
          start_time: booking.start_time,
          end_time: booking.end_time,
          invitee_email: booking.invitee.email,
          invitee_name: booking.invitee.name,
          meeting_url: booking.location.location_url,
          timestamp: new Date().toISOString()
        })
      });
    } catch (error) {
      console.error('Error notifying VAI system:', error);
    }
  }

  async getUpcomingEvents(userEmail, days = 7) {
    try {
      const startDate = new Date().toISOString();
      const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();

      const response = await this.client.get('/scheduled_events', {
        params: {
          invitee_email: userEmail,
          min_start_time: startDate,
          max_start_time: endDate,
          status: 'active',
          sort: 'start_time:desc'
        }
      });

      return response.data.collection.map(event => ({
        id: event.uri.split('/').pop(),
        name: event.name,
        startTime: event.start_time,
        endTime: event.end_time,
        location: event.location?.location_url,
        eventType: event.event_type.name,
        invitee: {
          email: event.invitee.email,
          name: event.invitee.name
        },
        status: event.status
      }));

    } catch (error) {
      console.error('Error getting upcoming events:', error);
      return [];
    }
  }

  async cancelEvent(bookingUri, reason = '') {
    try {
      await this.client.post(`${bookingUri}/cancellation`, {
        reason: reason
      });

      return { success: true };
    } catch (error) {
      console.error('Error canceling event:', error);
      return { success: false, error: error.message };
    }
  }

  async rescheduleEvent(bookingUri, newStartTime) {
    try {
      const response = await this.client.post(`${bookingUri}/reschedule`, {
        new_start_time: newStartTime
      });

      return {
        success: true,
        newBooking: response.data.resource
      };
    } catch (error) {
      console.error('Error rescheduling event:', error);
      return { success: false, error: error.message };
    }
  }

  async getUserAvailability(userUri, startDate, endDate) {
    try {
      const response = await this.client.get('/user_availability', {
        params: {
          user: userUri,
          start_time: startDate,
          end_time: endDate
        }
      });

      return response.data.availability.map(slot => ({
        startTime: slot.start_time,
        endTime: slot.end_time,
        status: slot.status
      }));

    } catch (error) {
      console.error('Error getting user availability:', error);
      return [];
    }
  }
}

module.exports = CalendlyIntegration;

VAI Calendly Webhook Handler

// VAI Calendly Webhook Handler
const express = require('express');
const CalendlyIntegration = require('./calendly-integration');
const crypto = require('crypto');
const app = express();
app.use(express.json());

const calendlyIntegration = new CalendlyIntegration();

// Verify Calendly webhook signature
function verifyCalendlySignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Main webhook endpoint
app.post('/webhooks/calendly', async (req, res) => {
  try {
    const signature = req.headers['calendly-webhook-signature'];
    const payload = req.body;

    // Verify webhook signature
    if (!verifyCalendlySignature(payload, signature, process.env.CALENDLY_WEBHOOK_SECRET)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const { event, payload: eventData } = payload;

    switch (event) {
      case 'invitee.created':
        await handleInviteeCreated(eventData);
        break;
      case 'invitee.canceled':
        await handleInviteeCanceled(eventData);
        break;
      case 'invitee.rescheduled':
        await handleInviteeRescheduled(eventData);
        break;
      default:
        console.log(`Unhandled event: ${event}`);
    }

    res.status(200).send();

  } catch (error) {
    console.error('Calendly webhook error:', error);
    res.status(500).send();
  }
});

async function handleInviteeCreated(eventData) {
  const { event, invitee } = eventData;

  try {
    // Check if this is a VAI-created booking
    const vaiMetadata = await getVAIMetadata(event.uri);
    
    if (vaiMetadata) {
      // Update VAI system with booking confirmation
      await fetch(`${process.env.VAI_API_URL}/calendly/confirmed`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.VAI_API_KEY}`
        },
        body: JSON.stringify({
          booking_id: event.uri.split('/').pop(),
          conversation_id: vaiMetadata.conversation_id,
          invitee_email: invitee.email,
          invitee_name: invitee.name,
          start_time: event.start_time,
          end_time: event.end_time,
          event_type: vaiMetadata.event_type,
          timestamp: new Date().toISOString()
        })
      });

      // Send confirmation to user if needed
      await sendUserConfirmation(invitee, event, vaiMetadata);
    }

    // Log the event for analytics
    await logCalendlyEvent('invitee_created', eventData);

  } catch (error) {
    console.error('Error handling invitee created:', error);
  }
}

async function handleInviteeCanceled(eventData) {
  const { event, invitee } = eventData;

  try {
    // Check if this is a VAI-related booking
    const vaiMetadata = await getVAIMetadata(event.uri);
    
    if (vaiMetadata) {
      // Update VAI system with cancellation
      await fetch(`${process.env.VAI_API_URL}/calendly/canceled`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.VAI_API_KEY}`
        },
        body: JSON.stringify({
          booking_id: event.uri.split('/').pop(),
          conversation_id: vaiMetadata.conversation_id,
          invitee_email: invitee.email,
          cancel_reason: invitee.cancel_reason,
          timestamp: new Date().toISOString()
        })
      });

      // Schedule follow-up if needed
      await scheduleFollowUp(invitee, vaiMetadata);
    }

    await logCalendlyEvent('invitee_canceled', eventData);

  } catch (error) {
    console.error('Error handling invitee canceled:', error);
  }
}

async function handleInviteeRescheduled(eventData) {
  const { event, invitee, old_event } = eventData;

  try {
    const vaiMetadata = await getVAIMetadata(event.uri);
    
    if (vaiMetadata) {
      // Update VAI system with reschedule
      await fetch(`${process.env.VAI_API_URL}/calendly/rescheduled`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.VAI_API_KEY}`
        },
        body: JSON.stringify({
          booking_id: event.uri.split('/').pop(),
          conversation_id: vaiMetadata.conversation_id,
          old_start_time: old_event.start_time,
          new_start_time: event.start_time,
          new_end_time: event.end_time,
          invitee_email: invitee.email,
          timestamp: new Date().toISOString()
        })
      });
    }

    await logCalendlyEvent('invitee_rescheduled', eventData);

  } catch (error) {
    console.error('Error handling invitee rescheduled:', error);
  }
}

async function getVAIMetadata(bookingUri) {
  try {
    const response = await fetch(`${process.env.VAI_API_URL}/calendly/metadata/${bookingUri.split('/').pop()}`, {
      headers: {
        'Authorization': `Bearer ${process.env.VAI_API_KEY}`
      }
    });

    if (response.ok) {
      return await response.json();
    }

    return null;
  } catch (error) {
    console.error('Error getting VAI metadata:', error);
    return null;
  }
}

async function sendUserConfirmation(invitee, event, metadata) {
  try {
    // Send personalized confirmation email
    await fetch(`${process.env.VAI_API_URL}/email/send`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.VAI_API_KEY}`
      },
      body: JSON.stringify({
        to: invitee.email,
        subject: `Confirmare programare: ${event.name}`,
        template: 'calendly_confirmation',
        data: {
          event_name: event.name,
          start_time: new Date(event.start_time).toLocaleString('ro-RO'),
          end_time: new Date(event.end_time).toLocaleString('ro-RO'),
          meeting_url: event.location?.location_url,
          conversation_context: metadata.notes,
          agent_type: metadata.agent_type
        }
      })
    });
  } catch (error) {
    console.error('Error sending user confirmation:', error);
  }
}

async function scheduleFollowUp(invitee, metadata) {
  try {
    // Schedule follow-up conversation if cancellation was due to scheduling issues
    if (invitee.cancel_reason && invitee.cancel_reason.includes('time')) {
      await fetch(`${process.env.VAI_API_URL}/followup/schedule`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.VAI_API_KEY}`
        },
        body: JSON.stringify({
          user_email: invitee.email,
          conversation_id: metadata.conversation_id,
          followup_type: 'reschedule_assistance',
          scheduled_for: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 hours later
          message: 'Am observat că ai anulat programarea. Pot să te ajut să găsim un moment mai convenabil?'
        })
      });
    }
  } catch (error) {
    console.error('Error scheduling follow-up:', error);
  }
}

async function logCalendlyEvent(eventType, eventData) {
  try {
    await fetch(`${process.env.VAI_API_URL}/analytics/log`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.VAI_API_KEY}`
      },
      body: JSON.stringify({
        event_type: eventType,
        platform: 'calendly',
        data: eventData,
        timestamp: new Date().toISOString()
      })
    });
  } catch (error) {
    console.error('Error logging Calendly event:', error);
  }
}

// API endpoint for VAI to schedule events
app.post('/api/calendly/schedule', async (req, res) => {
  try {
    const result = await calendlyIntegration.scheduleEventFromVAI(req.body);
    res.json(result);
  } catch (error) {
    console.error('Error scheduling event:', error);
    res.status(500).json({ error: 'Failed to schedule event' });
  }
});

// API endpoint to get upcoming events
app.get('/api/calendly/upcoming/:userEmail', async (req, res) => {
  try {
    const { userEmail } = req.params;
    const { days = 7 } = req.query;
    
    const events = await calendlyIntegration.getUpcomingEvents(userEmail, parseInt(days));
    res.json({ success: true, events });
  } catch (error) {
    console.error('Error getting upcoming events:', error);
    res.status(500).json({ error: 'Failed to get events' });
  }
});

const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
  console.log(`Calendly webhook server running on port ${PORT}`);
});

Ghid de Configurare

1

Creează Cont Calendly

Înregistrează-te și configurează tipurile de evenimente necesare.

calendly.com → Event Types → Create New
2

Generează API Key

Obține cheia API din setările de integrare Calendly.

3

Configurează Webhooks

Setează webhook-uri pentru evenimente de creare și anulare.

4

Testează Integrarea

Verifică programarea automată și notificările webhook.

Beneficii

Programare Simplificată

Elimină back-and-forth pentru stabilirea întâlnirilor.

Disponibilitate Real-time

Sincronizare instantanee a calendarului.

Automatizare Completă

Fluxuri personalizate de la programare la follow-up.

Analytics Detaliate

Rapoarte despre eficiența programărilor.

Pregătit pentru Calendly?

Integrează VAI Portal cu Calendly pentru a automatiza programarea întâlnirilor și a-ți optimiza calendarul inteligent.