Being able to create and read webhooks is critical, as it ensures that our applications won’t waste time on unnecessary server requests. With the use of Kotlin, we will delve into the exploration of webhooks. We’re going to learn how to use Kotlin to read and create Google Webhooks.
Webhooks are notifications triggered by specific events, such as receiving an email, opening a link within an email, creating or deleting an event, and more. They are crucial as they automatically inform our applications of significant occurrences without the necessity for periodic information retrieval at set intervals.
Why are Webhooks important?
The server initiates a webhook and sends it to your application without requiring your application to explicitly request it Instead of making multiple requests, your application can simply wait until it receives a webhook. This not only enhances the efficiency of applications but, more importantly, accelerates their processing speed.
In scenarios like sending an email or creating an event, it becomes crucial to ascertain whether the recipient opened the message, clicked within the messages, or modified or deleted an event. Having access to this information is instrumental in making informed decisions.
We’re going to create Google Webhooks to interact with our Google Calendar and the Kotlin programming language.
Google Pub/Sub for message sync
We can use Google Pub/Sub to sync Gmail messages between Google and Nylas in real time. While this is not mandatory, is highly recommended.
Here are the steps as detailed in our documentation.
Creating a Reading Webhooks Application
Contrary to what you might believe, before creating a webhook, it’s essential to have the ability to read it. This might seem counterintuitive, but it makes sense in practice. When we initiate the creation of a webhook, Nylas needs to verify the existence of a valid account and the validity of the creation request. Without this verification, anyone could create webhooks indiscriminately.
This is why the first step is to create the reading application.
We’ll begin by establishing a new Kotlin project, which we’ll name kotlin-webhooks.
// Import Nylas packages
import com.nylas.NylasClient
import com.nylas.models.FindEventQueryParams
// Import Spark and Jackson packages
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import spark.template.mustache.MustacheTemplateEngine;
import com.nylas.models.When
import spark.ModelAndView
import spark.kotlin.Http
import spark.kotlin.ignite
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.net.URLEncoder
import java.text.SimpleDateFormat
data class Webhook_Info(
var id: String,
var date: String,
var title: String,
var description: String,
var participants: String,
var status: String
)
var array: Array<Webhook_Info> = arrayOf()
object Hmac {
fun digest(
msg: String,
key: String,
alg: String = "HmacSHA256"
): String {
val signingKey = SecretKeySpec(key.toByteArray(), alg)
val mac = Mac.getInstance(alg)
mac.init(signingKey)
val bytes = mac.doFinal(msg.toByteArray())
return format(bytes)
}
private fun format(bytes: ByteArray): String {
val formatter = Formatter()
bytes.forEach { formatter.format("%02x", it) }
return formatter.toString()
}
}
fun addElement(arr: Array<Webhook_Info>, element: Webhook_Info): Array<Webhook_Info> {
val mutableArray = arr.toMutableList()
mutableArray.add(element)
return mutableArray.toTypedArray()
}
fun dateFormatter(milliseconds: String): String {
return SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(Date(milliseconds.toLong() * 1000)).toString()
}
fun main(args: Array<String>) {
val http: Http = ignite()
// Initialize Nylas client
val nylas: NylasClient = NylasClient(
apiKey = System.getenv("V3_TOKEN")
)
http.get("/webhook") {
request.queryParams("challenge")
}
http.post("/webhook") {
print("Hello from Webhook")
val mapper = jacksonObjectMapper()
val model: JsonNode = mapper.readValue<JsonNode>(request.body())
if(model["data"]["object"]["calendar_id"].textValue().equals(System.getenv("CALENDAR_ID"), false)){
if(Hmac.digest(request.body(), URLEncoder.encode(System.getenv("CLIENT_SECRET"), "UTF-8")) ==
request.headers("X-Nylas-Signature").toString()){
val eventquery = FindEventQueryParams(System.getenv("CALENDAR_ID"))
System.out.println(model["data"]["object"]["id"].textValue())
val myevent = nylas.events().find(System.getenv("GRANT_ID"), eventId = model["data"]
["object"]["id"].textValue(), queryParams = eventquery)
var participants: String = ""
for (participant in myevent.data.participants){
participants = "$participants;${participant.email.toString()}"
}
var event_datetime: String = ""
when(myevent.data.getWhen().getObject().toString()) {
"DATESPAN" -> {
val datespan = myevent.data.getWhen() as When.Datespan
event_datetime = datespan.startDate.toString()
}
"TIMESPAN" -> {
val timespan = myevent.data.getWhen() as When.Timespan
val startDate = dateFormatter(timespan.startTime.toString())
val endDate = dateFormatter(timespan.endTime.toString())
event_datetime = "From $startDate to $endDate"
}
"DATE" -> {
val datespan = myevent.data.getWhen() as When.Date
event_datetime = datespan.date
}
}
participants = participants.drop(1)
array = addElement(array, Webhook_Info(myevent.data.id, event_datetime.toString(),
myevent.data.title.toString(), myevent.data.description.toString(),
participants, myevent.data.status.toString()))
}
}
""
}
http.get("/") {
val model = HashMap<String, Any>()
model["webhooks"] = array
MustacheTemplateEngine().render(
ModelAndView(model, "show_webhooks.mustache")
)
}
}
We need a template to display the webhooks. Create a templates folder inside the resources folder, then create a new file and call it show_ids.mustache:
Everything is prepared, but the application must be deployed to be accessible from the outside.
Deploying the Reading Webhooks Application
In the past, Heroku would have been a suitable choice, however, its free tier is no longer available. Therefore, it’s time to explore better alternatives.
One such alternative is Qoddi, which requires a card for verifying a newly created account—this can be either a debit or credit card.
Firstly, we should upload our source code to GitHub, place it in a project named kotlin-web-hooks (or your chosen name), and include two essential files: Procfile and system.properties.
Now, we need to move into Qoddi. When we log in, we will be presented with this screen:
If we select any of the tiles, we will be redirected to the documentation for the corresponding programming language or environment. We need to press New to create a new project.
We’re going to name it kotlin-read-webhooks, we don’t need a datastore, its type is going to be web server and the app size XS:
Upon clicking on the Next button, we need to choose Github, as we’re going to upload our code from there:
We need to allow Qoddi to access our repository, so we need to make sure that it’s public:
We need to select our repository and then simply continue with the next screen:
Once we make sure that everything is correct, we need to press the Launch My App button:
Qoddi will start deploying our application, and this might take a couple of minutes:
We need to click on Activate SSL so that our application gets accessed as an https application:
Also, we’re not going to use .env files here, so we need to create Environment Variables:
Port and Host comes by default, so we need to add the rest:
Leave CLIENT_SECRET empty (or set it to another value) for now, as we will update it later.
One detail that can be easily overlooked is specifying the Container Port, which, for a Spark application is 4567. Additionally, ensure that Redirect Http to Https is activated:
After launching the application, grab the URL and open it in a web browser. It will be empty, as we haven’t defined any webhooks yet:
Creating the Create Webhooks Application
Now that our Read Webhooks application is up and running, it’s time to create the Create Webhooks application.
Let’s create a project called kotlin-create-webhooks, with the following pom.xml file:
The application will call the main file CreateWebhooks.kt:
// Import Nylas packages
import com.nylas.NylasClient
import com.nylas.models.CreateWebhookRequest
import com.nylas.models.Response
import com.nylas.models.Webhook
import com.nylas.models.WebhookTriggers
import com.nylas.resources.Webhooks
// Import DotEnv to handle .env files
import io.github.cdimascio.dotenv.dotenv
// The main function
fun main(args: Array<String>){
// Load our env variable
val dotenv = dotenv()
// Initialize Nylas client
val nylas: NylasClient = NylasClient(
apiKey = dotenv["V3_TOKEN"]
)
// Which triggers are we're going to use
val triggersList: List<WebhookTriggers> = listOf(WebhookTriggers.EVENT_CREATED)
// Create the webhook triggers request
val webhookRequest: CreateWebhookRequest = CreateWebhookRequest(triggersList, "
<YOUR_WEBHOOK_URL>/webhooks", "My first webhook",dotenv["GRANT_ID"])
// Create the webhook and return the response
val webhook: Response<WebhookWithSecret> =
Webhooks(nylas).create(webhookRequest)
// Print the webhook information
println(webhook.data)
}
Running the Create Webhooks Application
Run these commands on the terminal window to execute the application: