The User (Administrator) is given the option to subscribe to a selected event in the QRmaint system (in the system settings in the Webhook tab).
To subscribe to a new event, the following data must be defined in the form:
- type of event
- link with client API address
- subscription name (optional)
- secret – a string of characters that will be used to secure communication between the QRmaint system and the client’s server application. Its provision is recommended.
Each added subscription can be deactivated, edited, and deleted. Additionally, it is possible to generate a test webhook event, which allows you to simulate a real event of the selected type, but sends fictitious, test parameter values (e.g. a fictitious work order ID). This should facilitate the configuration of the client server application to support QRmaint webhooks.
The same type of event can be subscribed to multiple times, which allows clients to create several scenarios for handling one event in the QRmaint system.
Each subscribed event also has a history of calls along with a status. The status contains information about whether the event was correctly registered by the client server application. The TEST tag indicates a test event. The history also contains a unique event ID, body content, and the time the event was registered.
Additional information for developers #
QRmaint Webhooks communication with external API is done in accordance with current HTTP protocol communication standards. QRmaint Webhooks HTTP requests are sent using the POST method .
HTTP requests contain content (body) in the following form: and fields depending on the context (Webhook event type) example query body generated by a work order status change event:
{
string EventTypeId,
string RequestId
}
{
int WorkId,
int? PreviousStatusId,
int? NewStatusId
}
{
"EventTypeId": "WORK_STATUS_CHANGED",
"WorkId": 1371172,
"PreviousStatusId": 690,
"NewStatusId": 691,
"RequestId": "d8f2be95-55b6-4578-9c09-a085b02201aa"
}
Security and secrecy #
The query content is hashed with the HMAC SHA256 (Hash Message Authentication Code) algorithm , using a string of characters – the secret. The secret is used in the hashing function on the QRmaint system side, but it is also necessary to store it on the client application side, to read the correctness of the received query. Each event subscription should have a different secret.
The QRmaint system uses the secret and body of the HTTP request (as a string) to create a hash, which is sent in the x-qrmaint-signature header . The client server application should read the request header and then use the HMAC SHA256 algorithm and the secret and body of the request to check whether the value read from the header is the same as the hash created with the aforementioned algorithm. If the values are the same, it means that the received data was sent from the QRmaint system, and not, for example, from a server that impersonates QRmaint.
Example of a function that checks the credibility of query data from a webhook event in JavaScript (node.js)
function checkHMAC(requestSignature, body, secret) {
const hash = crypto.createHmac('SHA256', secret).update(body).digest('hex');
if (requestSignature == hash) {
console.log('request signature match');
return true;
} else {
console.log('request signature not match');
return false;
}
}
Example of a simple http server in node.js that can handle requests with QRmaint Webhooks:
const express = require('express');
const crypto = require('crypto');
const https = require('https');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 4000;
const secret = '1234567890';
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const options = {
key: fs.readFileSync(path.join(__dirname, './certs/key.pem')),
cert: fs.readFileSync(path.join(__dirname, './certs/cert.pem'))
}
const sslServer = https.createServer(options, app);
function checkHMAC(requestSignature, body, secret) {
const hash = crypto.createHmac('SHA256', secret).update(body).digest('hex');
if (requestSignature == hash) {
console.log('request signature match');
return true;
} else {
console.log('request signature not match');
return false;
}
}
app.post('/webhook-test/new-work-request', (req, res) => {
const body = req.body;
const headers = req.headers;
const requestSignature = headers['x-qrmaint-signature'];
if (checkHMAC(requestSignature, JSON.stringify(body), secret)) {
res.status(201).json({success: true, data: {}});
} else {
res.status(401).json({success: false, data: {}});
}
});
app.post('/webhook-test/new-work-order', (req, res) => {
const body = req.body;
const headers = req.headers;
const requestSignature = headers['x-qrmaint-signature'];
if (checkHMAC(requestSignature, JSON.stringify(body), secret)) {
res.status(201).json({success: true, data: {}});
} else {
res.status(401).json({success: false, data: {}});
}
});
app.post('/webhook-test/work-status-changed', (req, res) => {
const body = req.body;
const headers = req.headers;
const requestSignature = headers['x-qrmaint-signature'];
if (checkHMAC(requestSignature, JSON.stringify(body), secret)) {
res.status(201).json({success: true, data: {}});
} else {
res.status(401).json({success: false, data: {}});
}
});
sslServer.listen(port, () => {
console.log(`Secure server is listening on port ${port}`);
});