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-tokenheader. - 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.