DEV Community

Cover image for Replaying events from SavvyCal into your webhook with Elixir and HTTPoison
Byron Salty
Byron Salty

Posted on

Replaying events from SavvyCal into your webhook with Elixir and HTTPoison

This post will focus on a specific problem solution around the use of SavvyCal for developers but each step will use standard tech (for Elixir developers) and should be understandable and generalizable to other problems.

Context

For some context, SavvyCal is a calendar booking application that allows users to publicly post availability windows and allow sign-ups. When a user books a session it goes through their workflow and ultimately ends up on whichever calendar they already use. For example, it allows anyone to book time with me for a coffee chat that will then show up on my existing Google calendar. (Try it! https://savvycal.com/byronsalty/coffee)

Problem

While Savvy is rather full featured in their domain of calendar booking, you may want to use a webbhook to be notified of events in other systems and build other integrations.

Let's say every time someone books you for an event you want to store their name and email in a "contacts" list that you setup. After you create that it's going to work perfectly (of course!) going forward but what about all of the bookings that happened before the webhook was operational?

Let's figure out how to replay all the events that happened before the webhook was running by manually sending them back into the webhook

Outline

  1. Connect to SavvyCal API to get events to "replay"
  2. Create an Elixir project to act as our replay engine.
  3. Convert API results into new posts to our webhook
  4. Success

1. Connect to SavvyCal API to get events to "replay"

Savvy allows for two types of integrations - apps and personal use. We are going to use the much simpler "personal use" route.

Go to:
Savvy Cal > Settings > Developers > Personal Tokens > Create

Create one and write down the values that should look like:

pt_01GSJPE5R6N<snip>
pt_secret_d69cc128<snip>
Enter fullscreen mode Exit fullscreen mode

SavvyCal API Documentation

Now let's test it using curl.

The me api will give you some basic info on yourself:

curl -H 'Authorization: Bearer <private key>' https://api.savvycal.com/v1/me
Enter fullscreen mode Exit fullscreen mode

We will use the events api to get the events that we want to replay:

curl https://api.savvycal.com/v1/events \
     -H 'Authorization: Bearer <private key>' \
     -H 'Content-Type: application/json'
Enter fullscreen mode Exit fullscreen mode

So far so good?

2. Create an Elixir project to act as our replay engine.

We'll use mix to generate a base project called replay.

Create project:

$ mix new replay
$ cd replay
Enter fullscreen mode Exit fullscreen mode

Add HTTPoison and Poison dependencies for http calls and json manipulation respectively:

# edit mix.exs

defp deps do
[
    {:httpoison, "~> 1.8"},
    {:poison, "~> 5.0"}
]
end
Enter fullscreen mode Exit fullscreen mode

Install the dependencies:

mix deps.get
mix deps.compile
Enter fullscreen mode Exit fullscreen mode

Test that everything is working so far:

$ iex -S mix

iex> response = HTTPoison.get!("http://ip.jsontest.com/")
%HTTPoison.Response{
  status_code: 200,
  ...
}

iex> json = Poison.decode!(response.body)
%{"ip" => "..."}

Enter fullscreen mode Exit fullscreen mode

That's all the setup. Now let's put in the replay code.

3. Use API to get entries that we'll send to webhook

Lets a new function that we'll call from the command line to actually run our project.

Edit lib/replay.ex and add a start function:

  def start do
    IO.inspect("Starting Replay...")
  end
Enter fullscreen mode Exit fullscreen mode

Testing that everything works so far:

$ mix run -e "Replay.start()"
Compiling 1 file (.ex)
"Starting Replay..."
Enter fullscreen mode Exit fullscreen mode

Now let's add the HTTPoison call in to retrieve events. Note that we're going to pass in period=all because the API defaults to only upcoming events.

  # adding to lib/replay.ex > start()

  savvy_api_url = "https://api.savvycal.com/v1/events?limit=100&period=all"



  headers = [
    {"Authorization", "Bearer <add private key here>"},
    {"Content-type", "application/json"}
  ]

  response = HTTPoison.get!(savvy_api_url, headers)

  json = Poison.decode!(response.body)
  entries = json["entries"]
Enter fullscreen mode Exit fullscreen mode

Send those entries over to the webhook url that we have setup with SavvyCal:

  # continuing in lib/replay.ex > start()
  webook_url = "<enter your webhook url>"
  webhook_headers = [{"Content-type", "application/json"}]

  Enum.map(entries, fn entry ->
    body = Poison.encode!(%{
      type: "event.created",
      payload: entry
    })

    {:ok, _} = HTTPoison.post(webook_url, body, webhook_headers)
  end)
Enter fullscreen mode Exit fullscreen mode

Note: We are going to send these events through our webhook as if they were new events (See: event.created).

IMPORTANT: Make sure your webhook doesn't create duplicate results. In general, you want your code to be idempotent so handling this is best practice in any case and will allow you to rerun this replay as many times as necessary.

4. Success!

For fun, let's add in some timing and then run the whole thing. I bumped the number of entries up to the max at 100. Let's see how it goes.

We could just run our start() function now but I want to get some timing so I'll add a new timed() function that will then call our start() and wrap it with timing like this:

  #adding to lib/replay.ex > timed()
  def timed do
    {micro_secs, results} = :timer.tc( fn ->
      start()
    end)
    IO.inspect(results, label: "Num of events replayed")
    IO.inspect(micro_secs, label: "micro seconds")
  end
Enter fullscreen mode Exit fullscreen mode

Results?

$ mix run -e "Replay.timed()"
Compiling 1 file (.ex)
"Starting Replay..."
Num of events replayed: 100
micro seconds: 6613082
Enter fullscreen mode Exit fullscreen mode

6.6 seconds to replay 100 events sounds pretty good to me.

Top comments (0)