> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wiza.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time notifications when enrichment requests complete

Wiza sends webhook notifications to your server when enrichment requests complete. This lets you process results asynchronously without polling the API.

When a request finishes (or fails), Wiza sends a `POST` request to your configured URL with the enrichment results.

***

## Setting Up Your Webhook URL

Configure your default webhook URL in [Settings → API](https://wiza.co/app/settings/api). This URL applies to all API requests made with that key.

You can also enable **Send All Callbacks** to receive webhooks for all enrichment activity on your account, not just requests made through the API.

<Note>
  Your webhook endpoint must be publicly accessible and return a `200` status code to acknowledge receipt.
</Note>

***

## Handling Webhook Requests

Respond quickly with a `200` status code to acknowledge receipt. If your processing takes time, accept the webhook first and handle the work asynchronously in a background job.

Use `status.code` to check for success (`200`) or failure (`400`). For the full response schema, see the [Individual Reveal](/using-the-api/find-email-addresses#step-2-retrieve-reveal-results) documentation.

<CodeGroup>
  ```javascript JavaScript theme={"dark"}
  app.post('/webhook', (req, res) => {
    // Acknowledge receipt immediately
    res.status(200).send('OK');

    // Process asynchronously
    const { status, data } = req.body;

    if (status.code === 200) {
      processReveal(data);
    }
  });
  ```

  ```python Python theme={"dark"}
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route('/webhook', methods=['POST'])
  def wiza_webhook():
      payload = request.get_json()
      status = payload.get('status', {})
      data = payload.get('data')

      if status.get('code') == 200:
          process_reveal(data)

      # Acknowledge receipt immediately
      return jsonify({'status': 'ok'}), 200
  ```

  ```ruby Ruby theme={"dark"}
  post '/webhook' do
    payload = JSON.parse(request.body.read)
    payload_status = payload['status']
    data = payload['data']

    if payload_status['code'] == 200
      process_reveal(data)
    end

    # Acknowledge receipt immediately
    status 200
    { status: 'ok' }.to_json
  end
  ```
</CodeGroup>

***

## Overriding the Webhook URL Per Request

You can override the default webhook URL on a per-request basis by including a `callback_url` parameter in your **Start Individual Reveal** request.

```json theme={"dark"}
{
  "individual_reveal": {
    "profile_url": "https://www.linkedin.com/in/stephen-hakami-5babb21b0/"
  },
  "enrichment_level": "partial",
  "callback_url": "https://your-server.com/webhook"
}
```

***

## Webhook Payload

**Endpoint:** `POST {your_webhook_url}`

Wiza sends a webhook when an Individual Reveal completes or fails. The request is a `POST` with a `Content-Type: application/json` header sent to your configured URL or the `callback_url` specified in the request. The `data` object contains the same enrichment results as the corresponding GET endpoint.

**Individual Reveal Payload:**

```json theme={"dark"}
{
  "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",
    "location": "Toronto, Ontario, Canada",
    "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,
      "api_credits": {
        "total": 2,
        "email_credits": 2,
        "phone_credits": 0,
        "scrape_credits": 1
      }
    }
  }
}
```

**Failed Request Payload:**

When a request fails, the `status.code` will be `400`. The `data` object contains the same fields as a successful response, with a `fail_error` field indicating the failure reason.

```json theme={"dark"}
{
  "status": {
    "code": 400,
    "message": "Single reveal failed"
  },
  "type": "individual_reveal",
  "data": {
    "id": 123456,
    "status": "failed",
    "fail_error": "..."
  }
}
```

***

## Verifying Webhook Signatures

Webhook requests include an `x-auth-key` header containing a SHA256 hash of your API key. Use this to verify the request originated from Wiza.

<CodeGroup>
  ```javascript JavaScript theme={"dark"}
  const crypto = require('crypto');

  function verifyWebhook(request, apiKey) {
    const receivedHash = request.headers['x-auth-key'];
    const expectedHash = crypto
      .createHash('sha256')
      .update(apiKey)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(receivedHash),
      Buffer.from(expectedHash)
    );
  }
  ```

  ```python Python theme={"dark"}
  import hashlib
  import hmac

  def verify_webhook(headers, api_key):
      received_hash = headers.get('x-auth-key')
      expected_hash = hashlib.sha256(api_key.encode()).hexdigest()
      return hmac.compare_digest(received_hash, expected_hash)
  ```

  ```ruby Ruby theme={"dark"}
  require 'digest'
  require 'rack/utils'

  def verify_webhook(request, api_key)
    received_hash = request.headers['x-auth-key']
    expected_hash = Digest::SHA256.hexdigest(api_key)
    Rack::Utils.secure_compare(received_hash, expected_hash)
  end
  ```
</CodeGroup>

***

## Retry Behavior

If your webhook endpoint returns a non-2xx response, Wiza will retry up to 3 times. Ensure your endpoint is available and returns a `200` status code to acknowledge receipt.

<Note>
  If a webhook consistently fails (returns 404 or 403), Wiza will stop retrying for that request.
</Note>

***

## Related

* [Status Codes & Errors](/using-the-api/status-codes-and-errors) — Handle API responses
* [Authorization](/using-the-api/authorization) — Authenticate your API requests
