Inline hooks
Five identity events now POST a signed request to an endpoint you own. What arrives, how to verify it, the five-second budget, and why a hook can never block a sign-in.
An inline hook calls a system of yours when something happens in identity: somebody is added, somebody's details change, somebody is deactivated, somebody signs in. You register an HTTPS endpoint, we sign the request, and your code decides what to do about it.
This is also a correction. Until recently there was no dispatcher at all: hooks could be registered, listed and activated, and no outbound request was ever made. Our own earlier documentation described them as webhooks triggered on lifecycle events, and that description was wrong. It is right now, and this article says exactly where.
The five events
One event per hook. If you want two, register two. The console shows each with its literal type in brackets, which is what arrives in the payload and in the event header.
| Event | Raised by | `data` carries |
|---|---|---|
qorionix.user.created | A person being added to your organisation, including through a bulk import — which adds source: "bulk_import" so you can tell the two apart. | user_id, role, invited |
qorionix.user.updated | A change to a person's record. | user_id, email, status |
qorionix.user.deactivated | An account being locked, disabled or suspended. | user_id, status |
qorionix.user.login_success | A completed sign-in. This is the high-volume one. | user_id, email, ip_address, user_agent |
qorionix.user.group_membership_changed | Nothing. No code path raises it. | — |
A hook you register today is one of these five — the qorionix. prefix is current. If you registered a hook before this event naming changed, it is untouched and still fires: its stored type is still zentryq.user.created and friends, your receiver still gets exactly that string in event_type and in the event header, and nothing about it needs re-saving. The two forms are not interchangeable inside one hook — a hook is either the old spelling or the new one, permanently, from when it was created — but both are live at the same time across your organisation's hooks, and the platform fires either correctly. Same reasoning as the deprecated header spellings below.
Registering one
Identity → Inline hooks, headed Inline hooks for identity workflows. Press New hook. The wizard has two steps — Event & destination and What gets sent — and the second one shows you the exact request body before you save, which is worth reading rather than skipping.
- `Name`
- For humans. Write it as the outcome, not the mechanism — the console's own placeholder is
Tell the HR system about new joiners. - `Fires on`
- One of the five above. One event per hook.
- `Send it to`
- An
httpsURL you control. Plain HTTP is refused withUse https. This payload carries your people's email addresses and sign-in IP addresses; over plain http anyone on the path can read them., and a value without a scheme withThat is not a full URL. Include the scheme, for example https://example.com/hooks/qorionix. - `Extra headers`
- One per line as
Name: value. The headers we set cannot be overridden — an attempt is refused withThat header is set by us and cannot be replaced., and the check covers both the current and the deprecated spellings, so you cannot smuggle a forged signature in by using the old name. - `Start it now?`
Yes — start sending immediatelyorNo — save it paused, I will turn it on once my endpoint is ready. Take the second one unless the endpoint already exists.
Registering, editing, activating, pausing, rotating the secret and deleting all need organizations.write; reading needs organizations.read. That is a broad permission — anybody who can rename your organisation can register a hook — and it is worth knowing when you decide who holds it.
The destination is checked the same way outbound webhook destinations are: loopback, link-local, private and reserved ranges are refused, and the check runs at the socket rather than only on the name. The console explains why in one sentence — An https URL you control. Addresses inside private networks are refused — this platform sits next to cloud credentials and will not be used to reach them.
What arrives
{
"event_id": "…",
"event_type": "qorionix.user.login_success",
"organization_id": "…",
"occurred_at": "2026-09-03T10:20:00Z",
"data": {
"user_id": "…",
"email": "[email protected]",
"ip_address": "203.0.113.9",
"user_agent": "Mozilla/5.0 …"
}
}Content-Type: application/json
X-Qorionix-Signature: t=1756652400,v1=8f3c1d…e9
X-Qorionix-Timestamp: 1756652400
X-Qorionix-Event: qorionix.user.login_success
X-ZentryQ-Signature: t=1756652400,v1=8f3c1d…e9 (deprecated, identical)
X-ZentryQ-Timestamp: 1756652400 (deprecated, identical)
X-ZentryQ-Event: qorionix.user.login_successThe event value in both the body and both event headers always matches this hook's own stored type — qorionix.user.login_success above, because this example is a hook created under the current naming. A hook still holding the pre-rename type sends zentryq.user.login_success in exactly the same three places instead, consistently with itself; it never mixes the two spellings in one delivery.
The signing scheme is exactly the one used for outbound webhooks: the signed payload is the timestamp, a full stop, and the raw request body, MACed with HMAC-SHA256 under your hook's secret. Every rule in Verifying a webhook signature applies here without modification — MAC the raw bytes before parsing, compare in constant time, enforce a freshness window, and read the new header name falling back to the old one rather than swapping one literal in a single deploy.
Two differences from a webhook delivery worth noting: there is no delivery id header, because a hook is delivered once and never retried, and there is no retry header, for the same reason. Deduplicate on event_id from the body if you need to.
The signing secret
Each hook has its own secret, shown once when the hook is created and once again each time you rotate it. The console's wording is the instruction: Give this to whoever runs the receiving endpoint. They recompute an HMAC-SHA256 over the raw request body and the X-ZentryQ-Timestamp header and compare it with X-ZentryQ-Signature. If you lose it, rotate — there is no way to read it back.
Rotation has no overlap window. The console is explicit about the consequence before you confirm: Rotate the signing secret for "{name}"? The new secret is shown once. Deliveries keep going out, but your receiver rejects every one of them until you install the new value there. Do it in a change window, or make your receiver accept two secrets for the length of the rollout.
One attempt, five seconds, and a failure that changes nothing
This is the section that decides whether you can depend on a hook, so it is stated flatly.
- One attempt. No retries. If your endpoint is down, the event is gone. There is no queue, no backlog and no replay.
- Five seconds. Including TLS negotiation. Acknowledge and do the work afterwards.
- Your response is ignored. The status code is not read as a decision, and the body is read only to the first two kilobytes, for diagnostics. Returning
403does not deny anything. - A failure never affects the operation. A timeout, a connection refused, a
500, an enormous body — each is logged and audited and then dropped. None of them can make a sign-in fail, a user creation roll back, or a deactivation not happen. The console says the same thing in one line:Delivery is a notification, not a check. One attempt, five seconds, no retries — and a failure never blocks the sign-in or the change that triggered it. - Dispatch is detached from the request that caused it. The outbound call is not tied to the lifetime of the sign-in it follows, so a hook cannot slow a login down and a login finishing cannot cut a hook off mid-flight.
The practical consequence: a hook is a good way to start a workflow and a bad way to guarantee one. If the thing on the other end must happen — an access revocation, an audit record, a ticket — build it so that a missed hook is recoverable, by reconciling against the API on a schedule. If it is a convenience, a hook is exactly the right tool.
Operating them
Each row carries four actions: What is sent opens the payload and header preview without leaving the page; Activate and Pause switch delivery on and off; Rotate secret mints a new one and shows it once.
The confirmations are worth reading rather than dismissing, because each names the consequence exactly: Activate "{name}"? From now on, every {event} in this organisation is posted to {uri}. and Delete "{name}"? Nothing more is sent to {uri}. The signing secret is destroyed with it.
There is no delivery log and no per-hook success rate. If you need to know whether your endpoint is receiving what it should, that has to be measured on your side — which is another reason to reconcile against the API rather than trusting the stream.
What to do next
- Register a hook paused, point it at your endpoint, then activate it and watch one real event arrive before you build anything on it.
- Copy a verifier from Verifying a webhook signature — the scheme is identical and the article is written to be pasted from.
- If a hook is not arriving, work through An inline hook is not arriving before assuming it is your endpoint.
- If what you actually wanted was Qorionix to do something rather than tell you, read Lifecycle rules — and read the part about what fires them.