Skip to content

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 format truto_entity:action, where truto_entity can be a resource in Truto and action can be created, 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"
}

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,

  1. Store the v value sent as part of X-Truto-Signature header. WDHLSeu0EEcUf9YV5N659YZ9EFOnfuIOrLj8UMBDhOo in the example above.
  2. 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.
  3. Calculate the Base64 encoded version of the data calculated in step 2. Make sure that the Base64 encoding is URL safe.
  4. 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);
  }