Agent skill
Ory Webhooks Skill
Receive and authenticate Ory Actions webhooks (Ory Network identity flows). Use when setting up an endpoint for Ory webhooks, authenticating the api_key header you configured, debugging 401s, or routing on an event name you add to the Jsonnet request body.
Install this skill
npx skills add hookdeck/webhook-skills --skill ory-webhooks
Ory Network Actions can call a webhook when an identity flow runs: registration, login, settings, verification or recovery, before or after the flow. Ory does not sign these requests. You authenticate them with an api_key credential you configure on the action: a header name and value of your choosing, which Ory sends on every request. Your endpoint compares that header with the value it has stored.
This skill covers non-blocking webhooks (response.ignore: true): Ory fires the request and ignores the response. Blocking webhooks (response.parse: true), where your response can change the identity or interrupt the flow, are out of scope.
When to Use This Skill
- How do I receive Ory Actions webhooks?
- How do I authenticate an Ory webhook? Does Ory sign webhook payloads? (No: use the
api_keyheader) - How do I tell a registration from a login or settings webhook? (Ory adds no event name: add your own)
- What is in the request body? (Whatever your Jsonnet template produces)
- Why does my Ory webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyOryRequest(headers,
secret = process.env.ORY_WEBHOOK_SECRET,
headerName = process.env.ORY_WEBHOOK_HEADER || 'Authorization') {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()]; // Node lowercases header names
return Boolean(received) && safeEqual(received, secret); // WHOLE value, no "Bearer " parsing
}
- The header is whatever you set in the action's
auth.config.name(oftenAuthorization); the expected value isauth.config.value, compared exactly and in full, including aBearerprefix if you typed one. Don't strip or parse it. - Return
401on a missing header or mismatch,400for a non-empty body that isn't a JSON object,200once handled. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the credential if it leaks.
- Read the body raw (
express.raw({ type: '*/*' })), authenticate, then parse the JSON. - Only
in: headeris covered;in: cookieisn't.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Request Method and Body
You choose the HTTP method (POST, PUT, PATCH, even GET) when you create the action, so the examples accept all of them and treat an empty body as valid. The body is your own Jsonnet template over ctx (ctx.identity, ctx.flow, ctx.request_headers, ...). Ory has no fixed payload or event field. The examples assume this illustrative template, which adds an event field so one endpoint can serve several actions:
function(ctx) { event: 'registration.after', identity: ctx.identity }
Event Names (illustrative)
These come from the template above, not from Ory. They name the flow and when it runs.
event | Action runs |
|---|---|
registration.after | After an identity registers |
login.after | After a successful login |
settings.after | After an identity updates settings or traits |
verification.after | After an address is verified |
recovery.after | After an account recovery completes |
Environment Variables
ORY_WEBHOOK_HEADER=Authorization # auth.config.name (default: Authorization)
ORY_WEBHOOK_SECRET=your-secret # auth.config.value, the exact whole header value
Local Development
Ory can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 ory --path /webhooks/ory
Use the printed URL as the action's webhook URL.
Resources
- references/overview.md - What Ory Actions webhooks are, flows and payload shape
- references/setup.md - Create the action, auth config and Jsonnet body
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests