Skip to main content

Webhook Receiver

What a custom HTTPS receiver needs to accept, verify and parse deliveries from a webhook destination with the Generic (signed JSON) format. Slack and Teams bodies are platform message formats, not a contract.

Contract rules

  • The body contract is schemaVersion 1. Later versions may add fields, but existing fields won't be renamed or removed without a new schemaVersion. Ignore fields you don't recognize.
  • Header names always start with X-StratoLens-, even when the product is white-labeled.
  • Verify X-StratoLens-Signature on every request and reject any that fail. The endpoint URL alone doesn't prove a request came from StratoLens.

Request

Generic webhook request
PropertyValue
MethodPOST to the destination URL
Content-Typeapplication/json
WhenAfter a scan, once per notification rule that triggered and lists this destination; and on tests
SuccessAny 2xx status. The response body is ignored.
Timeout10 seconds per attempt
RetriesUp to 3 attempts on network error, timeout or 5xx, waiting 1 s then 4 s. 4xx and redirects are not retried; redirects are not followed.
Body sizeAt most 256 KB

Acknowledge fast

Respond with 2xx as soon as you have queued the work. Slow receivers hit the 10-second timeout and get retried.

Rejections count toward auto-disable

Ten failed deliveries in a row auto-disable the destination, including deliveries your receiver rejects with 4xx.

Headers

Generic webhook headers
HeaderValueNotes
X-StratoLens-Eventnotification_alert or testtest for destination tests and rule test sends.
X-StratoLens-DeliveryUUIDOne per delivery, the same across its retries. Use it to drop duplicates.
X-StratoLens-TimestampUnix time in secondsSet fresh on every attempt.
X-StratoLens-Signaturesha256=<hex>HMAC-SHA256 of {timestamp}.{raw body}, keyed with the destination's signing secret. Recomputed on every attempt.
Custom headerAs configuredOnly when the destination has a custom request header.

Verifying the Signature

  1. Read the raw request body as bytes, before any JSON parsing.
  2. Read X-StratoLens-Timestamp and build the string {timestamp}.{raw body}: the timestamp, a dot, then the body.
  3. Compute HMAC-SHA256 over that string. Use the signing secret exactly as shown, a 64-character hex string, as the UTF-8 key. Don't hex-decode it.
  4. Hex-encode the result in lowercase, prefix it with sha256=, and compare it with X-StratoLens-Signature using a constant-time comparison.
  5. Optionally, reject requests whose timestamp is too far from your clock, to limit replays. StratoLens doesn't set a tolerance; choose your own, allowing for the retry delays.

The secret is shown once when you create the destination and once each time you click Rotate secret. StratoLens keeps only an encrypted copy and can't show it again. Rotation takes effect immediately: the next delivery is signed with the new secret, so update the receiver right after rotating.

Custom Request Header

Optionally, one extra header is sent with every delivery, for receivers behind API Management, an API gateway or a Function key, such as Ocp-Apim-Subscription-Key or Authorization. The header gets the request past the gateway; the signature proves it came from StratoLens.

Notes

  • Name: a valid HTTP header name (letters, digits and !#$%&'*+.^_`|~-), at most 256 characters.
  • Reserved names, rejected ignoring case: Content-Type, Content-Length, Host, Connection, Transfer-Encoding, and anything starting with X-StratoLens-.
  • Value: at most 4,096 characters, with no control characters and no characters outside Latin-1, such as emoji.
  • The value is write-only: stored encrypted and never displayed or logged. Use Replace to change it or Remove to drop it.

Body

Top level

Generic webhook body, top level
FieldTypeMeaning
schemaVersionnumberAlways 1 for this contract.
eventstringnotification_alert or test (same as the header).
scanIdstringID of the scan that triggered the alert. Rule test sends use test-email; destination tests use test-sample.
scanTimestampstringISO 8601 time of the scan (or of the test).
entry.idstringNotification rule ID. Destination tests use test-sample-entry.
entry.namestringNotification rule name.
sectionsarrayOne per triggered alert type, in the rule's order.

Each section

Generic webhook body, each section
FieldTypeMeaning
alertTypeIdstringStable alert type ID (table below). Key on this, not displayName.
displayNamestringAlert type name as shown in StratoLens.
summaryLinestringHuman-readable one-line summary. Wording may change; don't parse it.
matchCountnumberTotal items that matched, after filters.
deepLinkUrlstring, optionalLink into StratoLens. For change-tracking types it opens the Changes view with the scan pair preselected; for others, the alert type's page. Absent when StratoLens has no public base URL configured.
itemsarray of objectsUp to 10 rows, in the same order as the email table. Each row is a flat object of string values; keys per alert type below. Always empty when the destination's Detail level is Summary.
itemsTruncatedbooleantrue when matchCount is larger than the number of rows in items (always true on Summary destinations when anything matched).

Alert Type IDs and Row Keys

Alert type IDs and item row keys
alertTypeIdDisplay nameRow keys
cost_anomaliesCost Anomaliesresource, type, date, costImpact (e.g. "USD 123.45 increase")
orphaned_resourcesOrphaned Resourcesresource, type, subscription, monthlyCost
sizingSizingresource (ends with " (updated)" for changed recommendations), type, current, recommended, monthlySavings
access_optimizationsAccess Optimizationsprincipal, role, scope, lastActivity ("Never" when never used)
subscription_quotasSubscription Quotassubscription (ends with " (updated)" for changed entries), location, quotaFamily, usage (e.g. "48 / 50 vCPUs"), utilization (e.g. "96.0%")
commitment_coverageCommitment Coveragename, type (Expiring, Underutilized or Purchase), subscription, impact
security_recommendationsSecurity Recommendationsresource, type, severity, finding
service_retirementsService Retirementsresource, retirement, deadline ("Undated" when unknown), impact
policy_exemption_expiryPolicy Exemption Expirationexemption, scope, category, expiresOn (YYYY-MM-DD), daysLeft (e.g. "12 days"), assignment
resource_changesResource Changesname, resourceGroup, subscription, type, change
defender_recommendation_changesDefender Recommendation Changesname, resourceGroup, subscription, type, change
defender_plan_changesDefender Plan Changesplan, subscription, tier, change
defender_vulnerability_changesDefender Vulnerability Changescve, subscriptions (e.g. "3 subscriptions"), severity (e.g. "High · CVSS 8.1"), change
security_alertsDefender Alertsname, alertName, resourceGroup, subscription, change
advisor_changesAdvisor Recommendation Changesname, resourceGroup, subscription, type, change
policy_assignment_changesPolicy Assignment Changesname, resourceGroup, subscription, type, change
policy_compliance_changesPolicy Compliance Changesname, resourceGroup, subscription, type, change
activity_log_eventsActivity Log Eventstime (ISO 8601), operation, resource, caller, status

Notes

  • change is Added, Modified or Deleted. Change-tracking rows list added items first, then modified, then deleted.
  • Values are display strings (formatted currency, percentages, names). Missing values are N/A or —.
  • activity_log_events sections always link to the Activity Explorer without a time window, unlike the email.

Example

A delivery to a destination with the Full detail level. Values in angle brackets are placeholders.

notification_alert payloadJSON
{
  "schemaVersion": 1,
  "event": "notification_alert",
  "scanId": "<scan id>",
  "scanTimestamp": "2026-10-25T06:12:41.000Z",
  "entry": { "id": "<rule id>", "name": "Platform team alerts" },
  "sections": [
    {
      "alertTypeId": "orphaned_resources",
      "displayName": "Orphaned Resources",
      "summaryLine": "<summary text>",
      "matchCount": 1,
      "deepLinkUrl": "https://<your StratoLens host>/app/recommendations/orphaned-resources?metricsTimeframeDays=7",
      "items": [
        { "resource": "disk-old-01", "type": "Disks", "subscription": "Production", "monthlyCost": "USD 12.00" }
      ],
      "itemsTruncated": false
    }
  ]
}

Troubleshooting

Signatures never match

Answer

Compute the HMAC over the raw body bytes exactly as received, not re-serialized JSON. Include the timestamp and the dot, and use the secret string as the key without hex-decoding it.

Signatures stopped matching

Answer

Someone rotated the secret. Copy the new secret into the receiver, or rotate again and update the receiver immediately.

I receive duplicate deliveries

Answer

A retry after a timeout or 5xx. Drop repeats with the same X-StratoLens-Delivery.

Items are always empty

Answer

The destination's Detail level is Summary. Switch it to Full.

Saving the custom header fails

Answer

If the error says the name "is reserved and cannot be overridden", pick a name that isn't in the reserved list.

Was this page helpful?

Last updated October 25, 2026