0

Im subscribing to a webhook that is listening for Calendly session creation (invitee.created) and session cancelation (invitee.canceled). When I get the data I update the relevant record in the database. This works fine for the most part as users would either create or cancel an event. However, when they reschedule an event, Calendly cancels the session and rebooks it with the new date which introduces a race condition bug.

some code for context:

// data is what I get from the webhook which is 
// an object containing information about the session 
// booked or cancellled. It looks something like this:

{
  created_at: '2022-07-06',
  created_by: 'https://api.calendly.com/users/3a1c8403',
  event: 'invitee.created',
  payload: {
    cancel_url: 'https://calendly.com/cancellations/058cb81f',
    created_at: '2022-07-06',
    email: 'xx@xxx.com',
    event: 'https://api.calendly.com/scheduled_events/7867fa63',
    first_name: null,
    last_name: null,
    name: 'xxxx',
    new_invitee: null,
    no_show: null,
    old_invitee: 'https://api.calendly.com/scheduled_events//invitees/283ba2b1-da59-4b2c',
    payment: null,
    questions_and_answers: [ [Object] ],
    reconfirmation: null,
    reschedule_url: 'https://calendly.com/reschedulings/058cb81f',
    rescheduled: false,
    routing_form_submission: null,
    status: 'active',
    text_reminder_number: null,
    timezone: 'Asia',
    tracking: {
      utm_campaign: null,
      utm_source: '158',
      utm_medium: null,
      utm_content: null,
      utm_term: null,
      salesforce_uuid: null
    },
    updated_at: '2022-07-06T16:53:23.191059Z',
    uri: 'https://api.calendly.com/scheduled_events/7867f'
  }
}


// then my logic:
const getSessionData = () => {
  axios
    .request({
      method: 'GET',
      url: `http://xxxx`,
      headers: {
        'Content-Type': 'application/json',
        Authorization: process.env.CALENDLY_API_KEY,
      },
    })



export default async (req, res) => {
  const body = (await buffer(req)).toString()
  const data = body ? JSON.parse(body) : null

  if(data) {
    switch (data?.event) {
      case 'invitee.canceled':
        const processCancellation = async () => {
          const cancelPromise = getSessionData(event)
          const cancelSessionData = await cancelPromise
    
          // update database with sessionData
          await supabase
            .from('booking')
            .update({
              // cancelSessionData...
            })
            .eq('id', sessionId)
        }
        processCancellation()
        break

      case 'invitee.created':
        const processBooking = async () => {
          const createPromise = getSessionData(event)
          const createSessionData = await createPromise
          await supabase
            .from('booking')
            .update({
              // createSessionData...
            })
            .eq('id', sessionId)
        }
        processBooking()
        break

      default:
        console.log(
          `Sorry, no data!`
        )
    }
  }
  res.send({ received: true })
}

How would I approach something like this?

Omar
  • 49
  • 1
  • 6

1 Answers1

0

Your code doesn't seem to have any race conditions on it, since it appears to be awaiting the completion of all promises before moving forward.

I think that your problem might be related to how Calendy calls your webhook, because you said that it first makes a call to cancel the old meeting, and then it makes another call to book a new one.

You cannot assume that these operations will be executed sequentially, since there may be cases when the re-book meeting request arrives before the cancel meeting request, because of the network conditions.

And even if they do arrive in the correct order, you probably have more than one application server running on your backend, so there is a possibility that the re-book meeting request gets processed before the cancel meeting request.

To solve this, you might want to check the type of event you receive on the payload: event: 'invitee.created',. Maybe Calendy sends a different type of event when the meeting is re-booked, so then you can ignore the cancelation request if that is the case.

However if you do this, you might not be able to cancel a meeting that was re-booked at least once. So you can also look at the updated_at timestamp that comes from Calendy on your database, you can ignore requests that are older than the current time stamp.

Also, be sure to never compare the timestamp of your server with the timestamp from Calendy, because the two might not be in sync.

Felipe
  • 6,312
  • 11
  • 52
  • 70