View as Markdown

Create Webhook

POST/v2/webhooks

Subscribes an https URL on a public host to event types. Answers 201 with the signing secret, shown only here; subscribing the same URL to the same events again, with the same credential, answers 200 with that webhook and its secret, so a retried create never makes a second one. At most 25 active webhooks per workspace. Free: no credits.

How are credits spent?

Webhooks are free. Creating one, the events it receives and test events consume no credits, and an event never reveals a field that a People Search by your workspace would mask.

Event types

A webhook receives a JSON POST at its url for each event type listed in its events:

  • person.saved - a person entered your workspace's saved people, from the app, the browser extension, a CSV enrichment or Save People. Sent once per person; again only after the person was removed and saved anew.
  • person.email_found - a saved person has a valid email your workspace owns, at save time or later when an email is found.
  • company.saved - a company entered your workspace's saved companies (Save Companies).
  • webhook.test - sent only by Send Test Event.

Only changes after the webhook was created are sent. Nothing is replayed from before, so subscribing never floods your receiver with history.

Event body

Response
{
  "id": "evt_6702a1b2c3d4e5f607182931",
  "type": "person.saved",
  "api_version": "2026-10",
  "created_at": "2026-10-08T12:00:40.000Z",
  "webhook_id": "6702a1b2c3d4e5f607182930",
  "data": {
    "person": { "id": "64f1a2b3c4d5e6f7a8b9c0d1", "...": "..." },
    "saved_at": "2026-10-08T12:00:00.000Z",
    "list_ids": ["64f1a2b3c4d5e6f7a8b9c0f1"]
  }
}

For person events data.person is the Person object as People Search returns it for your workspace. For company.saved it is { "company", "saved_at", "list_ids" } with the Company object. Deliveries are at least once and id stays the same across retries, so dedupe on it. A change of shape ships as a new api_version; a webhook keeps the version it was created with.

Verify the signature

Every delivery carries these headers:

  • X-GetProspect-Event-Id and X-GetProspect-Event-Type
  • User-Agent: GetProspect-Webhooks/1.0
  • X-GetProspect-Signature: t=<unix seconds>,v1=<hex>

v1 is an HMAC-SHA256 over <t>.<raw body> with the webhook secret. The secret is in the answer of this endpoint only, so store it when you create the webhook. Check the signature on the raw body, before parsing it, and refuse an old t:

Example
const crypto = require("crypto");

function isValid(header, rawBody, secret) {
  const [t, v1] = header.split(",").map((part) => part.split("=")[1]);
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return (
    v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex")) &&
    Math.abs(Date.now() / 1000 - Number(t)) < 300
  );
}

Delivery and retries

Answer 2xx within 10 seconds. Redirects are not followed. A transport error, 408, 429 or 5xx is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours; after that the event is failed. Any other answer is final. A retry is signed again with a new t.

A webhook is turned off (status: disabled) when your receiver answers 410, when deliveries did nothing but fail for 3 days, after 20 timeouts in a row, or when the member or API key that created it is gone. Once your receiver is fixed, re-activate it with Update Webhook; a webhook whose creator is gone cannot be re-activated, so create a new one. See what happened to each event in List Webhook Deliveries.

Limits

The url must be https on a public host. A workspace can have 25 active webhooks. Each webhook has one delivery in flight at a time, and a workspace gets at most 10 deliveries per second. Creating a webhook with the same url and events as one of your active webhooks returns that webhook, so a retried request never creates a duplicate.

Body parameters

  • urlstringrequiredmax length: 2048

    Where events are POSTed: an https URL on a public host.

    Example
    {
      "url": "https://hooks.zapier.com/hooks/standard/1/abc/",
      "events": [
        "person.saved"
      ]
    }
  • eventsenum[]required

    A webhook event type: person.saved (a person entered the saved set), person.email_found (a saved person has a valid email the workspace owns), company.saved (a company entered the saved set). A webhook takes 1-10, no repeats.

    Allowed values: person.saved person.email_found company.saved

    Example
    {
      "url": "https://hooks.zapier.com/hooks/standard/1/abc/",
      "events": [
        "person.saved"
      ]
    }
  • descriptionstringoptionalmax length: 200

    A note for people reading the webhook list.

  • api_versionenumoptional

    The payload version of the events (2026-10); defaults to the current one.

    Allowed values: 2026-10

Responses

  • 200 This credential already has an active webhook for this URL and these events: that one, with secret.
  • 201 Created; secret is shown only on this answer and the 200 one.
  • 400 Invalid body (VALIDATION_ERROR): invalid_format (not https, credentials in the URL, or a private host), value_not_allowed (unknown event type), out_of_range (1-10 events, or already 25 active webhooks), missing_field, wrong_type, unknown_field.
  • 401 Missing, unknown or inactive credentials (UNAUTHORIZED).
  • 500 Unexpected server error.

For error handling see Errors. Response objects are described in Person object and Company object.