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
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:
- You send a request. Call any async endpoint (e.g.
POST /v2/fetch/persons/live). - The API confirms instantly. You get back a
webhookIdthat uniquely identifies this request. Use it to match incoming callbacks back to your original request. - Processing happens in the background. The API fetches and prepares the data for you.
- 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
Workspace default (recommended)
Set a single HTTPS URL at workspace level so all async results are delivered automatically. No need to include webhookUrl in every request.
- Go to Settings > Webhooks
- Enter your HTTPS endpoint URL
- Click Save
- 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:
ngrok http 3000
Then use the forwarding URL in your request:
{
"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:
{
"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.
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:
{
"webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"errorCode": "..."
}
Result callback
When the job completes successfully, the callback carries the requested data in data:
{
"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 forerrorCodeinsidedatato distinguish errors from results. Ignore anything that doesn’t match. - Verify the webhookId. Store the
webhookIdfrom the initial response and check it against incoming callbacks. This is your correlation and validation key. - Respond quickly. Return a
200status 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
webhookIdto 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
webhookUrlfrom the request, verify that a workspace default URL is configured in Settings > Webhooks - Verify it accepts
POSTrequests with aContent-Type: application/jsonbody - Check your server/firewall logs for incoming requests
Timeout errors
- Your webhook endpoint must respond within 10 seconds
- If processing takes longer, return
200immediately 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
errorCodeindatawhen the result is not available (see Error codes) - Verify the URL does not require authentication or return redirects
Duplicate deliveries
- Use the
webhookIdto deduplicate on your side
Transient errors
fetch-data-erroris usually transient and safe to retry after a short delayresult-unavailablemeans 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