Engineering leaders are constantly seeking new ways to gain deeper insights into their teams’ productivity and better understand the overall health of their engineering projects. LinearB provides a wealth of resources for engineering insights and intelligence, and we’ve tailored our dashboards to be the most configurable solution in the industry. One critical component of this flexibility is the Incident API, which enables you to upload custom incident reports into LinearB to give you a more holistic picture of your software delivery management, specifically related to metrics like change failure rate and mean time to restore.
PagerDuty is one of the most prevalent incident management solutions on the market, and it offers a ton of flexibility for alerting, on-call management, and incident resolution. PagerDuty provides outgoing webhooks for incident services. However, the data sent from PagerDuty doesn’t match the format that the LinearB API expects. This guide will show you how to connect PagerDuty to the LinearB Incident API so you can accurately track your team’s engineering efficiency.
Overview
You'll need to set up a handful of services to connect PagerDuty to LinearB. We chose these services for this guide, but you can swap out most of the technologies in this list for your preferred solutions. This guide will show you how to orchestrate the following resources:
- PagerDuty - Generate incidents and transmit them to the ngrok server via webhooks for processing.
- Ngrok - Maintain a publicly accessible web server that receives webhooks from PagerDuty.
- Node.js - Transform the data from PagerDuty to a format compatible with LinearB.
- LinearB - Receive incident information via the API and make it available for tracking in dashboards.
Let’s get started!
Ngrok Setup
Ngrok is a popular ingress platform that makes it easy to set up web servers on your local machine that are publicly accessible via individualized URLs. Ngrok is an excellent tool for quickly testing and debugging new web services that need to integrate with external tools. We’ll use ngrok for this guide, but you can use any middleware solution or web server available. Whatever technology you use, ensure it can provide external API connections and run the code you need to transform the webhook data.
To start, create an ngrok account and follow the get started section of their dashboard to install and configure your local ngrok service. Once finished, you can then create a new ngrok ingress by running this command from your command line interface:
ngrok http 8080
You should now have a new ngrok ingress on port 8080. Take note of the first URL in the Forwarding section because you’ll need it later.
LinearB Setup
To use the LinearB Incident API, you need to create a LinearB API token and configure LinearB to accept API requests for the Incident API. Here’s how to do it:
- Generate a LinearB API Token
- Enable the API Integration by going to Company Settings -> Advanced Settings -> Incidents Detection, and click API Integration. Remember to click Save!
For more information about the LinearB Incident API, visit the help docs.
You should also set up service metrics to map incidents to repositories, we recommend giving services the same name in PagerDuty and LinearB. This feature is only available to LinearB business and enterprise accounts. If you have a free LinearB account, this guide will provide an alternative solution later.
PagerDuty Setup
In PagerDuty, ensure you have a service configured to create incident alerts. If you don’t, take a moment to create one now and configure it to your needs. Then, go to Integrations > Extensions, and click the New Extension button, and set the following parameters:
- Extension Type - Select “Generic Webhook”
- Name - Give the webhook a meaningful name.
- Service - Select the incident service you want this webhook to trigger for.
- URL - Your ngrok URL from earlier with “/webhook” appended to the end of it. For example: https://7881-67-22-7-13.ngrok-free.app/webhook
Node.js
The last part of this demo is some JavaScript code to listen for PagerDuty webhooks, transform the data to the format LinearB expects, and submit it to the LinearB Incident API to include it in your analytics.
Here is the full code example for this guide, we’ll dive into the details below.
const axios = require('axios');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 8080; // Port your local server is running on
const apiKey = process.env.API_KEY; // Your LinearB API key
const apiUrl = 'https://public-api.linearb.io/api/v1/incidents';
const headers = { // Set up the headers with the API token
'x-api-key': apiKey,
'Content-Type': 'application/json',
};
// Middleware to parse incoming JSON data
app.use(bodyParser.json());
// Define the webhook endpoint
app.post('/webhook', (req, res) => {
const webhookData = req.body; // Access the incoming webhook data
const incident = webhookData.messages[0].incident; // Access the properties of the PagerDuty incident object
let requestURL = apiUrl;
let requestData = {};
if (incident.status === 'triggered') { // Assign incident properties in the format LinearB expects
console.log("Creating new incident: ", incident.id)
requestData.provider_id = String(incident.id); // These properties are required
requestData.http_url = String(incident.html_url);
requestData.title = String(incident.title);
requestData.issued_at = String(incident.created_at);
requestData.services = [String(incident.service.name)];
} else if (incident.status === 'acknowledged') {
console.log("Updating existing incident: ", incident.id)
requestData.started_at = String(incident.updated_at);
requestURL += '/'+incident.id;
} else if (incident.status === 'resolved') {
console.log("Updating existing incident: ", incident.id)
requestData.ended_at = String(incident.resolved_at);
requestURL += '/'+incident.id;
}
console.log(requestData);
// The LinearB API expects PATCH requests to update existing incidents and POST requests to create new incidents.
if (incident.status === 'acknowledged' || incident.status === 'resolved') {
axios.patch(requestURL, requestData, { headers })
.then(response => {
console.log('Incident ${incident.id} has been updated.');
})
.catch(error => {
console.error('API error:', error.response.data);
});
} else if (incident.status === 'triggered') {
axios.post(requestURL, requestData, { headers })
.then(response => {
console.log('Update Successful');
})
.catch(error => {
console.error('API error:', error.response.data);
});
} else {
console.log("No action taken for incident: ", incident.id)
}
// Respond to the webhook request if needed
res.status(200).send('Webhook received successfully');
});
// Start the server
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
First, this code creates an Express.js app that listens for incoming webhooks to the /webhooks endpoint on port 8080.
const app = express();
const port = 8080; // Port your local server is running on
// Middleware to parse incoming JSON data
app.use(bodyParser.json());
// Define the webhook endpoint
app.post('/webhook', (req, res) => {
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
When it receives a webhook, it ingests the PagedDuty incident information and detects if it is a new Incident, an incident acknowledgment, or an incident resolution, these values map to Open, In Progress, and Closed in LinearB. For each of these situations, the code extracts required data from PagerDuty and maps it to the values LinearB expects. For example, when something triggers a new incident, this code maps the id, html_url, and created_at values from PagerDuty to the provider_id, http_url, and issued_at fields respectively and it passes through the title value without any modification; these are the fields LinearB requires to create a new incident. For example, here is the code that maps API data for a new PagerDuty incident.
if (incident.status === 'triggered') { // Assign incident properties in the format LinearB expects
console.log("Creating new incident: ", incident.id)
requestData.provider_id = String(incident.id); // These properties are required
requestData.http_url = String(incident.html_url);
requestData.title = String(incident.title);
requestData.issued_at = String(incident.created_at);
}
Lastly, the code uses Axios to send the data to LinearB via the incident API.
// The LinearB API expects PATCH requests to update existing incidents and POST requests to create new incidents.
if (incident.status === 'acknowledged' || incident.status === 'resolved') {
axios.patch(requestURL, requestData, { headers })
.then(response => {
console.log('Incident ${incident.id} has been updated.');
})
.catch(error => {
console.error('API error:', error.response.data);
});
} else if (incident.status === 'triggered') {
axios.post(requestURL, requestData, { headers })
.then(response => {
console.log('Update Successful');
})
.catch(error => {
console.error('API error:', error.response.data);
});
} else {
console.log("No action taken for incident: ", incident.id)
}
// Respond to the webhook request if needed
res.status(200).send('Webhook received successfully');
Here are a couple of additional things to note about this code:
- If you have port conflicts, you can change the port this runs on, but make sure you also update your ngrok ingress to forward to the port you specify.
- This code accesses your LinearB API key using an environment variable named API_KEY. You can rename this variable to whatever works best for you; here is a guide on working with environment variables in Node.
LinearB Free Users
If you are a free LinearB user, you won’t be able to use the Services feature in LinearB, so you’ll need to setup your integration to map PagerDuty services to repositories connected to LinearB. First, you need to remove this line from the code example:
requestData.services = [String(incident.service.name)];
Then, at the top of the code, add this serviceMapping object that links git repos to specific PagerDuty services. Update the values to match your organization.
// Update this list to map PagerDuty services to git repositories
const serviceMapping = {
'Service1': ['https://github.com/org/repo1.git', 'https://github.com/org/repo1.git']
}
Run the Code
For this guide, we’ll be the code in a file named server.js, but you can name the file anything you want. To run it, execute the following commands from your CLI:
npm init -y
npm install express --save
npm install body-parser --save
npm install axios --save
node server.js
You should now have a Node server running on the specified port.
Reporting Incidents to LinearB
Now, you should have everything you need to begin receiving PagerDuty incident alerts inside LinearB. If you want to test this immediately, log into your PagerDuty dashboard and create a new incident for the service you configured in this guide. Once you create the incident, everything should automatically be handled on the backend to make the incident appear in LinearB.
If you want to get started with LinearB, sign up for a free account today.