Gareth Wilson Gareth Wilson

Guide to Wiza Webhooks: Features and Best Practices

Published


Wiza webhooks deliver contact data when it's ready. You ask Wiza to find the work email and other contact details behind a LinkedIn profile, and instead of polling for the result, Wiza sends it to your endpoint when the reveal finishes or fails, ready for your CRM, outreach tool or enrichment pipeline.

This guide covers how Wiza webhooks are configured, the payload, what the x-auth-key header is and how to check it, Wiza's retry behaviour, and the practices that keep a Wiza receiver dependable.

What are Wiza webhooks?

Wiza enrichment is asynchronous. You start an Individual Reveal through the API, and "when a request finishes (or fails), Wiza sends a POST request to your configured URL with the enrichment results". The URL is either the default set for your API key, or a callback_url you pass on the request.

Wiza's documentation calls its check "Verifying Webhook Signatures", but nothing is computed over the body. Every request carries an x-auth-key header "containing a SHA256 hash of your API key". Wiza's samples produce it as lowercase hex. The value is the same on every request until your API key changes, so it works like a fixed shared credential: your receiver computes the hash once and compares the header with it.

Wiza webhook features

FeatureDetails
Default URLSettings > API, applies to every request made with that key
Per-request URLcallback_url on the Start Individual Reveal request
All activitySend All Callbacks covers enrichment activity outside the API too
Method and formatPOST with Content-Type: application/json
Event identifiertype, documented as individual_reveal, with status.code
Authenticationx-auth-key: the lowercase hex SHA-256 of your API key. No body signature
AcknowledgementReturn 200
RetriesUp to 3 for non-2xx responses. Consistent 404 or 403 stops retries for that request
Source IPsNot published

Common events

Wiza documents one webhook type, individual_reveal, sent when a reveal finishes or fails. status.code tells you which:

status.codestatus.messageMeaning
200Single reveal finishedThe reveal finished. data holds the result
400Single reveal failedThe reveal failed. data.fail_error gives the reason

Here is Wiza's example of a finished reveal, trimmed:

{
  "status": { "code": 200, "message": "Single reveal finished" },
  "type": "individual_reveal",
  "data": {
    "id": 123456,
    "status": "finished",
    "is_complete": true,
    "name": "Stephen Hakami",
    "company": "Wiza",
    "enrichment_level": "partial",
    "linkedin_profile_url": "https://www.linkedin.com/in/stephen-hakami-5babb21b0/",
    "title": "Founder, Chief Executive Officer",
    "email": "stephen@wiza.co",
    "email_type": "work",
    "email_status": "valid",
    "emails": [{ "email": "stephen@wiza.co", "email_type": "work", "email_status": "valid" }],
    "credits": { "email_credits": 1, "phone_credits": 0, "export_credits": 0 }
  }
}

data contains the same fields as the Individual Reveal GET endpoint, and data.id is the reveal's ID, the key that ties the webhook back to the request you made. With Send All Callbacks on, Wiza sends webhooks for all enrichment activity, but only the individual_reveal payload is documented, so log and acknowledge any other type you receive.

Setting up Wiza webhooks

  1. In Wiza, go to Settings > API and enter your HTTPS endpoint as the default webhook URL. Turn on Send All Callbacks if you want activity from outside the API too.

  2. Compute the lowercase hex SHA-256 of your API key, which is what x-auth-key will contain:

    printf %s "$WIZA_API_KEY" | sha256sum | cut -d' ' -f1
    
  3. To send a single request elsewhere, pass callback_url when you start the reveal:

    {
      "individual_reveal": {
        "profile_url": "https://www.linkedin.com/in/stephen-hakami-5babb21b0/"
      },
      "enrichment_level": "partial",
      "callback_url": "https://example.com/webhooks/wiza"
    }
    

In Hookdeck. Create a source with the Wiza type and use its URL as the webhook URL or callback_url. In Hashed API Key, enter the lowercase hex SHA-256 hash of your Wiza API key, as sent in x-auth-key, not the API key itself. Hookdeck compares x-auth-key with that value exactly, so an uppercase hash or the raw key won't match. The source accepts POST. If you rotate your Wiza API key, compute the new hash and update the source at the same time.

For local development, hookdeck listen 3000 wiza --path /webhooks/wiza with the Hookdeck CLI gives you a public HTTPS URL, with no account required.

Securing Wiza webhooks

Hash your API key once at startup, then compare x-auth-key with it in constant time on each request. Wiza's own Node sample passes the buffers straight to crypto.timingSafeEqual, which throws when the lengths differ, so check lengths first.

const crypto = require('crypto');
const express = require('express');

const app = express();

const apiKey = process.env.WIZA_API_KEY;
const expectedHash = apiKey
  ? crypto.createHash('sha256').update(apiKey, 'utf8').digest('hex')
  : null;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on buffers of different lengths
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post('/webhooks/wiza', express.raw({ type: '*/*' }), (req, res) => {
  if (!expectedHash) return res.sendStatus(500); // fail closed when unconfigured

  const received = req.get('x-auth-key');
  if (typeof received !== 'string' || !safeEqual(received, expectedHash)) {
    return res.sendStatus(401);
  }

  let payload;
  try {
    payload = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Queue the result keyed on payload.data.id. status.code 200 = finished, 400 = failed.
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac
import os


def verify_wiza_request(headers) -> bool:
    api_key = os.environ.get("WIZA_API_KEY")
    if not api_key:
        return False  # fail closed
    received = headers.get("x-auth-key")
    if not received:
        return False
    expected = hashlib.sha256(api_key.encode("utf-8")).hexdigest()
    return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))

Return 401, not 403 or 404, when the header doesn't match. Wiza stops retrying requests that consistently get 404 or 403, which you don't want for a misconfiguration you're about to fix.

A matching header shows the sender knows a value derived from your API key. The hash doesn't reveal the key, but it's the same on every request and covers nothing in the body, so a captured request can be replayed or have its body changed and still pass. Use HTTPS and keep the hash out of logs. Rotating the API key is the only way to change it.

Make Wiza webhooks production-ready. Hookdeck Event Gateway checks x-auth-key on every Wiza webhook, answers with a 200 straight away, and keeps every reveal result for replay.

Wiza webhook limitations and pain points

Three retries, then the result is gone

The Problem: If your endpoint is down for longer than Wiza's retries take, reveal results never arrive.

Why It Happens: "If your webhook endpoint returns a non-2xx response, Wiza will retry up to 3 times." Wiza doesn't publish the interval, and a consistent 404 or 403 stops retries early.

Workarounds:

  • Acknowledge with 200 immediately and process in the background, as Wiza recommends.
  • Poll the Individual Reveal GET endpoint for reveals you started but never heard back about.

How Hookdeck Can Help: Hookdeck accepts each Wiza webhook as it arrives, so the three retries only have to reach Hookdeck. Hookdeck then retries delivery to your service on your own schedule and lets you replay any result.

A key-derived credential, not a signature

The Problem: Anyone who sees one request's x-auth-key can send convincing fake results, and you can't change the value without rotating your API key.

Why It Happens: The header is a hash of the API key and nothing else. It's identical on every request.

Workarounds:

  • Serve the endpoint over HTTPS only and keep headers out of logs.
  • Rotate the API key if the hash may have leaked, and update the expected hash at the same time.
  • For results that drive important actions, fetch the reveal from the API by data.id before acting.

How Hookdeck Can Help: Hookdeck's Wiza source compares x-auth-key with the hash you enter. Hookdeck answers Wiza with a 200 either way, then records a request without the right header as failed verification, so it never reaches your destination.

Failed reveals arrive as webhooks too

The Problem: A handler that assumes every webhook carries an email address breaks on failures.

Why It Happens: Failed reveals are delivered with status.code 400 and a much smaller data object with fail_error.

Workarounds:

  • Branch on status.code before reading data.
  • Record failures against the reveal ID so you don't retry them blindly.

How Hookdeck Can Help: Hookdeck filters can route finished and failed reveals to different destinations based on status.code.

Best practices

Compute the hash once

Hash the API key at startup, compare in constant time, and fail closed if the key isn't configured.

Deduplicate on data.id

Retries can deliver the same result more than once. Store the reveal ID with its outcome and skip repeats. See our guide to webhook idempotency.

Use callback_url to route by workflow

Different pipelines can pass different callback URLs, or query parameters on one URL, so each result lands in the right place without inspecting the body.

Acknowledge fast and process asynchronously

Wiza asks for a quick 200. Verify, store, respond, then write to your CRM. See why to process webhooks asynchronously.

Conclusion

Wiza sends a JSON POST when a reveal finishes or fails, with x-auth-key, the lowercase hex SHA-256 of your API key, as the only credential. Hash the key once and compare in constant time over HTTPS, branch on status.code, deduplicate on data.id, and acknowledge quickly, because Wiza gives up after three retries.

Hookdeck Event Gateway sits in front of your receiver, checks x-auth-key, accepts each result straight away, and keeps it until your service has processed it.

Get started with Hookdeck for free and handle Wiza webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.