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
Tipuri de Evenimente Automate
Sales Demo
Demonstrație produs pentru clienți potențiali
Technical Support
Sesiune de suport tehnic personalizat
Strategy Call
Discuție strategică despre proiecte
Quick Consultation
Consultanță rapidă și întrebări generale
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
Creează Cont Calendly
Înregistrează-te și configurează tipurile de evenimente necesare.
calendly.com → Event Types → Create NewGenerează API Key
Obține cheia API din setările de integrare Calendly.
Configurează Webhooks
Setează webhook-uri pentru evenimente de creare și anulare.
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.