Skip to content

Pipedrive email verification with a webhook and a small service

· 4 min read · Tutorials

For Pipedrive email verification, subscribe a webhook to new persons, have a small service look up each person's email, verify it with an API, and write the result into a person custom field. Sales then filters on that field: send to valid, review risky, drop invalid. Everything below uses documented Pipedrive features: webhooks, the persons API and custom fields. About 50 lines of Node.js do the work.

What you need

  • A Pipedrive account and an API token. Pipedrive's authentication docs say the token goes in the x-api-token header.
  • A person custom field called something like "Email status" (a text field is simplest).
  • A pingutil API key for POST https://pingutil.com/v1/verify.
  • Somewhere to run a small HTTPS service.

Find the custom field key

Custom fields are addressed by a generated key, not by name. Pipedrive's custom field tutorial shows how to list fields and read the key:

curl -H "x-api-token: $PIPEDRIVE_TOKEN" \
  "https://yourcompany.pipedrive.com/api/v1/personFields"

Find "Email status" in the response and copy its key (a long hex string).

Step 1: create the webhook

Pipedrive's Webhooks v2 guide covers both the UI (Settings > Tools and apps > Webhooks) and the API. Set event_action to create and event_object to person, and protect the endpoint with HTTP Basic auth:

curl -X POST "https://yourcompany.pipedrive.com/api/v1/webhooks" \
  -H "x-api-token: $PIPEDRIVE_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "subscription_url": "https://verify.yourcompany.com/pipedrive",
    "event_action": "create",
    "event_object": "person",
    "version": "2.0",
    "http_auth_user": "pipedrive",
    "http_auth_password": "a-long-random-secret"
  }'

Two details from the guide shape the service. Pipedrive treats a request as failed if it doesn't get a 2xx within 10 seconds, and retries after 3, 30 and 150 seconds. Email verification can take up to about 25 seconds on a slow mail server. So the service must answer the webhook first and verify afterwards.

Step 2: the service

The webhook payload has a meta object with entity_id. Rather than depend on the exact shape of data, the service fetches the person by ID, where emails come back as an emails array of { value, primary, label }.

// node >= 18, npm install express
import express from 'express';

const PD = 'https://yourcompany.pipedrive.com/api/v2';
const FIELD = process.env.EMAIL_STATUS_FIELD_KEY;
const pdHeaders = { 'x-api-token': process.env.PIPEDRIVE_TOKEN, 'content-type': 'application/json' };
const basic = 'Basic ' + Buffer.from(`pipedrive:${process.env.WEBHOOK_SECRET}`).toString('base64');

const app = express();
app.use(express.json());

app.post('/pipedrive', (req, res) => {
  if (req.headers.authorization !== basic) return res.sendStatus(401);
  res.sendStatus(200); // answer inside Pipedrive's 10-second window
  const id = req.body?.meta?.entity_id;
  if (id) verifyPerson(id).catch((e) => console.error('verify failed', id, e));
});

async function verifyPerson(id) {
  const person = await fetch(`${PD}/persons/${id}`, { headers: pdHeaders }).then((r) => r.json());
  const emails = person.data?.emails ?? [];
  const email = (emails.find((e) => e.primary) ?? emails[0])?.value;
  if (!email) return;

  const v = await fetch('https://pingutil.com/v1/verify', {
    method: 'POST',
    headers: { authorization: `Bearer ${process.env.PINGUTIL_KEY}`, 'content-type': 'application/json' },
    body: JSON.stringify({ email }),
    signal: AbortSignal.timeout(30_000),
  });
  if (!v.ok) return; // 429 or error: leave the field empty, a nightly job can retry
  const r = await v.json(); // { status, reason, score, did_you_mean, ... }

  const label = r.did_you_mean ? `${r.status} (${r.reason}, did you mean ${r.did_you_mean}?)` : `${r.status} (${r.reason})`;
  await fetch(`${PD}/persons/${id}`, {
    method: 'PATCH',
    headers: pdHeaders,
    body: JSON.stringify({ custom_fields: { [FIELD]: label } }),
  });
}

app.listen(process.env.PORT ?? 3000);

In API v2, custom fields live in a separate custom_fields object and updates use PATCH (v1 used PUT with custom fields at the root), so don't copy a v1-style body from older examples.

Only subscribe to create. The PATCH changes the person, so a change webhook would fire again and verify the same address in a loop.

What the field will say

Value Meaning Suggested sales action
valid (mailbox_exists) Server confirmed the mailbox Sequence as normal
risky (accept_all) Catch-all domain, can't confirm Send, but watch for bounces. See catch-all domains.
risky (role_based) Shared inbox like info@ Look for a named contact
invalid (mailbox_not_found) Server rejected the mailbox Don't email; find a new address
unknown (greylisted) Server didn't answer clearly Re-check later

Every reason is listed in the verification status reference. Build a Pipedrive filter on the field so reps see only verified contacts in their outreach views.

The no-code option: Automations

If you're on Pipedrive Advanced or higher, Pipedrive's automation webhook requests can send a POST with Pipedrive fields in the body when a person is created. Only global admins can set up the webhook, and requests count toward automation execution limits. That gets the email to a verification service without a webhook subscription, but you still need something that writes the result back through the API.

Limits and existing contacts

pingutil allows 300 verifications a day per account and 60 requests a minute, across API and dashboard CSV uploads. That fits new contacts arriving through the day. For your existing contacts, export them and verify in batches of 50 with POST /v1/verify/batch, spread over several days, then import the status back. The same pattern for other CRMs is in our HubSpot webhook tutorial. To see the statuses before building anything, try a few addresses in the email verification demo.

Questions people ask

Can I verify emails in Pipedrive without writing code?
Partly. On Advanced and higher plans, Pipedrive Automations can send a webhook request with Pipedrive fields in the body, so you can call a verification service from an automation. Writing the result back into a custom field still needs something that calls the Pipedrive API, such as a small service or an integration platform.
Why subscribe to person create and not person change?
Because your service updates the person after verifying. If you listen to change events, that update fires another webhook, and you'll verify the same address in a loop. Listen to create, and re-verify on a schedule instead.