AutoElevate Webhooks (Beta)
Discover how to effectively use AutoElevate webhooks to automate tasks and enhance your workflows in the Beta environment.
Table of Contents
Overview
Webhooks let AutoElevate notify your own systems the moment something happens — an elevation request is raised, an elevated session starts, an agent changes state, a rule is created, an alert fires. Instead of polling the Partner API, you register an HTTPS endpoint and a list of event types, and AutoElevate delivers a signed HTTP request to that endpoint each time one of those events occurs.
Webhooks are created and managed entirely through the Partner API. There is no webhook screen in the Admin Portal. This article is for a technician or developer at your organization who is comfortable making authenticated HTTP calls; if you only want AutoElevate events posted into a chat channel, see Send AutoElevate Events to Microsoft Teams or Slack (Beta), which walks through that specific case.
This is a Beta feature. Webhooks are newly released and still being validated. Payload contents may change before general availability. The Partner API reference is the authoritative source for the current contract; where this article and the reference disagree, follow the reference.
Before You Begin
Partner API access
Everything in this article is done with authenticated Partner API calls to the base URL https://partner-api.autoelevate.com. You need an API key, and every request must carry two headers:
-
Authorization— every Partner API route, including all seven webhook routes, accepts either key scheme. Keys are created in the Admin Portal on a user's page (Users → the user → API keys) by anyone whose portal role has permission to edit users. The Scheme picker offers Bearer (most tooling), the default, and HMAC-SHA256 (signed). A Bearer key is sent asAuthorization: Bearer …and works from any HTTP client. An HMAC key requires you to sign each request; the exact signing steps are in the Partner API reference. -
X-Acknowledgment: i-understand-this-is-beta-and-may-change— the beta acknowledgment, sent verbatim. Without it the API returns 400 withMissing or invalid X-Acknowledgment header.
Creating a key is covered in Partner API (BETA); this article assumes you can already make a successful call. A quick way to prove that is a read such as GET /api/v1/webhooks, or GET /api/v1/companies, which also returns the company IDs you need for company-scoped webhooks.
Routes in this article are versioned (/api/v1/…). The unversioned form (/api/webhooks) is a permanent alias for v1 — never the latest version — but the reference recommends pinning an explicit version. Requests are rate-limited to 20 per minute per tenant, per method, per route; over the limit the API returns 429 with a Retry-After header.
Webhook permissions
Webhook routes are covered by four permissions. In the API key permission picker on the user's detail screen they appear as:
- Read webhooks — list and view webhooks and their delivery logs.
- Create webhooks — register a new webhook.
- Update and test webhooks — change a webhook, enable or disable it, and send a test event.
- Delete webhooks — remove a webhook.
Only the Administrator role holds these permissions. Every existing Administrator role received all four when the feature was released. Custom roles did not, and there is no screen in the Admin Portal for adding permissions to a role. The API key picker only ever offers permissions the user's role already holds, so it cannot be used to work around this. To use webhooks, create the API key on a user with the Administrator role, or contact CyberFOX Support to have the webhook permissions added to a custom role.
Looking up company IDs through GET /api/v1/companies needs the separate Read companies permission. Creating a company-scoped webhook does not: Create webhooks is the only permission the key needs. Without Read companies, copy a company's ID from the browser address bar on its page in the Admin Portal, which ends in /companies/ followed by the ID; your portal role needs permission to view companies to open that page.
Restricted company access
If the user behind the API key is restricted to certain companies, that user sees only webhooks scoped to those companies. A webhook that covers every company in your organization can only be created or viewed by a user with access to every company; a restricted user who tries is refused with 403 and asked to name a company.
Endpoint requirements
-
HTTPS only. An
http://URL is rejected when the webhook is created. - Publicly reachable. AutoElevate will not deliver to loopback, private (RFC 1918), link-local, carrier-grade NAT or other reserved addresses. The URL is checked when you register it, and the address it resolves to is checked again at every delivery.
- No redirects. A 3xx response is treated as a failure and is never followed. Register the final URL.
- Answer quickly. Return a 2xx within 10 seconds. Do your processing after you have acknowledged the request, not before.
How Webhooks Work
What a webhook is
A webhook is a small record made up of:
| Field | Required | What it does |
|---|---|---|
name |
Yes | A label for your own reference. 1–255 characters. Names do not have to be unique. |
url |
Yes | The HTTPS endpoint AutoElevate delivers to. Up to 2048 characters. Write-only after creation — see The URL is treated as a credential below. The same URL may be used by more than one webhook. |
eventSubscriptions |
Yes | The event types this webhook subscribes to — at least one, at most all 53. Duplicates are collapsed. |
format |
No |
native (the default), teams-incoming-webhook or slack-incoming-webhook. |
companyId |
No | Omit it to receive events for every company in your organization, or set a company ID (a UUID, from GET /api/v1/companies or the company page's address in the Admin Portal) to receive only that company's events. A company that does not exist returns 422; one you cannot access returns 403. |
enabled |
— | Not set at creation: a new webhook is live the moment it is created. If you send it on create, it is ignored and the webhook is live. It exists only on update, where setting it to false keeps the webhook but stops new deliveries. |
A webhook stores only its tenant and company. It has no link to the API key or the user that created it.
Event types
There are 53 subscribable event types. The identifier is what you put in eventSubscriptions and what arrives in the X-AutoElevate-Event header. The Partner API reference (WebhookEventType) always carries the current list.
| Area | Event types |
|---|---|
| Elevation requests |
request.created, request.approved, request.denied, request.duplicate.detected
|
| Elevated sessions |
session.created, session.completed, session.cancelled
|
| Agent updates |
agent-update.requested, agent-update.started, agent-update.completed, agent-update.failed, agent-update.download-failed, agent-update.verification-failed
|
| Computers |
computer.enrolled, computer.removed, computer.moved, computer.elevation-mode.changed, computer.auto-blocker-mode.changed, computer.technician-mode.changed, computer.admin-privileges.changed, computer.uac.changed, computer.restart-requested, computer.restarted
|
| Elevation rules |
rule.created, rule.updated, rule.removed
|
| Alerts |
alert.virus.detected, alert.execution.blocked, alert.execution.allowed
|
| Auto Blocker |
blocker-rule.created, blocker-rule.updated, blocker-rule.removed, blocker-event.converted-to-rule
|
| Privileged access |
technician-session.started, technician-session.authenticated, technician-session.time-started, technician-session.time-ended, os-login.requested, os-login.authenticated, technician-mode.bypassed
|
| Organization structure |
company.created, company.updated, company.removed, company.merged, location.created, location.updated, location.removed, location.merged, user.created, user.updated, user.removed, user.2fa.reset, user.password.reset
|
The five user.* event types are raised for your organization as a whole, because users belong to the organization rather than to a company. A webhook that subscribes to any of them must be organization-wide — combining them with a companyId is rejected at creation. Every other event type works at either scope. The exception is rule.*, blocker-rule.* and blocker-event.converted-to-rule events for organization-level rules. Those reach organization-wide webhooks only.
The identifier test is reserved for the test route and cannot be subscribed to.
Formats
| Format | What is delivered | Signed |
|---|---|---|
native |
The AutoElevate JSON envelope described below. For your own consumers — an automation platform, a SIEM, a ticketing system, custom code. | Yes |
teams-incoming-webhook |
A Microsoft Teams Adaptive Card summarizing the event, with an Open in AutoElevate button where the event has a record to open. | No |
slack-incoming-webhook |
A Slack Block Kit message with the same content. | No |
Only the native format carries a signature, because Teams and Slack cannot verify one. For those two formats the URL itself is the secret — treat it accordingly. The API key scheme you choose has no bearing on this: it covers only your calls to the Partner API.
The native payload
Every native delivery is a JSON envelope with the same outer shape. The data block varies by event type; its exact contents for each type are in the Partner API reference. A request.approved delivery looks like this, with identifiers shortened:
{
"webhookVersion": "1",
"eventType": "request.approved",
"timestamp": "2026-09-21T18:05:02.881Z",
"deliveryId": "…",
"tenant": {
"mspId": "…", "mspName": "Example Partner",
"companyId": "…", "companyName": "Example Customer",
"locationId": "…", "locationName": "Head Office"
},
"data": {
"request": {
"id": "…",
"approvalState": "APPROVED",
"requestType": "elevatedProcess",
"elevationType": "admin",
"elevationRequestExplanation": "Need to install the VPN client.",
"duplicateRequestCount": 0,
"ruleCreated": false,
"updatedBy": { "id": "…", "name": "Technician Name" },
"ticketingSystemInfo": { … },
"createdAt": "2026-09-21T18:04:11.204Z",
"updatedAt": "2026-09-21T18:05:02.881Z"
},
"computer": { "id": "…", "machineName": "FINANCE-LT-042", "platform": "windows" },
"file": { … }, "trigger": { … }, "osUser": { … }, "computerState": { … }
}
}Two things about timestamps are worth knowing before you build a consumer:
-
Every timestamp inside a delivery is ISO 8601 in UTC with milliseconds (
2026-09-21T18:05:02.881Z). There are two exceptions. TheX-AutoElevate-Timestampheader is Unix seconds, because it feeds the signature. The certificate dates underdata.trigger.publisherCertInfo(validFrom,validTo,signingTime) arrive as the agent sent them, for example9/28/2020 12:00:00 AM. -
The envelope's
timestampis when the event happened, not when it was sent. A delivery that was held briefly still carries the original time.
The management API uses a different convention again: createdAt and updatedAt on a webhook record are epoch milliseconds as numbers. That is by design, not a defect.
Delivery headers
Every delivery carries two headers, and native deliveries carry two more:
-
X-AutoElevate-Delivery— the delivery ID. Retries of the same event reuse it. -
X-AutoElevate-Event— the event type. -
X-AutoElevate-Timestamp— native only. Unix seconds at which the delivery was signed. -
X-AutoElevate-Signature— native only. See Verifying Signatures.
Creating and Managing Webhooks
The routes
All webhook management lives under /api/v1/webhooks on https://partner-api.autoelevate.com:
| Action | Route | Permission |
|---|---|---|
| List webhooks |
GET /api/v1/webhooks — paginated with skip and take (default 50, maximum 200); returns items and totalCount
|
Read |
| Create a webhook | POST /api/v1/webhooks |
Create |
| Get one webhook | GET /api/v1/webhooks/{id} |
Read |
| Update a webhook | PATCH /api/v1/webhooks/{id} |
Update and test |
| Delete a webhook | DELETE /api/v1/webhooks/{id} |
Delete |
| Read delivery logs | GET /api/v1/webhooks/{id}/delivery-logs |
Read |
| Send a test event | POST /api/v1/webhooks/{id}/test |
Update and test |
Creating a webhook
Send POST /api/v1/webhooks with a JSON body. A webhook that posts two elevation-request events for one company to a Teams channel:
{
"name": "Approvals channel",
"url": "https://<id>.aa.environment.api.powerplatform.com:443/powerautomate/automations/direct/workflows/…",
"eventSubscriptions": ["request.created", "request.approved"],
"format": "teams-incoming-webhook",
"companyId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Leave out companyId for an organization-wide webhook, and leave out format for native. The response is:
{
"webhookConfiguration": {
"id": "…",
"companyId": "…",
"name": "Approvals channel",
"url": "https://<id>.aa.environment.api.powerplatform.com/...?...",
"eventSubscriptions": ["request.created", "request.approved"],
"format": "teams-incoming-webhook",
"enabled": true,
"createdAt": 1790000000000,
"updatedAt": 1790000000000
},
"secret": "…64 hexadecimal characters…"
}The webhook's ID is webhookConfiguration.id; keep it for every later call. The signing secret is the top-level secret, alongside the configuration rather than inside it.
The signing secret is shown exactly once. It appears in the response to the create call and never again. It cannot be viewed later and cannot be rotated. If you lose it, or need to change it, create a new webhook and delete the old one. Store it in a secrets manager at the moment you create the webhook.
After creating, send a test event with POST /api/v1/webhooks/{id}/test and confirm the endpoint received it. If the test or a read returns 404 in the first few seconds after creation, wait a moment and try again — see Troubleshooting.
The URL is treated as a credential
For the Teams and Slack formats, anyone holding the URL can post into your channel, so AutoElevate treats every webhook URL as write-only. When you read a webhook back, url is redacted to its origin — for example https://hooks.slack.com/.... Sending that redacted form back in a PATCH is rejected; to keep the existing URL, leave url out of the request. To change it, send the full new URL.
Disabling and deleting
PATCH with "enabled": false keeps the webhook and its configuration but stops new deliveries. DELETE removes it, and its delivery logs are no longer reachable through the API. Disable when you are pausing an integration; delete when you are retiring it or replacing its URL or secret.
A webhook outlives the key that created it. Because it has no link to that key or user, it keeps delivering after the key expires or is revoked. To disable or delete it later, use any key that carries Update and test webhooks or Delete webhooks — creating a new short-lived key for the purpose is fine.
Neither is a kill switch for deliveries already in progress. A delivery that has entered its retry cycle keeps the endpoint details it started with and continues retrying for up to about six minutes after you disable or delete the webhook. Expect a short tail of traffic.
Testing
POST /api/v1/webhooks/{id}/test sends one event of type test to the webhook's URL and returns {"success": true} if the endpoint answered with a 2xx, or {"success": false} if it did not. It works even while the webhook is disabled. A test delivery is not retried, and it is recorded in the delivery logs like any other attempt. The native test payload carries the standard envelope with "eventType": "test", a tenant block with mspId and, for a company-scoped webhook, companyId (no names), and a data block of message (This is a test webhook delivery from AutoElevate.) and triggeredBy — the name, or email address if no name is set, of the user whose key sent it.
Because a new webhook is live the moment it is created, testing before any real event can arrive takes an extra step: create the webhook, immediately PATCH it with "enabled": false, send the test, then PATCH it with "enabled": true to go live. Events raised in the seconds between create and disable can still be delivered.
Verifying Signatures
This section applies to the native format only.
Each delivery carries X-AutoElevate-Timestamp (Unix seconds) and X-AutoElevate-Signature in the form:
t=<unix seconds>,v1=<hex>The v1 value is an HMAC-SHA256 of the string <timestamp>.<raw request body>, keyed with the webhook's signing secret and hex-encoded. Use the 64-character secret as the key exactly as shown. Do not hex-decode it. To verify a delivery:
- Read the timestamp from the header and reject the delivery if it is older than your tolerance. Five minutes is a reasonable choice; the platform does not enforce one, so the decision is yours.
- Take the raw request body exactly as received. Do not parse and re-serialize it — any change in whitespace or key order invalidates the signature.
- Compute HMAC-SHA256 over
<timestamp>.<raw body>with the secret, and hex-encode the result. - Compare it to the
v1value using a constant-time comparison.
Do not confuse this with AE-HMAC-SHA256, the optional scheme for signing your requests to the Partner API. That authenticates you to AutoElevate; the webhook signature authenticates AutoElevate to you. They use different keys and different inputs.
Delivery Rules
- Respond within 10 seconds. AutoElevate waits up to 10 seconds for a 2xx, with a hard deadline of 15 seconds wall-clock. Acknowledge first, process afterwards.
- Retries. No response, 408, 429 and any 5xx are retried after 10 seconds, 60 seconds and 300 seconds — four attempts in total. Every other status is final and is never retried.
-
De-duplicate on the delivery ID. Retries reuse the same
X-AutoElevate-Deliveryvalue, so your consumer should treat a repeated ID as the same event. -
Order is not guaranteed. Delivery is best effort. Order events by the envelope's
timestamp, never by arrival, and reconcile through the Partner API where certainty matters. - Redirects are never followed. A 3xx is a failure.
Reading the Delivery Logs
GET /api/v1/webhooks/{id}/delivery-logs returns { "deliveryLogs": [ … ] } — the 50 most recent attempts for that webhook, newest first. It does not paginate and there is no way to reach older attempts, so pull the logs promptly after a failure; a busy webhook with retries can turn over 50 rows quickly.
| Field | Meaning |
|---|---|
deliveryId |
Ties every attempt of one delivery together. Also sent on the wire as X-AutoElevate-Delivery. |
eventType |
The event type delivered. |
attemptNumber |
1 for the first attempt, up to 4. |
requestUrl |
The endpoint, redacted to its origin. |
responseStatusCode |
The HTTP status your endpoint returned; empty when there was no response at all. |
responseBody |
The first 1024 characters of your endpoint's response. |
success |
Whether the attempt was accepted. |
errorMessage |
Set when AutoElevate itself could not complete the request — see the table under Troubleshooting. |
createdAt |
When the attempt was made, as epoch milliseconds. |
Each entry also carries its own id and the webhookConfigurationId it belongs to.
Best Practices
- Use a Service user's API key rather than a person's, so the webhook keeps working when staff change. The Service user needs the Administrator role for its key to carry the webhook permissions. See Partner API (BETA).
- Grant only what the integration needs. A consumer that only needs to exist does not need Create or Delete on its key, and creating a company-scoped webhook does not need Read companies.
-
Prove the key with a read first. A 200 from
GET /api/v1/webhooksorGET /api/v1/companiesrules out key, header and signing problems before you create anything. - Store the signing secret when it is shown. There is no second chance.
- Verify signatures on every native delivery and reject stale timestamps. An unsigned endpoint will accept anything that reaches it.
- Acknowledge fast, process later. Queue the payload and return 2xx; long processing inside the request is the most common cause of avoidable retries.
- De-duplicate on the delivery ID and order by the envelope timestamp.
- Scope per company where you can. Smaller, targeted webhooks are easier to reason about than one organization-wide firehose.
- Re-scope or delete a webhook when its company is merged or removed. The webhook is not re-pointed automatically; it stays configured and silently receives nothing.
- Pull delivery logs as soon as something fails. The API returns only the 50 most recent attempts.
Troubleshooting
401 on any call
The credentials were missing, invalid or expired. Check the key has not expired or been revoked, that the Authorization header matches the key's scheme (Bearer … for a Bearer key, the signed AE-HMAC-SHA256 … header for an HMAC key), and — for HMAC — that the signed timestamp is within 5 minutes of AutoElevate's server time and the body hash matches the exact bytes sent.
400 — "Missing or invalid X-Acknowledgment header"
The beta acknowledgment header is missing or not exactly i-understand-this-is-beta-and-may-change. Send it, verbatim, on every request.
403 — "You are not authorized to perform this action."
This is the API's general permission error, and it does not say which permission is missing. Match the route you called to the permission column in The routes above, then check that the API key carries it. If the key's user is not an Administrator, the permission is not available to grant — use an Administrator's key, or contact CyberFOX Support to have the webhook permissions added to the role.
403 — "A webhook without a companyId receives events for every company; your company access is restricted, so name a company."
The key's user is restricted to certain companies and tried to create an organization-wide webhook. Add a companyId the user has access to, or use a key belonging to a user with access to every company.
400 on create or update
Validation errors come back as a BadRequestError whose message names the field and the problem. The ones you are likely to meet:
| Message | Cause and fix |
|---|---|
| Webhook URLs must use https. | The URL starts with http://. Only HTTPS is accepted. |
| Event types … are raised for the MSP, not a company, so only a webhook without a companyId can subscribe to them. — the message lists the offending event types | You combined one or more user.* event types with a companyId. Those events are organization-level: remove the companyId, or remove the user.* types. |
| Webhook URL is the redacted form a read returns, not a real endpoint. Omit it to keep the current URL, or send the full one. | You sent back the redacted url from a read. Leave url out of the PATCH to keep it, or send the complete new URL. |
A validation error on eventSubscriptions listing the accepted values |
The list contains test or an identifier that does not exist. test is reserved; check spelling against the event type table. |
A validation error on url about a private or reserved address |
The URL is an IP address in a private or reserved range, or a local-only name such as localhost or *.local. Registration does not check DNS. |
422 on create
The companyId names a company that does not exist. Look it up with GET /api/v1/companies or on the company's page in the Admin Portal.
404 immediately after creating a webhook
A read or test in the first few seconds after a successful create can return 404 while the new record propagates. Wait a few seconds and retry.
429 on any call
You have exceeded the rate limit of 20 requests per minute for that route. Wait for the number of seconds in the Retry-After header, then retry. The bucket refills continuously, at roughly one request every three seconds.
What the delivery log shows
| What the log shows | What it means |
|---|---|
| Redirect not followed. Point the webhook at its final URL. | Your endpoint answered 3xx. Redirects are never followed. Register the URL the redirect points to. |
| Blocked outbound request: "…" resolves to a private or reserved address. | At delivery, the hostname resolved to a private or reserved address. An internal-only hostname passes registration and then fails every delivery. Use a publicly reachable endpoint. Not retried. |
| The signing secret for this webhook could not be read. | A platform-side problem, not something you can fix. Open a support ticket with the details listed below. |
responseStatusCode empty, no response body |
No response at all — DNS, TLS or a timeout. Retried on the normal schedule. |
| 401, 403 or 404 | Your endpoint rejected the delivery. Final; not retried. For Teams and Slack this usually means the URL has expired or been regenerated — create a new webhook with the new URL. |
| 408, 429, 5xx | Your endpoint was slow, throttling or failing. Retried after 10 s, 60 s and 300 s. |
A company-scoped webhook has gone quiet
If the company it was scoped to has been merged into another company or removed, the webhook is not re-pointed. It remains configured and receives nothing. Re-scope it to the surviving company or delete it.
Deliveries arrive twice or out of order
Both are expected. Retries reuse the delivery ID, so de-duplicate on it; and delivery order is not guaranteed, so order by the envelope's timestamp. Where your process needs certainty, confirm the current state through the Partner API.
Deliveries continued after I disabled or deleted the webhook
Deliveries already in their retry cycle keep going for up to about six minutes; only new events are stopped. If you need traffic to stop immediately, make your endpoint return a final status such as 410, which ends retries for that delivery.
Deliveries continue after the API key expired
Expected. A webhook is not tied to the key or user that created it, so expiring or revoking that key does not stop it. Create a new key with Update and test webhooks or Delete webhooks, then disable or delete the webhook.
What to include in a support ticket
Everything Support needs is in the delivery log entry, and a webhook ID from GET /api/v1/webhooks:
- The webhook ID.
- The
deliveryId— this is the one value Support can search for. If you cannot reach the API, it is also in theX-AutoElevate-Deliveryheader of the delivery you received. - The
createdAtandattemptNumberof the failing attempt. - The
eventType. -
responseStatusCodeanderrorMessage, verbatim.
Never include your API key, signing secret or a full webhook URL in the ticket.
Security Notes
- A webhook URL is a capability. Whoever holds it can receive your events — or, for Teams and Slack, post into your channel. AutoElevate never returns a full URL after creation, and the permission to create webhooks is held only by the Administrator role for the same reason.
- Only native deliveries are signed. Verify the signature and reject stale timestamps. Teams and Slack deliveries rely on the secrecy of the URL alone.
- The signing secret is shown once and cannot be rotated in place. Rotation is create-new, delete-old.
- A webhook outlives its creator's key. Revoking a key does not stop a webhook it created; disable or delete the webhook itself.
- Deliveries never go to private or reserved addresses — checked at registration and again at every delivery, with DNS pinned so a public hostname cannot be re-pointed inward between the check and the connection — and never follow redirects. A webhook cannot be turned against systems inside your own network.
- Delivery logs keep only the first 1024 characters of your endpoint's response. Keep secrets out of your endpoint's error responses.