Skip to content

Integration guides

Polling, zero setup

Updated

The recommended way to retrieve async enrichment results. Trigger a job, pull the result on demand, free of charge, no public HTTPS endpoint required.

All live endpoints run in the background. They reply immediately with a webhookId, then deliver the actual result once it's ready, usually within seconds. The Contact Email Finder works the same way.

Polling is the recommended way to retrieve that result: trigger a job, then pull the result on demand by calling GET /v2/webhooks/:webhookId until it's ready.

It works without any infrastructure on your side: no public URL to expose, no receiver to deploy, no tunnel to maintain. The same delivery model fits backend services, batch pipelines, mobile apps, CLI tools, notebooks, and local development.

Trigger and poll

Trigger any async endpoint, save the webhookId it returns, then call GET /v2/webhooks/:webhookId until the status is succeeded. That's the entire integration.

Trigger and poll

const KEY = "YOUR_API_KEY";
const BASE = "https://api.reversecontact.com";

const trigger = await fetch(`${BASE}/v2/fetch/persons/live`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ url: "https://social.com/in/janedoe" })
});
const { data: { webhookId } } = await trigger.json();

while (true) {
  const res = await fetch(`${BASE}/v2/webhooks/${webhookId}`, {
    headers: { "Authorization": `Bearer ${KEY}` }
  });
  const { data } = await res.json();
  if (data.status === "succeeded") { console.log(data.result); break; }
  if (data.status === "errored")   throw new Error(data.errorCode ?? "Job failed");
  await new Promise(r => setTimeout(r, 2000));
}

Which delivery method should you use?

Text
Do you already run a public HTTPS receiver?
   |
   +-- NO   -> Polling (recommended)
   |
   +-- YES  -> Polling (recommended), or Webhooks if you prefer push delivery

Polling works at any volume and is the simplest path for every team. Webhooks are an alternative for teams that already operate a public HTTPS receiver and prefer a push model.

Polling vs webhooks

Both handle the same volumes, the same data, and the same enrichment cost. The only real difference is the delivery model (pull vs push) and the trade-offs that come with it.

CriterionPolling (recommended)Webhooks (advanced)
Setup timeunder 1 min30 to 60 min
Public HTTPS endpointnot requiredrequired
Local dev, mobile, CLIworks out of the boxneeds ngrok or a tunnel
Cost per status check0 credits0 credits
Volume capacityunlimitedunlimited

Same endpoints, same data, same enrichment cost. Only the delivery mechanism differs.

When to use polling

Polling fits the vast majority of integrations:

  • Backend services and APIs. Server-to-server calls where you want the simplest possible delivery model, with one less moving part to monitor.
  • Batch jobs and pipelines. A pipeline step that needs to block on the enrichment result as if the call were synchronous, without standing up a callback receiver.
  • Internal tools behind a firewall or VPN. Admin consoles or back-office systems with no public surface.
  • Mobile, desktop, CLI, notebooks. Anything without a backend to receive callbacks.
  • Compliance-constrained environments. Security posture that disallows exposing inbound webhook receivers, or restricts which services can receive third-party callbacks.
  • Local development and prototyping. Skip ngrok / Cloudflare Tunnel entirely while you're iterating.

Webhooks are an alternative when you already operate a public HTTPS receiver and prefer a push model. For everything else, polling is faster to integrate and equally cost-effective at any volume.

Polling and webhook callbacks are not mutually exclusive. You can use both, e.g. webhooks as the primary delivery channel and polling as a fallback for status checks. See Combining polling with a webhookUrl at the end of this guide.

How it works

  1. Create a job. Call any async endpoint (e.g. POST /v2/fetch/persons/live). You can omit webhookUrl from the request body when you intend to retrieve the result by polling.
  2. Receive the webhookId. The API responds immediately with { status: "created", webhookId, pollUrl }.
  3. Poll for the result. Call GET /v2/webhooks/:webhookId periodically until status is "succeeded" or "errored".
  4. Read the data. When status is "succeeded", the result field contains the enrichment payload.

Step 1: Trigger a job

const res = await fetch("https://api.reversecontact.com/v2/fetch/persons/live", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    url: "https://linkedin.com/in/williamhgates"
  })
})
const { data } = await res.json()
console.log(data.webhookId, data.pollUrl)

The response includes a pollUrl hint pointing to the polling endpoint:

JSON

Initial Response

{
  "success": true,
  "data": {
    "status": "created",
    "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "pollUrl": "/v2/webhooks/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
  },
  "error": null
}

Step 2: Poll for the result

Call GET /v2/webhooks/:webhookId until you receive a terminal status (succeeded or errored).

async function pollUntilDone(webhookId) {
  while (true) {
    const res = await fetch(`https://api.reversecontact.com/v2/webhooks/${webhookId}`, {
      headers: { "Authorization": "Bearer YOUR_API_KEY" }
    })
    const { data } = await res.json()
    if (data.status === "succeeded") return data.result
    if (data.status === "errored")  throw new Error(data.errorCode ?? "Job failed")
    await new Promise(r => setTimeout(r, 2000)) // wait 2s before next poll
  }
}

Response shapes

While the job is still running:

JSON

in_progress

{
  "success": true,
  "data": {
    "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "status": "in_progress",
    "eventType": "FETCH_PERSON_DATA",
    "errorCode": null,
    "createdAt": "2026-04-08T12:43:02.017Z",
    "result": null,
    "creditsConsumed": 0
  }
}

When the job completes successfully:

JSON

succeeded

{
  "success": true,
  "data": {
    "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "status": "succeeded",
    "eventType": "FETCH_PERSON_DATA",
    "errorCode": null,
    "createdAt": "2026-04-08T12:43:02.017Z",
    "result": {
      "firstName": "Bill",
      "lastName": "Gates",
      "headline": "Chair, Gates Foundation and Founder, Breakthrough Energy",
      "...": "endpoint-specific result payload"
    },
    "creditsConsumed": 2
  }
}

When the job fails:

JSON

errored

{
  "success": true,
  "data": {
    "webhookId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
    "status": "errored",
    "eventType": "FETCH_PERSON_DATA",
    "errorCode": "person-not-found",
    "createdAt": "2026-04-08T12:43:02.017Z",
    "result": null,
    "creditsConsumed": 0
  }
}

creditsConsumed: credits billed for this job so far. 0 while the job is in progress; the final amount once it has completed.

Polling frequency

The polling endpoint is free (zero credits) and does not count against your per-minute rate limit. That said, please poll responsibly:

Use caseRecommended interval
Interactive UI (a user is waiting)every 2 seconds
Background job (batch processing)every 5 to 10 seconds
Long-running enrichmentevery 30 seconds with exponential backoff

Most jobs complete within a few seconds. Polling more aggressively than once per second offers no real benefit and increases load on your application.

Inspect and retrieve results from the dashboard

If you don't want to write code yet, or you just need to check a single job, you can use Job Lookup directly from the dashboard:

  1. Open Logs › Job Lookup from the sidebar.
  2. Paste any webhookId returned by an async endpoint.
  3. Watch the job status update in real time, inspect the full result payload, and download it as JSON.

You can also jump straight to Job Lookup from the Webhook Events page. Every row has an action button on the right that opens the lookup pre-filled for that event.

This is the fastest way to test the polling experience end-to-end without writing a single line of integration code, and a convenient tool for support, ops, and manual debugging sessions. It uses the exact same API under the hood, so what you see in the dashboard is exactly what your code will receive from GET /v2/webhooks/:webhookId.

Result retention

Polling results are retained for 72 hours by default, counted from the moment the job is created (not from when it completes). After that window, the record and its stored payload are purged and the polling endpoint returns a WEBHOOK_EVENT_EXPIRED error.

Error responses

StatusCodeWhen
404WEBHOOK_EVENT_NOT_FOUNDThe webhookId doesn't exist or doesn't belong to your workspace
410WEBHOOK_EVENT_EXPIREDThe result has expired and is no longer retrievable

Combining polling with a webhookUrl

If you provide a webhookUrl in the request body, the API will deliver the result to that URL and the same result remains retrievable via polling. This is useful when:

  • You want webhooks as the primary path but need a fallback mechanism in case a delivery is missed.
  • You want to inspect or re-fetch a result after the original webhook delivery.
  • You're migrating from webhooks to polling and want to validate parity before switching.