Processing webhook events
General guidelines for processing Truto webhook events.
Webhook event payload structure
The webhook event is a JSON object with the following properties,
Body
id
is the unique ID for the webhook event.event
is the name of the event. E.g,integrated_account:created
. It's in the formattruto_entity:action
, wheretruto_entity
can be a resource in Truto andaction
can becreated
,updated
,deleted
, etc.payload
is the data associated with the action. In most cases, it will be updated resource.environment_id
is the environment this webhook event is associated with.created_at
is the time when this webhook event was generated.webhook_id
is the ID of the webhook associated with this event.
An example webhook event sent when a new integrated account is connected is shown below,
json
{
"id": "3a0da6ba-b2d1-473f-957c-51f6825e3623",
"event": "integrated_account:created",
"payload": {
"id": "79a39d69-e27e-49cb-b9a9-79f5eea7aa26",
"tenant_id": "acme-1",
"environment_integration_id": "27a8c0ff-0c2e-4383-b651-0772b3515921",
"context": {},
"created_at": "2023-06-16T09:21:21.000Z",
"updated_at": "2023-06-16T09:21:21.000Z",
"is_sandbox": false,
"unified_model_override": {},
"environment_id": "ac15abdc-b38e-47d0-97a2-69194017c177",
"integration": {
"id": "1fa47bf3-5f1f-4b65-bcd0-8d07ab455e15",
"name": "helpscout",
"category": "helpdesk",
"is_beta": false,
"team_id": "68ea7267-2aec-4da0-b5d9-192cc84eb2de",
"sharing": "allow",
"default_oauth_app_id": null,
"created_at": "2023-02-16T09:27:09.000Z",
"updated_at": "2023-02-17T12:56:55.000Z"
},
"environment_integration": {
"id": "27a8c0ff-0c2e-4383-b651-0772b3515921",
"integration_id": "1fa47bf3-5f1f-4b65-bcd0-8d07ab455e15",
"environment_id": "ac15abdc-b38e-47d0-97a2-69194017c177",
"show_in_catalog": true,
"is_enabled": true,
"override": {},
"created_at": "2023-02-16T09:27:16.000Z",
"updated_at": "2023-02-16T09:27:16.000Z"
}
},
"environment_id": "ac15abdc-b38e-47d0-97a2-69194017c177",
"created_at": "2023-06-16T09:21:22.369Z",
"webhook_id": "077ec306-5756-43fa-9a06-0cc0da4eabe0"
}
Header
For security, Truto adds a X-Truto-Signature
header which contains the Base64 encoded SHA256 HMAC of the request body. For the request body shown above, the following header will be added to the request,
http
X-Truto-Signature: format=sha256,v=WDHLSeu0EEcUf9YV5N659YZ9EFOnfuIOrLj8UMBDhOo
Verification of the webhook event
You can get the SHA256 HMAC in the v
part of the header value and do the following steps to make sure the webhook event is legit and sent from Truto,
- Store the
v
value sent as part ofX-Truto-Signature
header.WDHLSeu0EEcUf9YV5N659YZ9EFOnfuIOrLj8UMBDhOo
in the example above. - Calculate the SHA256 HMAC of the request body using the
secret
attribute returned to you when you had created the webhook. IMPORTANT: Please use the raw string received as the body and not the JSON parsed version of it that most frameworks return you in the middleware function. - Calculate the Base64 encoded version of the data calculated in step 2. Make sure that the Base64 encoding is URL safe.
- Compare the value obtained in step 3 with the value stored in step 1. They should be equal. If they are not, then the payload has been tampered with and the webhook event is not generated by Truto.
js
const crypto = require("crypto");
function verifyWebhook(headerValue, secret, body) {
// Get HMAC from header
const hmacFromHeader = Buffer.from(headerValue.split("v=")[1], "base64");
// Calculate the SHA256 HMAC of the request body
const hmac = crypto.createHmac("sha256", secret);
hmac.update(body);
// Calculate the Base64 encoded version of the data
const calculatedHmac = Buffer.from(hmac.digest("hex"), "hex");
return crypto.timingSafeEqual(hmacFromHeader, calculatedHmac);
}
// Note: You need to pass the raw body of the webhook request to the verifyWebhook function. If you are using Express.js, you can get the raw body like this:
app.use(
bodyParser.json({
verify: function (req, res, buf) {
req.rawBody = buf.toString();
},
})
);
// And then in your route:
app.post("/webhook", (req, res) => {
let isVerified = verifyWebhook(
req.headers["x-truto-signature"],
secret,
req.rawBody
);
console.log(isVerified ? "Webhook Verified" : "Webhook Verification Failed");
});
csharp
public static bool VerifyWebhook(string headerValue, string secret, string body)
{
// Get HMAC from header
var hmacFromHeader = Encoding.UTF8.GetBytes(headerValue.Split("v=")[1]);
var secretBytes = Encoding.UTF8.GetBytes(secret);
var bodyBytes = Encoding.UTF8.GetBytes(body);
// Calculate the SHA256 HMAC of the request body
var hmac = new HMACSHA256(secretBytes);
var calculatedHmac = hmac.ComputeHash(bodyBytes);
var base64OfCalculatedHmac = Convert.ToBase64String(calculatedHmac);
string urlSafeBase64String = base64OfCalculatedHmac.Replace('+', '-').Replace('/', '_').TrimEnd('=');
var generatedBytes = Encoding.UTF8.GetBytes(urlSafeBase64String);
return CryptographicOperations.FixedTimeEquals(hmacFromHeader, generatedBytes);
}