An inline hook is not arriving, or times out
Six causes in order: the event nobody raises, the paused hook, the missing secret, the refused address, the five-second budget, and the fact that there are no retries.
You registered an inline hook and your endpoint is not seeing requests, or is seeing them and failing. The causes below are in the order they actually occur. The first two account for most of it and neither is a problem with your code.
1. You are subscribed to the event nothing raises
Four of the five event types have a real dispatch site. One does not:
qorionix.user.created fires
qorionix.user.updated fires
qorionix.user.deactivated fires
qorionix.user.login_success fires
qorionix.user.group_membership_changed NOTHING RAISES THISA hook on the last one saves, activates, displays as healthy and stays silent forever. The console warns when you choose it — Nothing in the platform raises this event yet. A hook on it saves and stays quiet — pick another event unless you are preparing for a future release. — and it is an easy warning to click past.
Check the Fires on value on your hook before anything else. If it is that one, nothing else in this article applies.
2. The hook is paused
The wizard's last question is Start it now?, and the sensible answer while your endpoint is being built is No — save it paused, I will turn it on once my endpoint is ready. A hook saved that way stays paused until somebody presses Activate.
The row shows Active or Inactive. Pressing Activate confirms with the consequence spelled out: Activate "{name}"? From now on, every {event} in this organisation is posted to {uri}.
3. The hook has no signing secret, so it is skipped
Signing with an empty key produces a value anybody can compute, which is worse than no signature at all because the receiver believes it verified something. So a hook without a secret is not delivered to, and the reason is recorded:
inline hook not delivered: no signing secret; rotate it to re-enable
Fix: press Rotate secret on the row. The new value is shown once and never again. This is also worth checking on a hook that used to work — it is the failure mode that looks most like the dispatcher is broken.
4. The destination is refused
Inline hook destinations go through the same outbound-address guard as everything else the platform sends, and it is checked at the socket rather than only on the name — so a hostname that resolved publicly at save time and privately later is still refused.
- Plain HTTP is refused at save time, with
Use https. This payload carries your people's email addresses and sign-in IP addresses; over plain http anyone on the path can read them. - A URL with no scheme is refused with
That is not a full URL. Include the scheme, for example https://example.com/hooks/qorionix. - Loopback, link-local, private and reserved addresses are refused entirely. The console says why:
Addresses inside private networks are refused — this platform sits next to cloud credentials and will not be used to reach them.
There is no per-tenant exception to the last one and there will not be. An outbound request to one of those ranges from inside our network is a route to credentials rather than to your service. If you need events inside a private network, terminate them on a public endpoint you control and forward inward — and verify the signature there, so nothing unauthenticated crosses your perimeter.
5. Your endpoint takes longer than five seconds
Five seconds is the whole budget, including TLS negotiation. It is half the budget an outbound webhook gets, because a hook fires on a login path and a slow one would be a slow product.
The shape of a correct receiver is the same as for any webhook: acknowledge immediately, queue the work, and process it afterwards. A receiver that writes to its own database before responding will start timing out under load, and there is no retry to catch what it misses.
One thing you do not need to worry about: dispatch is detached from the request that caused it, so a login completing does not cut a hook off mid-flight, and a slow hook does not hold a login open.
6. It is arriving and your verifier is rejecting it
The signing scheme is identical to outbound webhooks: the signed payload is the timestamp, a full stop, and the raw request body, MACed with HMAC-SHA256. Every rule in Verifying a webhook signature applies unchanged.
Three specific things go wrong here more than anything else:
- The body was parsed and re-serialised before it reached the MAC.
{"a":1}and{ "a": 1 }are the same object and different bytes, and only the bytes we sent will verify. Capture the raw body first, verify, then parse. - The receiver reads only one header name. Every header is sent twice — as
X-Qorionix-…and as the deprecatedX-ZentryQ-…, with identical values written by the same call. Read the new name and fall back to the old. Do not swap one literal for the other in a single deploy. - The secret was rotated and not installed. Rotation has no overlap window: the previous secret stops working the moment the new one is issued, and the confirmation says so —
Deliveries keep going out, but your receiver rejects every one of them until you install the new value there.
Note that there is no delivery id header on an inline hook and no retry header, because a hook is delivered once. Deduplicate on event_id in the body if you need to.
What you cannot see, and what to do about it
There is no delivery log for inline hooks and no per-hook success rate. The console shows what a hook is, where it points and whether it is active; it does not show you what happened to the last hundred attempts.
So the measurement has to be on your side. Two things worth doing when you build the receiver:
- Log every arrival with its
event_idandoccurred_at, before you verify anything. That gives you a record of what reached you, separate from what you accepted. - Reconcile on a schedule. Read the user list or the audit log over the API periodically and compare against what your hooks delivered. This is the boring design that survives a five-second budget with no retries, and it is what we would build ourselves.
If a hook is genuinely not being dispatched after you have worked through all six causes, tell us the hook's name, its event, and the time of an event you expected it to fire on. That is enough to find the dispatch in our logs.