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_key header)
  • 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 (often Authorization); the expected value is auth.config.value, compared exactly and in full, including a Bearer prefix if you typed one. Don't strip or parse it.
  • Return 401 on a missing header or mismatch, 400 for a non-empty body that isn't a JSON object, 200 once 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: header is covered; in: cookie isn'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.

eventAction runs
registration.afterAfter an identity registers
login.afterAfter a successful login
settings.afterAfter an identity updates settings or traits
verification.afterAfter an address is verified
recovery.afterAfter 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →