Webhooks

7 min read

Push delivery for async enrichment results. Use when you already run a public HTTPS receiver and prefer a push model.

Tip

Most teams ship faster with Polling.

No public endpoint required, free to call, around 5 minutes to first result. The webhook setup below is documented for teams that already run a public HTTPS receiver and need real-time push delivery.

  • No public HTTPS endpoint required
  • Free: zero credits per poll
  • Off the clock: doesn’t count toward your per-minute rate limit

Start with Polling

Introduction

Webhooks deliver async enrichment results to a public HTTPS URL you operate. Use them when you already operate a public HTTPS receiver and prefer a push model.

For most use cases, polling is faster to integrate and equally cost-effective. Same endpoints, same data, same enrichment cost. See the Polling guide to compare both side by side.

How it works

Async endpoints work in the background so your application doesn’t have to wait. The API accepts your request immediately, processes it, and delivers the result to your server as soon as it’s ready.

This is what happens step by step:

  1. You send a request. Call any async endpoint (e.g. POST /v2/fetch/persons/live).
  2. The API confirms instantly. You get back a webhookId that uniquely identifies this request. Use it to match incoming callbacks back to your original request.
  3. Processing happens in the background. The API fetches and prepares the data for you.
  4. The result is delivered to you. As soon as the data is ready, the API sends a POST request to your webhook URL with the full JSON payload.

Important

Async by design, no synchronous SLA. We aim to POST each result in under 15 seconds, but processing can stretch to tens of minutes depending on conditions (source availability, queue load, retries). Live endpoints are built to update databases and feed async pipelines, not to back a synchronous, real-time request a user is waiting on.

Async endpoints

All of these endpoints deliver results via webhook:

Endpoint
Profiles
Person Profile Live POST /v2/fetch/persons/live
Company Profile Live POST /v2/fetch/companies/live

1. Configure your webhook URL

Set a single HTTPS URL at workspace level so all async results are delivered automatically. No need to include webhookUrl in every request.

  1. Go to Settings > Webhooks
  2. Enter your HTTPS endpoint URL
  3. Click Save
  4. Click Test to send a test payload and verify connectivity

Tip

The workspace default URL enables full delivery tracking in the Webhook Events dashboard. Note that delivery failures are not refunded: the data was fetched, so the credit is kept and the result stays retrievable via polling.

Per-request webhook URL override (optional)

Include a webhookUrl field in your request body to override the delivery target for a single request. This is the fastest way to test locally with a tunnel like ngrok:

Local tunnel
Bash
ngrok http 3000

Then use the forwarding URL in your request:

Request body
JSON
{
  "url": "https://social.com/in/janedoe",
  "webhookUrl": "https://a1b2c3d4.ngrok-free.app/webhooks/reversecontact"
}

If no webhook URL is available (neither in the request body nor in workspace settings), the API returns 422 immediately:

422 WEBHOOK_URL_REQUIRED
JSON
{
  "success": false,
  "data": null,
  "error": {
    "code": "WEBHOOK_URL_REQUIRED",
    "message": "A webhook URL is required to receive results. Either provide a \"webhookUrl\" in the request body, or configure a default webhook URL in your workspace settings."
  }
}

No credits are consumed. Configure a default URL in Settings > Webhooks or include webhookUrl in your request body.

2. Set up a receiver

Tip

Don’t already operate a public HTTPS receiver? Skip this entirely with Polling. You’ll trigger jobs the same way and pull results on demand, no server required.

A webhook receiver is a standard POST endpoint that accepts JSON and returns 200. That’s it.

Node.js / Express
const express = require("express");
const app = express();

app.use(express.json());

app.post("/webhooks/reversecontact", (req, res) => {
  const event = req.body;
  console.log("Webhook received:", event.webhookId);

  if (event.data) {
    // Process the result, e.g. from POST /v2/fetch/persons/live
    console.log("Profile:", event.data.firstName, event.data.lastName);
  }

  // Always respond with 200 to acknowledge receipt
  res.status(200).json({ received: true });
});

app.listen(3000, () => console.log("Listening on port 3000"));
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/webhooks/reversecontact", methods=["POST"])
def webhook():
    event = request.get_json()
    print("Webhook received:", event["webhookId"])

    if "data" in event:
        # Process the result, e.g. from POST /v2/fetch/persons/live
        print("Profile:", event["data"]["firstName"], event["data"]["lastName"])

    # Always respond with 200 to acknowledge receipt
    return jsonify({"received": True}), 200

if __name__ == "__main__":
    app.run(port=3000)

Webhook payload

Error callback

When something goes wrong, you receive a callback with an errorCode inside data describing the issue. The original input fields are echoed back alongside the error code:

Error callback
JSON
{
  "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "errorCode": "..."
}

Result callback

When the job completes successfully, the callback carries the requested data in data:

Result callback
JSON
{
  "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "data": {
    "...": "endpoint-specific result payload"
  }
}

Tip

Check for errorCode inside data to distinguish errors from successful results. If data.errorCode is present, the request failed. Otherwise treat it as the terminal success for that webhookId.

Enrichment error codes

These codes describe the enrichment outcome and arrive as data.errorCode in the callback:

Code Description
invalid-request The request input is malformed or doesn’t match the expected format
data-not-found No matching result was found
person-not-found The person profile doesn’t exist
company-not-found The company profile doesn’t exist
email-not-found No email could be resolved for this person
fetch-data-error The API failed while fetching data. Safe to retry
result-unavailable The enrichment completed but the result was lost on our side. Retry the job. If the request was charged, contact support to get the credit back.

Delivery error codes

These codes mean the enrichment was fetched successfully but the result could not be pushed to your webhook URL. The job is reported as succeeded, the result is still available, and the failed push is named by a deliveryStatus field (set to one of the codes below) on the poll and check_task responses.

Code Billed Description
webhook-url-invalid Yes Your webhook returned a 4xx response
webhook-url-errored Yes Your webhook returned a 5xx or a network error
webhook-url-timeout Yes Your webhook did not respond in time
webhook-url-unreachable Yes The webhook URL could not be reached from production

Note

Delivery failures are billed, not refunded: the data was successfully fetched, so the credit is kept. The job stays succeeded and the result is populated, poll GET /v2/webhooks/:webhookId to retrieve it. The deliveryStatus field tells you the push failed (and why) without flipping the job to errored.

Security best practices

  • Use HTTPS. The API rejects plain HTTP URLs for webhook delivery.
  • Validate the payload shape. Every callback contains webhookId + data. Check for errorCode inside data to distinguish errors from results. Ignore anything that doesn’t match.
  • Verify the webhookId. Store the webhookId from the initial response and check it against incoming callbacks. This is your correlation and validation key.
  • Respond quickly. Return a 200 status code promptly. If you need to do heavy processing, acknowledge receipt first and process asynchronously.

Dashboard monitoring

The Webhook Events page in your dashboard gives you full visibility into every webhook delivery:

  • Status distribution. A summary showing succeeded, errored, and in-progress events at a glance.
  • Event log. Each row displays the timestamp, webhookId, status, credits consumed, credits refunded, and error code (if any).
  • Filters. Narrow down events by status, error code, or webhookId to quickly find specific deliveries.

Go to production

Before going live, make sure your webhook integration is solid:

  • Deduplicate on webhookId. In rare cases, a callback may be delivered more than once.
  • Log the webhookId. Include it in your application logs to trace requests end-to-end.
  • Monitor your credits. Credits are deducted when the request is accepted, before the result is delivered. Refundable errors are automatically refunded. See Rate limits & credits for details.

For the full hardening checklist, see Production checklist.

Troubleshooting

Not receiving webhooks

  • Ensure your endpoint is publicly accessible from the internet (not just localhost)
  • If you omitted webhookUrl from the request, verify that a workspace default URL is configured in Settings > Webhooks
  • Verify it accepts POST requests with a Content-Type: application/json body
  • Check your server/firewall logs for incoming requests

Timeout errors

  • Your webhook endpoint must respond within 10 seconds
  • If processing takes longer, return 200 immediately and handle the data asynchronously

Receiving an error or not-found callback instead of data

  • This is expected. The API sends a callback with an errorCode in data when the result is not available (see Error codes)
  • Verify the URL does not require authentication or return redirects

Duplicate deliveries

  • Use the webhookId to deduplicate on your side

Transient errors

  • fetch-data-error is usually transient and safe to retry after a short delay
  • result-unavailable means the enrichment completed but the result was lost on our side. Retry the job. If the request was charged, contact support to get the credit back

Previous

Polling, zero setup

Next

Current position and work history ordering