Agent skill

PingOne Webhooks Skill

Receive and authenticate PingOne subscription webhooks. Use when building an endpoint for PingOne audit events, securing it with the header you set on the subscription (httpEndpoint.headers), handling batched JSON_ARRAY or ND_JSON bodies, debugging 401s, or handling events like USER.CREATED, USER.UPDATED and AUTHENTICATION.CREATED.

Install this skill

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


PingOne (Ping Identity) pushes audit events to your app through subscriptions (webhooks). PingOne does not sign these requests. Instead, you add a header to the subscription (httpEndpoint.headers), PingOne sends it on every delivery, and your endpoint checks it.

When to Use This Skill

  • How do I receive PingOne webhooks?
  • How do I authenticate PingOne subscription requests? (There is no signature: use a header you set)
  • How do I handle a batch of events (JSON array or ND_JSON) from PingOne?
  • How do I handle USER.CREATED, USER.UPDATED or AUTHENTICATION.CREATED events?
  • Why does PingOne keep retrying my webhook endpoint?

Verification (core)

Paste the exact header value you set on the subscription into PINGONE_WEBHOOK_SECRET, and the header name into PINGONE_WEBHOOK_HEADER (default Authorization). The value is whatever you typed, so compare the whole value; do not strip or parse a Bearer or Basic prefix.

const crypto = require('crypto');

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); // equal-length buffers: never throws
}

function verifyPingOneRequest(headers, config = process.env) {
  const secret = config.PINGONE_WEBHOOK_SECRET;
  if (!secret) return false;                      // never accept when unconfigured
  const name = (config.PINGONE_WEBHOOK_HEADER || 'Authorization').toLowerCase();
  const received = headers[name];                 // Node lowercases header names
  return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}

Return 401 for a missing header or a mismatch, 400 for a body that isn't a JSON array or ND_JSON, and 200 once handled. In Python use hmac.compare_digest on bytes (a.encode()), never str, which raises on non-ASCII.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the header value on the subscription if it leaks. Dedupe on each event's id if replays matter.

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

Body Format

With the ACTIVITY format, the body is a batch of audit events (up to 500) in one of two shapes:

  • JSON_ARRAY: [{...}, {...}]
  • ND_JSON: one JSON event per line

The examples handle both. Read the raw body (in Express, express.raw({ type: '*/*' }) so any content type works) and parse it only after the credential check. PingOne retries the current event until it gets a 200, so return 200 only once you've handled the batch.

Event Types

Each event has action.type. PingOne documents many; the examples handle these:

action.typeSent When
USER.CREATEDA user is created
USER.UPDATEDA user is updated
USER.DELETEDA user is deleted
AUTHENTICATION.CREATEDAn authentication is recorded
PASSWORD.RESETA password is reset

Choose the ones you receive with filterOptions.includedActionTypes on the subscription. See the Audit Reporting Events reference for the full list.

Environment Variables

PINGONE_WEBHOOK_SECRET=replace-with-the-exact-header-value   # whole value, incl. any "Bearer " prefix
PINGONE_WEBHOOK_HEADER=Authorization                         # header name you set (case-insensitive)

Local Development

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

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

When using Hookdeck's PingOne source, the setup note is the same: paste the exact header value you set on the subscription.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →