- 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 PHP, LeafPHP, Koyeb, and Bruno, we will delve into the exploration of 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.
A webhook is initiated by the server and sent to your application without the need for 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 message was opened, if there was a click within the messages, or if an event was modified or deleted. Having access to this information is instrumental in making informed decisions.
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.
As indicated in the title, we will utilize a distinct set of tools to create and read webhooks like PHP, Koyeb and Bruno:
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.
First, we will install Composer, a package manager for PHP, similar to npm, pip or gem.
To install it we need first to download the installer rename it to composer-setup.php and then run it on the terminal window:
$ php composer-setup.php --install-dir=~/.local/bin --filename=composer
Now we can call it from the terminal window:
$ composer
Once that’s installed, we’re going to create a folder named php-read-webhooks. Open your terminal window and navigate to that folder.
While plain PHP is sufficient for creating web applications, there are instances where a framework is beneficial for achieving better organization and structure in the code. For this specific project, I have opted for LeafPHP, a straightforward and elegant PHP micro-framework.
We can install it with Composer, using the following command:
$ composer require leafs/leaf
As we’ll require a frontend template system, we’re going to use Blade, a Lavarel template engine.
$ composer require leafs/blade
Now, something unexpected arises – a twist you might not have anticipated. I certainly didn’t. Here’s how the story unfolds:
I intended to utilize sessions to store each webhook call, avoiding reliance on any database since PHP lacks Data Classes. Using Globals was ruled out. During testing, everything appeared fine until I observed that the GET section generated one session, while the POST section created another. This posed a significant issue, as two distinct sessions couldn’t communicate. I attempted to use cookies to preserve and reuse the session ID value but encountered the problem of generating two different cookies.
To resolve this, I sought a solution to store the session ID in a plain file for easy retrieval. Simple-TxtDb came to the rescue – a single class capable of writing and reading plain files with zero dependencies. I submitted a pull request (which was merged 🥳) to address an issue related to dynamic properties.
One small change is to change this line on the txtdb.class.php file:
private $_db_dir = __DIR__ . '/db/';
For this one:
private $_db_dir = __DIR__ . '/database/';
Thinking back, the browser is one session and the webhook coming from Nylas is another, so it’s obvious that they will be different. Also, using sessions makes things a bit cumbersome, so let’s just use the plain text database and be done with it.
Now, let’s proceed to create our index.php file, which will manage all the webhook-related tasks.
<?php // Load dependencies require __DIR__ . '/vendor/autoload.php'; require("txtdb.class.php"); use Leaf\Blade; // Declare global objects $app = new Leaf\App(); $blade = new Blade('views', 'storage/cache'); $db = new TxtDb(); // Class to hold Webhooks information class Webhook { public $id; public $date; public $subject; public $from_email; public $from_name; } // Main page $app->get('/', function() use($app, $blade, $db){ // Check our text db for a recorded session $webhooks = $db->select('webhooks'); // Display it echo $blade->render('webhooks', ['webhooks' => $webhooks]); }); // This will be called to validate our webhook $app->get('/webhook', function () use($app) { $challenge = request()->get('challenge'); //Return the challenge echo "$challenge"; }); // Page for the Webhook to send the information to $app->post('/webhook', function () use($app, $db) { // Read the webhook information $json = file_get_contents('php://input', true); // Decode the json $data = json_decode($json); $is_genuine = verify_signature(file_get_contents('php://input'), mb_convert_encoding(getenv('CLIENT_SECRET'), 'UTF-8', 'ISO-8859-1'), request()->headers('X-Nylas-Signature')); # Is it really coming from Nylas? if(!$is_genuine){ response()->status(401)->plain('Signature verification failed!'); } error_log("Time to save the webhook"); $webhook = new Webhook(); // Fetch all the webhook information $webhook->id = $data->data->object->id; $date = $data->data->object->date; $date = new DateTime("@$date"); $date = $date->format('Y-m-d H:i:s'); $webhook->date = $date; $webhook->subject = $data->data->object->subject; $webhook->from_email = $data->data->object->from[0]->email; $webhook->from_name = $data->data->object->from[0]->name; // Store the webhook information into the session $db->insert("webhooks", ["webhook" => $webhook]); error_log("Webhook was saved"); // Return success back to Nylas response()->status(200)->plain('Webhook received'); exit(); }); // Function to verify the signature function verify_signature($message, $key, $signature){ $digest = hash_hmac('sha256', $message, $key); return(hash_equals($digest, $signature)); } // Run the app $app->run(); ?>
We also need to create a folder named views and inside a file named webhooks.blade.php:
<!Doctype html> <html> <head> <script src="https://cdn.tailwindcss.com"></script> <title>Webhooks</title> </head> <body> <h1 class="text-4xl font-bold dark:text-black bg-green-600 border-green-600 border-b p-4 m-4 rounded grid place-items-center">Webhooks</h1> <table style="width:100%"> <tr class="bg-green-600 border-green-600 border-b p-4 m-4 rounded"> <th>Id</th> <th>Date</th> <th>Subject</th> <th>From Email</th> <th>From Name</th> </tr> @if (!is_null($webhooks)) @foreach ($webhooks as $webhook["webhook"]) @foreach ($webhook["webhook"] as $webhook_elem) <tr class="bg-white-600 border-green-600 border-b p-4 m-4 rounded"> <td><p class="text-sm font-semibold">{{$webhook_elem["id"]}}</p></td> <td><p class="text-sm font-semibold">{{$webhook_elem["date"]}}</p></td> <td><p class="text-sm font-semibold">{{$webhook_elem["subject"]}}</p></td> <td><p class="text-sm font-semibold">{{$webhook_elem["from_email"]}}</p></td> <td><p class="text-sm font-semibold">{{$webhook_elem["from_name"]}}</p></td> </tr> @endforeach @endforeach @endif </table> </body> </html>
To ensure that GitHub creates the necessary folders when we upload our project, let’s create a new folder with a dummy file inside. Create a folder named database, containing a file named “dummy” with a simple text like “DO NOT DELETE.”.
The folder structure should look like this:
BTW, storage will be generated when we run the project locally.
Now that we have all the source code in place, it’s time to run our application. Simply, run the following command on the terminal window:
$ php -S localhost:8000
We can now, open our favourite browser:
Initially, we must make our application accessible to the outside world, but we don’t intend to host it anywhere at the moment since we are still in the testing phase.
Keep in mind that as we’re going to test locally, we’re not going to authenticate that the webhook is coming from Nylas, so we need to comment out lines 43 to 49.
We’re going to use Bruno, an amazing tool to test APIs. It stores everything on your filesystem and the majority of features are free and open source. PHP and Bruno work perfectly together. Later, adding LeafPHP and Koyeb will make our webhooks application a great tool.
We can install it with Homebrew:
$ brew install bruno
When we launch it, we need to create a collection, so we can create one and name it Nylas-V3.
After this, we can add calls, in our case http://localhost:8000/webhook
On the Body tab, we’re going to use this Webhook response template:
{ "specversion": "1.0", "type": "message.created", "source": "/google/emails/realtime", "id": "xxx", "time": 1709379109, "webhook_delivery_attempt": 1, "data": { "application_id": "xxx", "object": { "body": "Email Body", "created_at": 1709379093, "date": 1709379093, "folders": [ "UNREAD", "IMPORTANT", "CATEGORY_PERSONAL", "INBOX" ], "from": [ { "email": "[email protected]", "name": "Alvaro Tejada Galindo" } ], "grant_id": "xxx", "id": "xxx", "object": "message", "snippet": "Yup!", "starred": false, "subject": "Hey! Ho!", "thread_id": "18dfeef3637c1813", "to": [ { "email": "xxx", "name": "Blag aka Alvaro Tejada Galindo" } ], "unread": true } } }
With our application running, all that’s required is to press Enter and let Bruno do its job.
Upon opening our reader application, we will observe the successful recording of the webhook:
Awesome, now that we know it’s working, we can uncomment lines 43 to 49.
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 Koyeb, which is completely free to up to one service. The next one will cost $0.0022/hr on the Eco plan.
Firstly, we should upload our source code to GitHub, place it in a project named php-read-webhooks (or your chosen name), and include two essential files: Procfile and .htaccess.
This is Procfile:
web: heroku-php-apache2
And this is .htaccess:
<IfModule mod_rewrite.c> Options +FollowSymlinks Options +Indexes RewriteEngine on # if your app is in a subfolder # RewriteBase /my_app/ # test string is a valid files RewriteCond %{SCRIPT_FILENAME} !-f # test string is a valid directory RewriteCond %{SCRIPT_FILENAME} !-d RewriteRule ^(.*)$ index.php?uri=/$1 [NC,L,QSA] # with QSA flag (query string append), # forces the rewrite engine to append a query string part of the # substitution string to the existing string, instead of replacing it. </IfModule>
Your folder structure should look like this:
Now, we need to move into Koyeb. Upon logging in, we will encounter this screen:
We’re going to create one environmental variable. Port comes by default, so we need to add the CLIENT_SECRET.
We need to press Deploy once we finished. Koyeb will start deploying our application, and this might take a couple of minutes.
Once ready, we will deploy our application and access it.
Now that our Read Webhooks application is up and running, it’s time to create the Webhooks application. We going to create a folder named php-create-folder. Run the following Composer command:
$ composer require vlucas/phpdotenv
This command will install phpdotenv, which will help us use .env files in our application.
If we had previously executed this command in a higher-level folder, it would prompt us to use it as a template. We should decline, as we aim to generate a new one. This process will generate the files composer.json, composer.lock, and the vendor folder
Now, we can proceed by creating an “index.php” file:
<?php // Import your dependencies require_once('vendor/autoload.php'); // Load env variables $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->load(); // Define the headers $headr = array(); $headr[] = 'Accept: application/json'; $headr[] = 'Content-type: application/json'; $headr[] = 'Authorization: Bearer ' . $_ENV['V3_TOKEN']; // Array with information needed to create the webhook $data = array( 'description' => "My PHP Webhook", 'trigger_types' => array("message.created"), 'webhook_url' => "https://xxx.koyeb.app/webhook", 'notification_email_addresses' => array($_ENV['EMAIL']) ); // Call the webhooks endpoint $ch = curl_init( "https://api.us.nylas.com/v3/webhooks" ); // Encode the data as JSON $payload = json_encode( $data ); // Submit the Email information curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload ); // Submit the Headers curl_setopt( $ch, CURLOPT_HTTPHEADER, $headr); // We're doing a POST curl_setopt($ch, CURLOPT_POST, true); // Return response instead of printing. curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); // Send request. $result = curl_exec($ch); echo $result; // Close request curl_close($ch); ?>
We must create an .env file to store our credentials:
V3_TOKEN=<YOUR_V3_TOKEN>
Run these commands on the terminal window to execute the application:
$ php index.php
Now, copy the webhookSecret value and update the Client Secret environment variable.
Our application will display each new event created.
Utilizing PHP, LeafPHP, Koyeb and Bruno to create and read webhooks was a great experience.
Do you have any comments or feedback? Please use our forums 😎
Here’s the repo for Read PHP Webhooks and Create PHP 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.