Agent skill

Wiza Webhooks Skill

Receive and authenticate Wiza webhooks. Use when setting up a Wiza webhook endpoint, verifying the x-auth-key header, debugging 401s, or handling individual_reveal results for finished (200) and failed (400) lookups.

Install this skill

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


Wiza is a B2B contact data platform. When a contact lookup (an individual reveal) completes, Wiza POSTs the result as JSON to your webhook URL. Webhooks are not signed. Every request carries an x-auth-key header containing a SHA-256 hash of your Wiza API key, and your endpoint authenticates the request by recomputing that hash and comparing.

When to Use This Skill

  • How do I receive Wiza webhooks?
  • How do I verify the x-auth-key header on a Wiza webhook?
  • Does Wiza sign webhook payloads? (No: it sends a hash of your API key)
  • How do I handle individual_reveal results, finished or failed?
  • Why does my Wiza webhook endpoint return 401 after I rotated the API key?

Verification (core)

const crypto = require('crypto');

function verifyWizaRequest(headers, apiKey = process.env.WIZA_API_KEY) {
  if (!apiKey) return false;                       // never accept when unconfigured
  const received = headers['x-auth-key'];          // lowercase hex SHA-256 of the API key
  if (!received) return false;
  const expected = crypto.createHash('sha256').update(apiKey, 'utf8').digest('hex');
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);  // lengths first: it throws otherwise
}
  • Header: x-auth-key: <lowercase hex SHA-256 of your API key> (header name is case-insensitive). Compare the whole value; nothing is stripped or prefixed.
  • Python: hmac.compare_digest(received.encode(), expected.encode()). Pass bytes, never str, which raises TypeError on non-ASCII input.
  • Return 401 on a missing header or mismatch, 400 for a body that isn't a JSON object, 200 once handled. Non-2xx responses are retried up to 3 times.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.
  • This is a static shared credential. It is the same on every request. It authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks. Rotating the Wiza API key changes the header value, so update your configuration at the same time.

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

Event Types

The payload is { status: { code, message }, type, data }; there is no top-level id (the reveal's id is data.id). The type documented for webhooks is individual_reveal; status.code says how the lookup ended.

typestatus.codeMeaning
individual_reveal200The reveal finished; data holds the result
individual_reveal400The reveal failed

Environment Variables

WIZA_API_KEY=your-wiza-api-key   # Settings > API

The examples hash this key and compare the digest with x-auth-key.

Using Hookdeck

Enter the lowercase hex SHA-256 hash of your Wiza API key, as sent in x-auth-key, not the API key itself. Compute it with:

printf %s "$WIZA_API_KEY" | sha256sum
# or
node -e "console.log(require('crypto').createHash('sha256').update(process.env.WIZA_API_KEY).digest('hex'))"

Local Development

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

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

Use the printed URL as the webhook URL in Wiza.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →