Introducing the Syntinul API and Webhooks

28 AUGUST 2024 • Written by Mehmet Kadir

We're always looking for ways to make improvements to the Syntinul platform and build on the functionality it currently offers. One of the features we've been developing is the Syntinul API and webhooks, which is now available on all subscription plans. This initial release provides API and webhook functionality that are the most important to our customers. We'll be adding more API endpoints and webhook events as we get feedback from users, so please do reach out to us if you have any suggestions. In this article we'll detail the new API and webhooks as a way to document and help developers make use of these integration options. If you run into any issues or need help, just drop us an email.

API

By default, API functionality is disabled and must be enabled on the Integrations page under the API tab. Once enabled, an API key is generated. This key must be included in the 'x-api-key' header of any API requests that are made. Below is an example of how an API request can be made using JavaScript:

fetch('https://api.syntinul.com/integrations/api/v1/results', {
  method: 'GET',
  headers: {
    'X-Api-Key': '<API key goes here>',
  },
})
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

There are currently 3 endpoints available, as detailed below:

Endpoint
/results
Method: GET
Returns: Object containing most recent scan results
/exposures
Method: GET
Returns: Array containing current exposures
/assets
Method: GET
Returns: Array containing current assets

Here are example responses for each endpoint, showing the structure of the returned data:

Results

{
  "type": "SCHEDULED",
  "start_time": "2024-08-26T10:51:23.007Z",
  "finish_time": "2024-08-26T10:52:00.071Z",
  "scope": {
    "networks": [
      "203.0.113.137/32",
      "198.51.100.28/32"
    ],
    "exclusions": []
  },
  "exposures": [
    {
      "tcp": [
        "80",
        "443"
      ],
      "udp": [],
      "host": "203.0.113.137"
    },
    {
      "tcp": [
        "22",
        "80",
        "443"
      ],
      "udp": [
        "53"
      ],
      "host": "198.51.100.28"
    }
  ],
  "changes": [
    {
      "tcp": [
        "+80",
        "+443"
      ],
      "udp": [],
      "host": "+203.0.113.137"
    },
    {
      "tcp": [
        "+22",
        "+80",
        "+443"
      ],
      "udp": [
        "+53"
      ],
      "host": "+198.51.100.28"
    }
  ]
}

Exposures

[
  {
    "tcp": [
      "80",
      "443"
    ],
    "udp": [],
    "host": "203.0.113.137"
  },
  {
    "tcp": [
      "22",
      "80",
      "443"
    ],
    "udp": [
      "53"
    ],
    "host": "198.51.100.28"
  }
]

Assets

[
  {
    "network": "203.0.113.137/32",
    "exclusions": [],
    "description": "Web Server",
    "tags": [
      "Production"
    ],
    "target_count": 1
  },
  {
    "network": "198.51.100.28/32",
    "exclusions": [],
    "description": "API Server",
    "tags": [
      "Staging"
    ],
    "target_count": 1
  }
]

Webhooks

By default, webhook functionality is disabled and must be enabled on the Integrations page under the Webhooks tab. Once enabled, a signing secret is generated. This secret should be used by your endpoint to verify that received events are from us. Each event includes a signature in its 'x-signature' header, which should be compared to the signature you calculate using your secret and the event payload.

Below is an example of how you might implement your endpoint using JavaScript:

const express = require("express");
const crypto = require("crypto");
const app = express();
const port = 3000;

app.use(express.json());

const signingSecret = "Signing secret goes here";

app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    try {
      const event = req.body;
      const receivedSignature = req.headers["x-signature"];
      const computedSignature = crypto
        .createHmac("sha256", signingSecret)
        .update(JSON.stringify(event))
        .digest("hex");
      // Verify the signature, otherwise the payload cannot be trusted
      if (computedSignature === receivedSignature) {
        console.log("Signature verification successful");
        switch (event.event_type) {
          case "scan.completed":
            // Your logic goes here
            console.log(event)
            break;
          default:
            console.log("Unhandled event type");
        }
      } else {
        console.log("Signature verification failed");
        return res.sendStatus(400);
      }
      res.sendStatus(200);
    } catch (error) {
      console.log(error);
      res.sendStatus(400);
    }
  }
);

app.listen(port, () => {
});

In the example event below, we can see the structure of the 'scan.completed' event:

{
  "id": "FolTlLOZinktKvJ5p8LFEPI9u61SUbpg",
  "api_version": "2024-07-28",
  "timestamp": 1724669520138,
  "event_type": "scan.completed",
  "data": {
    "scan_type": "SCHEDULED",
    "start_time": "2024-08-26T10:51:23.007Z",
    "finish_time": "2024-08-26T10:52:00.071Z",
    "scope": {
      "networks": [
        "203.0.113.137/32",
        "198.51.100.28/32"
      ],
      "exclusions": []
    },
    "exposures": [
      {
        "tcp": [
          "80",
          "443"
        ],
        "udp": [],
        "host": "203.0.113.137"
      },
      {
        "tcp": [
          "22",
          "80",
          "443"
        ],
        "udp": [
          "53"
        ],
        "host": "198.51.100.28"
      }
    ],
    "changes": [
      {
        "tcp": [
          "+80",
          "+443"
        ],
        "udp": [],
        "host": "+203.0.113.137"
      },
      {
        "tcp": [
          "+22",
          "+80",
          "+443"
        ],
        "udp": [
          "+53"
        ],
        "host": "+198.51.100.28"
      }
    ]
  }
}

The 'scan.completed' event is triggered whenever a scan is completed. Both successful and unsuccessful events are logged on the Integrations page under the Webhooks tab. Receiving an HTTP 200 from your endpoint is logged as a success, whereas any other response (including no response or timeout) is logged as a failure after 3 unsuccessful delivery attempts.

And that's all there is to it! With the addition of these new features, customers can now integrate data gathered by Syntinul with existing systems or leverage it to build new solutions. We plan to further develop the API and webhook functionality based on user feedback and emerging use cases.