Guide to OneLogin Webhooks: Features and Best Practices
OneLogin webhooks stream what happens in your OneLogin account to a system you run: users logging in and out, failed logins, password changes, new MFA devices, app access and administrative changes. If you're feeding identity activity into a SIEM, a logging pipeline or an alerting rule for risky logins, the OneLogin Event Broadcaster is how those events leave OneLogin without polling the Events API.
This guide covers how the Event Broadcaster delivers events, how to authenticate it with a custom header, how to filter the firehose by event_type_id, what happens when your endpoint fails, and the best practices for production.
What are OneLogin webhooks?
OneLogin calls its webhook feature the Event Webhook API, or the Event Broadcaster. It sends batches of events "in near real-time" to one endpoint you specify, as a POST. You create it in the OneLogin Admin portal under Developers > Webhooks. OneLogin has no API for creating the subscription.
The broadcaster is a firehose. It doesn't let you pick event types: "All possible Event Types are sent in the payload so you will need to filter out the events that you are interested in." Your receiver gets everything and keeps what it needs.
OneLogin does not sign these requests. Instead, the broadcaster lets you add custom headers to every request, and OneLogin's docs suggest using one as a credential: "set an arbitrary string as a custom header value and ensure that the same string is sent with each webhook payload."
OneLogin webhook features
| Feature | Details |
|---|---|
| Configuration | OneLogin Admin portal, Developers > Webhooks. No API |
| Subscription model | One broadcaster sends every event type on the account |
| Method and format | POST. OneLogin recommends the JSON Array format, which sends a top-level array of events |
| Authentication | A custom header you add to the broadcaster. No signature |
| Event identifier | event_type_id, an integer on each event |
| Idempotency key | uuid on each event |
| Success response | HTTP 200 exactly |
| Retries | A non-200 response or a timeout re-sends the batch, up to 3 attempts in all, then it is dropped |
| Timeout | Not documented |
| Source IPs | Not published |
| Replay protection | None |
Common events
OneLogin lists a few popular event types on its webhook page and points to the Event Types API (GET /api/1/events/types) for the complete list, which is long.
event_type_id | Event |
|---|---|
5 | User Login Success |
6 | User Login Failed |
7 | User Logout |
8 | User Logged into an App |
11 | User Changed Password |
22 | User Added an MFA Device |
The body is an array of flat event objects. Each object has the same long list of keys, and the ones that don't apply to an event are null. Here is OneLogin's "bare bones" login example, trimmed to the populated fields:
[
{
"create": { "_id": "d210df80-ede8-42ba-8199-00ce951bc222" },
"uuid": "d210df80-ede8-42ba-8199-00ce951bc222",
"event_type_id": 5,
"event_timestamp": "2017-12-05 19:13:06 UTC",
"account_id": 92920,
"user_id": 32916209,
"user_name": "John Richards",
"actor_user_id": 32916209,
"actor_user_name": "John Richards",
"actor_system": "",
"ipaddr": "125.236.219.21",
"notes": "Authentication method: password.",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) ...",
"risk_score": null,
"app_name": null
}
]
The full example also carries keys for apps, roles, groups, directories, policies, MFA devices, RADIUS configurations and proxy agents. Three details matter when you parse it:
event_type_idis a number, not a string, so compare it with5, not"5".event_timestampis a string such as"2017-12-05 19:13:06 UTC", not ISO 8601, so parse it explicitly.- The payload has no event name. Names come from the Event Types API, so map the IDs you care about in your own code.
OneLogin's own filtering example is "Do something with events that have an event_type_id=5 and risk_score>50", which is a good model for alerting on risky logins.
Setting up OneLogin webhooks
- Generate a long random value for the header, for example with
openssl rand -hex 32, and choose a header name such asX-OneLogin-Secret. - In the OneLogin Admin portal, go to Developers > Webhooks and create an Event Broadcaster with your HTTPS endpoint URL.
- Choose the JSON Array format, as OneLogin recommends. The docs don't describe the other formats.
- Add a custom header with your chosen name and value, then save and enable the broadcaster.
In Hookdeck. Create a source with the OneLogin type. Enter the custom header's name in Header Name and its exact value in Header Value, and use the source URL as the broadcaster's endpoint. Hookdeck matches the header name case-insensitively and the value exactly, including any Bearer prefix you typed in OneLogin.
Securing OneLogin webhooks
Authenticating a OneLogin request means checking the custom header you configured. Compare its whole value with your stored copy in constant time, and reject the request if the header is missing or different.
const crypto = require('crypto');
const express = require('express');
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on buffers of different lengths
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/onelogin', express.raw({ type: '*/*' }), (req, res) => {
const headerName = process.env.ONELOGIN_WEBHOOK_HEADER || 'X-OneLogin-Secret';
const expected = process.env.ONELOGIN_WEBHOOK_SECRET;
if (!expected) return res.sendStatus(500); // fail closed when unconfigured
const received = req.get(headerName); // case-insensitive lookup
if (typeof received !== 'string' || !safeEqual(received, expected)) {
return res.sendStatus(401);
}
const events = JSON.parse(req.body.toString('utf8'));
for (const event of Array.isArray(events) ? events : [events]) {
if (event.event_type_id === 5 && (event.risk_score ?? 0) > 50) {
// queue an alert for a risky login
}
// skip event.uuid values you have already processed
}
res.sendStatus(200); // OneLogin treats anything other than 200 as a failure
});
The same check in Python:
import hmac
import os
def verify_onelogin_request(headers) -> bool:
expected = os.environ.get("ONELOGIN_WEBHOOK_SECRET")
if not expected:
return False # fail closed
header_name = os.environ.get("ONELOGIN_WEBHOOK_HEADER", "X-OneLogin-Secret")
received = headers.get(header_name) # Starlette and Flask headers are case-insensitive
if not received:
return False
return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))
A matching header shows that the sender knows the secret. It doesn't show that the body is unchanged, and a captured request can be replayed later and still pass, because OneLogin sends no signature and no timestamp. Serve the endpoint over HTTPS only, keep the value long and random, and rotate it in OneLogin and in your configuration if it might have leaked.
Make OneLogin webhooks production-ready. Hookdeck Event Gateway checks the custom header on every OneLogin batch, stores each delivery durably, and lets you replay anything your service failed to process.
OneLogin webhook limitations and pain points
A shared string is the only credential
The Problem: Anyone who learns the header value can post fabricated login events to your endpoint, and nothing in the request shows whether the body was altered.
Why It Happens: OneLogin's broadcaster has no signing. The custom header is a static value that OneLogin repeats on every request.
Workarounds:
- Use a long random value, HTTPS only, and keep it out of logs and source control.
- Deduplicate on
uuid, so a replayed batch has no further effect. - Before taking a drastic action such as disabling an account, confirm the event through the OneLogin Events API.
How Hookdeck Can Help: Hookdeck's OneLogin source type compares the header you name with the exact value you enter. Hookdeck answers OneLogin with a 200 either way, then records a request without the right header as failed verification, so it never reaches your destination.
Three attempts, then the batch is gone
The Problem: A short outage or a slow deploy can lose events for good.
Why It Happens: OneLogin re-sends a batch after a non-200 response or a timeout, "for a maximum of 3 attempts and then will not be sent again." It doesn't say how long it waits for a response or how far apart the attempts are.
Workarounds:
- Return
200as soon as the header checks out, and process the batch afterwards. - Return exactly
200. OneLogin's rule is "an HTTP 200 status code", so don't rely on202or204. - After an incident, backfill the gap from the OneLogin Events API.
How Hookdeck Can Help: Hookdeck accepts each batch as soon as it arrives and stores it, then delivers it to your service with retries on a schedule you control. If your service is down, events wait in Hookdeck instead of using up OneLogin's three attempts.
Everything arrives, all the time
The Problem: Your endpoint receives every event type on the account, including many you have no use for, and volume follows your login traffic.
Why It Happens: The broadcaster is a firehose with no event selection. OneLogin leaves filtering to the receiver.
Workarounds:
- Filter on
event_type_idas early as possible, before any expensive work. - Size your receiver for peak login traffic, such as the start of the working day.
How Hookdeck Can Help: Hookdeck filters can drop event types you don't need, or send login failures and password changes to different destinations, before anything reaches your code.
The payload has IDs, not names
The Problem: Logs and alerts built straight from the payload show event_type_id: 22, which means nothing to whoever reads them.
Why It Happens: Event names live in the Event Types API, not in the webhook body, and the webhook page lists only six of them.
Workarounds:
- Fetch the Event Types API once and keep a mapping of the IDs you handle in your code.
- Acknowledge IDs you don't recognise with
200and log them for review.
How Hookdeck Can Help: Each request is stored with its full body in Hookdeck, so you can search past deliveries for an event_type_id and see real examples before writing a handler for it.
Best practices
Iterate the whole batch
A request can hold several events. Loop over the array, handle each event on its own, and make sure one bad event doesn't fail the whole batch, since a failure re-sends all of it.
Deduplicate on uuid
A retried batch contains events you may already have processed. Store each uuid before acting on it and skip repeats. See our guide to webhook idempotency.
Treat every field as nullable
Most keys are null for any given event type. Use optional access and defaults, especially for risk_score, app_name and the actor fields.
Acknowledge fast and process asynchronously
With only three attempts and no documented timeout, a slow handler is risky. Verify, persist the batch, return 200, and process it in the background. See why to process webhooks asynchronously.
Monitor for silence
A broadcaster that stops sending looks the same as a quiet hour. Alert when no events arrive for longer than your normal login traffic allows.
Conclusion
The OneLogin Event Broadcaster posts every account event to one endpoint, in batches, with a custom header as its only credential. Compare that header in constant time over HTTPS, filter on event_type_id, deduplicate on uuid, and answer with a fast 200, because OneLogin gives up after three attempts.
Hookdeck Event Gateway sits in front of your receiver, checks the header, filters out the event types you don't need, and keeps every batch until your service has processed it.
Get started with Hookdeck for free and handle OneLogin webhooks reliably in minutes.