Agent skill

Cloudentity (SecureAuth) Webhooks Skill

Receive and authenticate Cloudentity (SecureAuth) webhooks. Use when setting up a Cloudentity or SecureAuth Connect webhook endpoint, checking the X-API-Key header, debugging 401s, or handling batched audit events like user.created, user.authenticated, group.created and client.created.

Install this skill

npx skills add hookdeck/webhook-skills --skill cloudentity-webhooks


Cloudentity is now SecureAuth (cloudentity.com redirects to secureauth.com); this skill keeps the cloudentity name. SecureAuth Connect workspace webhooks send batches of audit events as JSON ({ "audit_events": [ ... ] }) when subscribed events happen, such as user.created or user.authenticated. Webhooks are not signed. Each request carries an X-API-Key header with the Webhook API Key, generated by SecureAuth and shown once when you create the webhook.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the key if it leaks.

When to Use This Skill

  • How do I receive Cloudentity or SecureAuth webhooks?
  • How do I verify the X-API-Key header on a SecureAuth Connect webhook?
  • Does Cloudentity sign webhook payloads? (No: use the Webhook API Key)
  • How do I handle user.created, user.deleted or login.failed audit events?
  • Why does my Cloudentity 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 verifyCloudentityRequest(headers, apiKey = process.env.CLOUDENTITY_WEBHOOK_API_KEY) {
  if (!apiKey) return false;                // never accept when unconfigured
  const received = headers['x-api-key'];    // whole value, plain text
  return Boolean(received) && safeEqual(received, apiKey);
}
  • Header: X-API-Key: <Webhook API Key> (case-insensitive name, whole value compared).
  • Python: hmac.compare_digest(received.encode(), key.encode()) on bytes, never on str.
  • Return 401 on a missing or wrong key, 400 for a body without an audit_events array, 200 once handled.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

Event identifiers use a subject.action form (user.deleted); the docs say the payload delivers the matching field in subject_action form (user_deleted). The docs don't name the field that carries the event type. The external-logging guide reads audit_events.payload.login_failed.cause, so the examples infer that each audit event's payload object holds one key named for the event and dispatch on that key. This is an inference, not documented: confirm it against a real delivery. Available events depend on the server type.

EventSent When
user.createdA user is created
user.updatedA user is updated
user.deletedA user is deleted (also at workspace and organization level)
user.authenticatedA user authenticates
group.createdA group is created
login.accepted / login.failedA sign-in attempt succeeds / fails
consent.created / consent.revokedAn Open Banking, CDR or FDX consent is created / revoked
client.createdAn application (client) is created

Other documented events include access_token.issued, password.updated, otp.sent and organization.created. See references/overview.md.

Environment Variables

CLOUDENTITY_WEBHOOK_API_KEY=your-webhook-api-key   # shown once when the webhook is created

Local Development

SecureAuth can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 cloudentity --path /webhooks/cloudentity

Use the printed URL as the webhook URL in SecureAuth.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →