Audit Webhook Connector
Descope's Audit Webhook Connector allows Descopers to stream audit logs to their own API.
1. Deploy an API to Capture Webhook Events
Deploy an API to a service of your choice that accepts HTTP requests. This API will be used to receive the audit logs from Descope.
This connector sends outbound requests from Descope's static IPs. Allowlist these on your API gateway, firewall, or server as needed.
The connector posts a JSON array of audit events, not a single event object. Your handler must iterate over the array:
export async function POST(request: Request) {
// The body is an array of audit events, even when it contains a single event.
const events = await request.json();
for (const event of events) {
// Handle each audit log
}
return new Response(null, { status: 204 });
}Return a 2xx status only after you have successfully processed the batch. Descope treats any 2xx response as a successful delivery and will not resend those events.
2. Configuring the Webhook Audit Connector
Navigate to the Descope's Audit Webhook Connector configuration page and fill in the required parameters:
- Connector Name: Provide a unique name for your connector. This assists in distinguishing it, especially when multiple connectors are derived from the same template.
- Connector Description: Briefly explain the purpose of this connector.
- Base URL: Input the API URL where you'd like to send audit events to. This should start with either
http://orhttps://. Use the URL from Step 1. - Authentication Type: Descope supports various methods to authenticate with your service. Choose the method that suits your API:
- Bearer Token: Used for access keys such as JWTs.
- API Key: This usually involves a key-value pair.
- Basic Authentication: The traditional username and password method.
- None: Select this if your API doesn't require any authentication.
- Headers (Optional): Some APIs need specific headers, usually key-value pairs, to provide more details about the impending action.
- HMAC Secret (Optional): HMAC is a symmetric key method for message signing. The provided secret will be used to sign the payload. The outcome signature will be sent in the
x-descope-webhook-s256header. The recipient service should use this secret to validate the payload's integrity and authenticity by verifying the supplied signature. - Trust Any Certificate: By default, this option is turned off. If enabled, the client will overlook any certificate errors. While convenient for testing, it's crucial to remember that this is an insecure choice for production.
- Stream Audit Events: Select this if you want to stream audit events to your API.

3. View Audit Logs
Once you've configured the Audit Webhook Connector, your events will be sent to the API you specified. You can view the audit logs in the Audit page, which should match the events you receive in your API.


Each event includes the originating client IP as a top-level remoteAddress field. See Audit Event remoteAddress Field.
Payload Contract
Request Envelope
Each delivery is an HTTP POST whose body is a JSON array of audit events. A single request contains up to 100 events, so your endpoint must handle batches, not just individual events.
[
{ "action": "Change Password", "...": "..." },
{ "action": "UserModified", "...": "..." }
]Event Fields
Every event in the array contains the following top-level fields. Field names are camelCase.
| Field | Type | Description |
|---|---|---|
action | string | The action performed. See Audit Events for the full list. Some action values contain spaces — for example, Change Password. |
actorId | string | Identifies who performed the action. |
userId | string | The destination user the action was performed on. |
projectId | string | The Descope project the event belongs to. |
loginIds | array of strings | The login identifiers associated with the user. |
occurred | number | Time of the event as a Unix epoch timestamp in milliseconds. |
occurred_formatted | string | The same timestamp as an RFC 3339 string, for example 2026-06-08T20:18:25.843277Z. |
device | string | Source device, such as Desktop or Mobile. Can also reflect the SDK used, such as NodeJS. |
method | string | The authentication method used. May be an empty string. |
geo | string | Two-letter country code for the origin of the request. |
remoteAddress | string | IP address (v4/v6) of the origin of the request. |
tenants | array of strings | Associated tenant IDs. May be null. |
data | object | Request context and event-specific details. See The data object. |
Timestamps are numbers
occurred is a number, not a string. Passing it directly to a date parser that expects ISO 8601 will fail. Either divide by 1000 and parse as epoch seconds, parse the millisecond value directly, or use the occurred_formatted companion field.
The data object
Client and request context is nested inside data, not at the top level. Common keys include:
| Key | Type | Description |
|---|---|---|
browser | string | Browser detected from the request. |
os | string | Operating system detected from the request. |
osVersion | string | Operating system version. |
device | string | Device type, repeated from the top-level field. |
correlation_id | string | Matches the flow execution ID in troubleshooting logs. See Correlating Audit and Troubleshooting Logs. |
flow_id | string | The flow that produced the event. |
flow_execution_id | string | The specific flow execution. |
request_details | object | The request uri and method. |
Change | object | Present on modification events such as UserModified. Describes what changed. |
The remaining contents of data vary by action. Treat it as an open map and read only the keys you need.
Custom attribute timestamps
Timestamps inside custom attributes are also delivered as epoch-millisecond numbers, and they do not have a _formatted companion field. For example, a lastPasswordReset custom attribute arrives as "custom_attribute_lastPasswordReset": 1780949905852.
Example Payloads
[
{
"action": "Change Password",
"actorId": "U3Dga0ziehqoVAyXog2H6AaknVFn",
"userId": "U3Dga0ziehqoVAyXog2H6AaknVFn",
"projectId": "P2ABc0defGHijKLmnOPqrSTuvWXyz",
"occurred": 1780949905843,
"occurred_formatted": "2026-06-08T20:18:25.843277Z",
"method": "",
"device": "Desktop",
"geo": "US",
"loginIds": ["user@company.com"],
"remoteAddress": "203.0.113.42",
"tenants": null,
"data": {
"browser": "Chrome",
"correlation_id": "3ErwHAgTHSy1pwg0SgN9eFcNIsu",
"count": 0,
"device": "Desktop",
"flow_execution_id": "3ErwG1GLbW4tWI3nAKRD5zO09E9",
"flow_id": "reset-password",
"os": "macOS",
"osVersion": "10.15.7",
"request_details": {
"uri": "/v1/flow/next",
"method": "POST"
}
}
}
]Differences From the Search Audit API
The Audit Webhook and the Search Audit API push the same events, but they do not populate them identically in the event itself.
It's important to make sure you do not use one as a reference for the other.
| Concept | Audit Webhook | Search Audit API |
|---|---|---|
| Login identifiers | loginIds | externalIds |
| Time of the event | occurred, a JSON number | occurred, a JSON string |
| ISO timestamp | occurred_formatted | Not included |
| Event ID | Not included | ID |
| Event severity | Not included | type |
| Everything else | action, actorId, userId, projectId, device, method, geo, remoteAddress, tenants, data | Same names |
The two most commonly overlooked differences are the following:
- The login identifier key is not the same: The webhook sends
loginIds; the API returnsexternalIds. occurredis a number on the webhook and a string from the API: Both are Unix epoch milliseconds, but the JSON types are different.
Ensure that you're normalizing these values appropriately as you see fit, when ingesting these audit events.
Delivery Behavior
Audit events are not delivered in real time. Descope persists audit events first, then a background loop periodically collects pending events and streams them to your endpoint in batches.
Consequences to design for:
- Expect delays: A gap of several minutes between the action and the webhook delivery is normal. Under backlog or retry conditions, the gap can be considerably longer.
- Events arrive grouped: Actions spanning a window of time are typically delivered together in a single batch, in one request.
- Do not rely on the webhook for synchronous flow: If your product needs to reflect an event to an end user immediately, drive that from your own application logic and do not rely on events from the webhook.
- Reconcile periodically: For critical data, use the Search Audit API as a fallback to catch events that a failed delivery may have missed.
Note
Delivery timing is not a guaranteed service level and can vary with event volume and system load. Do not hard-code a timeout based on observed delays.
Use Cases
Roles Revoked From A User
As a security best practice, monitoring changes in the association of roles to users is crucial. As the identity provider, Descope can provide the role name as part of the "UserModified" audit event.
The data object of that event contains:
{
"Change": {
"removed_multi_tenant_roles": {
"T2lTcqSm7f5GD8lRwCyo4aHvC6wo": [
{
"id": "ROL2lKYLkbHroW5IntCAYTWPX86rRs",
"name": "test1"
}
]
}
},
"browser": "Chrome",
"correlation_id": "2nQJ0PEyu5EHrMJChqNgPC4AeVp",
"device": "Desktop",
"os": "macOS",
"osVersion": "10.15.7"
}In this example the role name "test1" was removed from the user.
Flow Modified In Production
Monitoring when authentication flows are modified is critical for maintaining security and compliance. As the identity provider, Descope can provide the modified flow and associated user as part of the "FlowUpdated" audit event.
{
"action": "FlowUpdated",
"actorId": "U2mLZYMlcIsqX6JouDBZUXQY97sSt",
"loginIds": ["user@company.com"],
"occurred": 1705329022000,
"occurred_formatted": "2024-01-15T14:30:22Z",
"device": "Desktop",
"data": {
"browser": "Chrome",
"correlation_id": "3mQK1PFzv6FIsNKDhrOhQD5BfWq",
"flow_id": "FL2mLZYMlcIsqX6JouDBZUXQY97sSt",
"flow_name": "Production Login Flow",
"os": "macOS",
"osVersion": "10.15.7",
"changes": {
"modified_steps": ["email_verification", "mfa_setup"]
}
},
"os": "macOS",
"osVersion": "10.15.7",
"remoteAddress": "203.0.113.42"
...
}In this example the user user@company.com modified the "Production Login Flow" from 203.0.113.42.
Project Setting Modified
Tracking changes to project-level configurations helps maintain security posture and compliance. Descope can provide the modified setting and associated actor as part of the "ProjectSettings" audit event.
{
"action": "ProjectSettings",
"actorId": "U2QGzw7GJtOLEisPiRE6CgXrLm4",
"loginIds": ["admin@company.com"],
"occurred": 1705337118000,
"occurred_formatted": "2024-01-15T16:45:18Z",
"device": "Desktop",
"data": {
"browser": "Chrome",
"correlation_id": "4nRL2QGzw7GJtOLEisPiRE6CgXr",
"os": "Windows",
"osVersion": "11",
"settings_changed": {
"jwt_expiration": {
"old_value": "3600",
"new_value": "7200"
},
"allowed_origins": {
"added": ["https://newapp.company.com"],
"removed": []
}
}
}
}In this example the user admin@company.com edited the "jwt_expiration" and "allowed_origins" settings.