To allow the integration of Expel data without API polling, Expel can send webhook requests to external servers.

Webhook data model

The webhook body is UTF-8-encoded JSON.

The webhook json itself is a wrapper around a "change event", which is a structure that provides all the contextual data around an event within Workbench. This object varies by event but embeds a consistent series of representational Expel objects, for example, Investigation and RemediationAction.

  • guid: the unique ID for the main object associated with the webhook.

  • rule: the name of the notification rule subscription that produced the webhook.

  • event_name: the name of the event that triggered the rule. This name maps directly to the "change event" object provided in the data key.

  • data: JSON model of the "change event" emitted by Workbench

{
	"guid": "1a9c6d7a-78e6-4238-8cd7-8d2ec2492cab",
	"rule": "incident is created",
	"event_name": "incident_created",
	"data": {...}
}

Webhook signature

Every webhook sent by Expel contains the HTTP header Expel-Signature-256, formatted as sha256={some hash}. The header is a SHA-256 HMAC of the webhook payload bytes, generated using a secret key. The secret key is provided through Workbench when the webhook integration is created. See Adding a webhook integration.

Example Python snippet showing signature header verification

import hmac
import hashlib

# secret_bytes, accessible by this verification secret

# request object sent by Expel has been processed, with the body_bytes and header extracted
# body_bytes
# signature_header

# extract the signature
header_split = siganture_header.split("=")
if len(header_split) < 2:
	# not a valid signature header
	return

expel_sig = header_split[1]

# generate an hmac of the body_bytes
local_sig = hmac.new(secret_bytes, body_bytes, digestmod=hashlib.sha256).hexdigest()

# constant time compare, returns a boolean
valid_webhook = hmac.compare_digest(expel_sig, local_sig)

Note

All webhook requests that Expel send are encoded using UTF-8.

Webhook POST behavior

The receiving server is expected to reply with a status code to the webhook request. No response body is required.

  • 200: the request has been successfully received, no retries.

  • 406: the server is actively rejecting the webhook, no retries.

  • Any other valid 4xx or 5xx code: indicates some failure on the server side in processing. The webhook request is resent up to 3 times with a backoff.

  • Additionally, any connection failure also results in 3 retries.

Adding a webhook integration

To subscribe to webhook notifications in Workbench, you need to add at least one webhook integration.

  1. From the main menu, click Organization Settings > Integrations.

  2. At the bottom of the page, click Add a webhook destination.

  3. Enter a name for the webhook.

  4. Enter the destination URL to send the webhook requests to.

  5. Copy the secret key for the webhook and save it in a safe location.

    Caution

    When you finish the process and click Add, you can't access this secret key again.

  6. Click Add.

Adding a notification rule with a webhook destination

If at least one webhook integration exists, you can subscribe to notification rules where the destination is a webhook URL.

  1. Add a new notification rule.

    See Organization Notifications setup for Workbench.

  2. From the Notify Via section, select Webhook.

  3. Select a webhook URL.

Best practices for securing webhooks

When setting up an HTTP server for consuming Expel webhooks, use the following best practices:

  • Ensure that the server always uses HTTPS and has a valid signed certificate.

  • Always verify the Expel-Signature-256 header sent with all valid requests.

  • Set a limit on requests per second and request size to prevent DOS.

    It is unlikely that Expel webhooks will constitute a large volume of requests, but the server should be able to handle burst of requests.

  • Optionally, allowlist Expel's IP addresses to further limit unwanted traffic.