# 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)

```javascript
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/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ory-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ory-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ory-webhooks/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:

```jsonnet
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

```bash
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):

```bash
npx hookdeck-cli listen 3000 ory --path /webhooks/ory

```

Use the printed URL as the action's webhook URL.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ory-webhooks/references/overview.md) - What Ory Actions webhooks are, flows and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ory-webhooks/references/setup.md) - Create the action, auth config and Jsonnet body
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ory-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ory-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests