Użytkownik (Administrator) otrzymuje możliwość zasubskrybowania wybranego wydarzenia w systemie QRmaint (w ustawieniach systemowych w zakładce Webhook).
Subskrypcja nowego zdarzenia wymaga zdefiniowania w formularzu następujących danych:
- typu zdarzenia
- linku z adresem API klienta
- nazwy subskrypcji (opcjonalnie)
- sekretu – czyli ciągu znaków, który posłuży do zabezpieczenia komunikacji między systemem QRmaint a aplikacją serwerową klienta. Jego podanie jest zalecane.
Każdą dodaną subskrypcję można dezaktywować, edytować, usunąć. Dodatkowo jest możliwość wygenerowania testowego zdarzenia webhook, które pozwala zasymulować prawdziwe zdarzenie wybranego typu, ale wysyła fikcyjne, testowe wartości parametrów (np. fikcyjny ID zadania). Powinno to ułatwić konfigurację klienckiej aplikacji serwerowej do obsługi webhooków QRmaint.
Zdarzenie tego samego rodzaju można subskrybować wielokrotnie, co pozwala klientom na stworzenie kilku scenariuszy obsługi jednego zdarzenia w systemie QRmaint.
Każdy zasubskrybowany event posiada również historię wywołań wraz ze statusem. Status zawiera informacje o tym czy zdarzenie zostało poprawnie zarejestrowane przez kliencką aplikację serwerową. Tag TEST oznacza zdarzenie testowe. W historii znajduje się także unikalne Id eventu, treść body oraz czas zarejestrowania zdarzenia.
Informacje dodatkowe dla programistów #
Komunikacja QRmaint Webhooks z zewnętrznym API odbywa się zgodnie z aktualnymi standardami komunikacji protokołem HTTP. Zapytania HTTP Webhooków QRmaint wysyłane są metodą POST.
Zapytania HTTP zawierają treść (body) w następującej postaci:
{
string EventTypeId,
string RequestId
}
oraz pola zależne od kontektu (typu zdarzenia Webhook)
{
int WorkId,
int? PreviousStatusId,
int? NewStatusId
}
rzykładowe body zapytania wygenerowane przez zdarzenie zmiany statusu zadania:
{
"EventTypeId": "WORK_STATUS_CHANGED",
"WorkId": 1371172,
"PreviousStatusId": 690,
"NewStatusId": 691,
"RequestId": "d8f2be95-55b6-4578-9c09-a085b02201aa"
}
Zabezpieczenia i sekret #
Treść zapytania jest hashowana algorytmem HMAC SHA256 (Hash Message Authentication Code), z użyciem ciągu znaków – sekretu. Sekret jest wykorzystywany w funkcji hashującej po stronie systemu QRmaint, ale konieczne jest także jego przechowywanie po stronie aplikacji klienta, do odczytania poprawności odebranego zapytania. Każda subskrypcja zdarzenia powinna mieć inny sekret.
System QRmaint wykorzystuje sekret oraz body zapytania HTTP (jako ciągu znaków – string) do utworzenia hash, który przesyłany jest w nagłówku x-qrmaint-signature. Aplikacja serwerowa klienta powinna odczytać nagłówek zapytania, a następnie wykorzystać algorytm HMAC SHA256 oraz sekret i body zapytania do sprawdzenia, czy odczytana z nagłówka wartość jest taka sama jak hash utworzony wspomnianym algorytmem. Jeśli wartości są takie same, oznacza to, że otrzymane dane zostały wysłane z systemu QRmaint, a nie np. z serwera, który podszywa się pod QRmaint.
Przykład funkcji sprawdzającej wiarygodność danych zapytania ze zdarzenia webhook w języku 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;
}
}
Przykład prostego serwera http w node.js potrafiącego obsłużyć zapytania z 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}`);
});