How to retrieve booking information using Calendly API integration

I’m trying to fetch appointment details from Calendly using their API but keep getting 401 authentication errors. I’ve looked at the API docs and tried different approaches but something is still wrong with my setup.

Here’s my current implementation:

document.addEventListener("message", function (e) {
    if (e.origin.includes("calendly.com")) {
        const msgData = e.data;
        if (msgData.event && msgData.event === "calendly.event_scheduled") {
            console.log("Booking confirmed:", msgData.payload);
            
            const bookingUri = msgData.payload.event.uri;
            const attendeeUri = msgData.payload.invitee.uri;
            
            getCalendlyBookingInfo(bookingUri, attendeeUri);
        }
    }
});

async function getCalendlyBookingInfo(bookingUri, attendeeUri) {
    try {
        const bookingId = bookingUri.split("/").pop();
        const attendeeId = attendeeUri.split("/").pop();
        
        const apiToken = 'Bearer myPersonalAccessToken123';
        
        const requestHeaders = {
            'Content-Type': 'application/json',
            'Authorization': apiToken
        };
        
        const bookingResponse = await fetch(`https://api.calendly.com/scheduled_events/${bookingId}`, {
            method: 'GET',
            headers: requestHeaders
        });
        
        const attendeeResponse = await fetch(`https://api.calendly.com/scheduled_events/${bookingId}/invitees/${attendeeId}`, {
            method: 'GET', 
            headers: requestHeaders
        });
        
        const bookingInfo = await bookingResponse.json();
        const attendeeInfo = await attendeeResponse.json();
        
        if (!attendeeInfo || !attendeeInfo.resource) {
            console.error("Missing booking info:", attendeeInfo);
            return;
        }
        
        const appointmentTime = attendeeInfo.resource.scheduled_at;
        const displayTime = new Date(appointmentTime).toLocaleTimeString();
        const displayDate = new Date(appointmentTime).toLocaleDateString();
        
        console.log(`Appointment date: ${displayDate}`);
        console.log(`Appointment time: ${displayTime}`);
        
        document.querySelector('.booking-date').textContent = displayDate;
        document.querySelector('.booking-time').textContent = displayTime;
        
    } catch (err) {
        console.error("Failed to get booking data:", err);
    }
}

I’ve tried creating personal access tokens but still get 401 errors every time. What am I missing here?

Had the same issue when I started with Calendly’s API. You’re probably using a placeholder token instead of a real one. Go to your Calendly account settings → Developer Tools and generate a Personal Access Token. Copy the whole thing - it’s long and easy to cut off by mistake. Also check if your Calendly plan actually allows API access. Some basic plans don’t. Make sure the token matches the account that owns the events you’re trying to reach. I wasted hours debugging this before realizing I was using a token from the wrong account. Once you get the right token, your code should work.

Check your token scopes first. When you created the personal access token, make sure you enabled the right scopes for reading scheduled events and invitee data. I hit this exact issue - my token worked but couldn’t access the endpoints I was calling. Rate limiting also caught me off guard. Calendly’s API has strict limits, so if you’re making multiple calls quickly while testing, you might hit those limits before authentication even processes. Add some delay between requests or check the response headers for rate limit info. Also verify your account actually owns those events. The API throws 401 errors if you try accessing events from other users, even with a valid token. This happens constantly in team setups where events might be under different user accounts than you expect.

Been there with those 401 errors. The token issue mentioned above is spot on, but there’s another common gotcha.

Your URI parsing might be off. Calendly URIs from webhooks sometimes include the full path, not just the ID you need for API calls. Try logging what you’re actually extracting:

console.log('Full booking URI:', bookingUri);
console.log('Extracted ID:', bookingUri.split("/").pop());

Also, some Calendly webhook URIs need different handling depending on whether they come from embedded widgets or direct bookings.

Honestly though, I stopped wrestling with Calendly’s API quirks ages ago. Now I just use Latenode to handle all this stuff. It has native Calendly integration that handles the authentication and URI parsing automatically. You connect your Calendly account once and it pulls booking data without any token headaches.

I set up workflows there that grab new bookings and push the data wherever I need it. Way cleaner than debugging API calls manually.

Your webhook listener looks good, but you’re probably hitting a timing issue. Calendly fires the webhook right when someone books, but the data isn’t always ready in their API yet.

Hit this exact problem last year. The booking gets created but there’s usually a few seconds before you can actually fetch it. Just add a delay before your API calls:

if (msgData.event && msgData.event === "calendly.event_scheduled") {
    console.log("Booking confirmed:", msgData.payload);
    
    // Wait a bit for the data to be available
    setTimeout(() => {
        const bookingUri = msgData.payload.event.uri;
        const attendeeUri = msgData.payload.invitee.uri;
        getCalendlyBookingInfo(bookingUri, attendeeUri);
    }, 3000);
}

Check your personal access token permissions too. When you created it, did you select “Read” access for scheduled events and invitees?

Also make sure you’re testing with events that actually exist. Old webhook data or deleted test URIs will throw auth errors even with valid tokens.

Btw, the webhook payload usually has most of what you need anyway. You might not even need those API calls for basic booking details.

Double-check you’re not mixing up URI formats - Calendly returns different structures depending on how the booking came in. Sometimes it’s just the UUID, other times it’s the full calendly.com/scheduled_events/uuid path. Your split(‘/’).pop() might be grabbing the wrong part. Also, personal access tokens expire and Calendly doesn’t always give you a clear warning when that happens.