- Products
- Solutions Use casesBy industry
- Developers
- Resources Connect
- Pricing
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.
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.
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.
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.
This will be our pom.xml file:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>kotlin-read-webhooks</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>consoleApp</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <kotlin.code.style>official</kotlin.code.style> <kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget> <main.class>WebhooksKt</main.class> </properties> <repositories> <repository> <id>mavenCentral</id> <url>https://repo1.maven.org/maven2/</url> </repository> </repositories> <build> <sourceDirectory>src/main/kotlin</sourceDirectory> <testSourceDirectory>src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>1.9.0</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.22.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass>${main.class}</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-test-junit5</artifactId> <version>1.9.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> <version>1.9.0</version> </dependency> <dependency> <groupId>com.nylas.sdk</groupId> <artifactId>nylas</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>io.github.cdimascio</groupId> <artifactId>dotenv-kotlin</artifactId> <version>6.4.1</version> </dependency> <dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-kotlin</artifactId> <version>1.0.0-alpha</version> </dependency> <dependency> <groupId>com.sparkjava</groupId> <artifactId>spark-template-mustache</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>com.github.spullara.mustache.java</groupId> <artifactId>compiler</artifactId> <version>0.9.4</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> <version>2.14.2</version> </dependency> <dependency> <groupId>io.github.cdimascio</groupId> <artifactId>dotenv-java</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency> </dependencies> </project>
Let’s create our main class called Webhooks.kt:
// 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:
<html> <head> <script src="https://cdn.tailwindcss.com"></script> <title>Webhooks</title> </head> <body> <div class="bg-green-600 border-green-600 border-b p-4 m-4 rounded w-2/5 grid place-items-center"> <br> <table style="width:100%"> <tr> <th>Id</th> <th>Date</th> <th>Title</th> <th>Description</th> <th>Participants</th> <th>Status</th> </tr> {{#webhooks}} <tr> <td><p class="font-semibold">{{id}}</p></td> <td><p class="font-semibold">{{date}}</p></td> <td><p class="font-semibold">{{title}}</p></td> <td><p class="font-semibold">{{description}}</p></td> <td><p class="font-semibold">{{participants}}</p></td> <td><p class="font-semibold">{{status}}</p></td> </tr> {{/webhooks}} </table> </div> </body> </html>
Everything is prepared, but the application must be deployed to be accessible from the outside.
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.
This is Procfile:
web: java -jar /workspace/target/kotlin-read-webhooks-1.0-SNAPSHOT-jar-with-dependencies.jar
And this is system.properties:
java.runtime.version=11 maven.version=3.9.4
Your folder structure should look like this:
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:
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:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>kotlin-create-webhooks</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>consoleApp</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <kotlin.code.style>official</kotlin.code.style> <kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget> <main.class>CreateWebhooksKt</main.class> </properties> <repositories> <repository> <id>mavenCentral</id> <url>https://repo1.maven.org/maven2/</url> </repository> </repositories> <build> <sourceDirectory>src/main/kotlin</sourceDirectory> <testSourceDirectory>src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>1.9.0</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.22.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.6</version> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> <configuration> <archive> <manifest> <mainClass>${main.class}</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-test-junit5</artifactId> <version>1.9.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> <version>1.9.0</version> </dependency> <dependency> <groupId>com.nylas.sdk</groupId> <artifactId>nylas</artifactId> <version>2.0.0-beta.1</version> </dependency> <dependency> <groupId>io.github.cdimascio</groupId> <artifactId>dotenv-kotlin</artifactId> <version>6.4.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency> </dependencies> </project>
It’s important that we create an .env file to store our credentials:
V3_TOKEN=<YOUR_V3_TOKEN> GRANT_ID=<YOUR_GRANT_ID> CALENDAR_ID=<YOUR_CALENDAR_ID>
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) }
Run these commands on the terminal window to execute the application:
$ mvn package $ java -jar target/kotlin-create-webhooks-1.0-SNAPSHOT-jar-with-dependencies.jar
Now, copy the webhookSecret value and update the Client Secret environment variable.
Our application will be able to display each new event created.
Using Google Webhooks with Kotlin is easy and powerful. Let’s see what can you create! 🥳
Do you have any comments or feedback? Please use our forums 😎
Want to fetch the repo? Here you go kotlin-read-webhooks 🥳
Don’t miss the action, join our LiveStream Coding with Nylas!
Blag aka Alvaro Tejada Galindo is a Senior Developer Advocate at Nylas. He loves learning about programming and sharing knowledge with the community. When he’s not coding, he’s spending time with his wife, daughter and son. He loves Punk Music and reading all sorts of books.