Cold Leads

E-Mail-Adressen neuer HubSpot- und Pipedrive-Kontakte prüfen oder finden

Für wen
Sales-Operations-Teams mit HubSpot oder Pipedrive
Das Problem
Kontakte kommen aus Formularen, Importen und manueller Eingabe mit Tippfehlern, toten Domains oder ganz ohne Adresse, und niemand prüft sie, bis eine Kampagne Bounces erzeugt.
Die Lösung
Prüfen Sie jeden Kontakt beim Anlegen: mit einer Custom-Code-Aktion in einem HubSpot-Workflow oder einem kleinen Webhook-Empfänger für Pipedrive. Ein Kontakt mit E-Mail-Adresse wird geprüft; ein Kontakt ohne, aber mit Namen und Firmenwebsite erhält eine vorgeschlagene Adresse, die klar als Vermutung gekennzeichnet ist.
Was Sie bekommen
Kontakteigenschaften in HubSpot oder Personenfelder in Pipedrive, die Status, Score und Grund von Cold Leads zeigen oder eine vorgeschlagene Adresse mit Methode und Konfidenz.

Adressen, IDs und Ergebnisse in den Beispielen sind illustrativ. example.com ist für Dokumentation reserviert, eine echte Prüfung dieser Adressen liefert daher invalid.

Die Codebeispiele sind in allen Sprachen gleich, ihre Kommentare sind auf Englisch.

Wie alles zusammenpasst

  • Es gibt keine eingebaute Integration: Der Workflow oder der Empfänger ruft die Cold-Leads-API mit Ihrem geheimen Schlüssel auf, und die Antwort wird mit den eigenen Werkzeugen des CRM zurückgeschrieben. Der API-Aufruf legt den Kontakt nicht in Cold Leads an.
  • Kontakt mit E-Mail-Adresse: POST /api/v1/verify (1 Credit) liefert status (valid, risky oder invalid), score und Grundcodes.
  • Kontakt ohne E-Mail-Adresse, aber mit Vor- oder Nachname und Firmenwebsite: POST /api/v1/find (1 Credit) liefert die wahrscheinlichste Adresse. Ihre method ist nur dann verified, wenn ein Mailserver das Postfach bestätigt hat, sonst pattern: eine Vermutung aus gängigen Adressformaten. Speichern Sie sie in einem eigenen Feld und prüfen Sie sie; überschreiben Sie das E-Mail-Feld nie mit einer Vermutung.

HubSpot: eine Custom-Code-Aktion in einem Kontakt-Workflow

  1. Legen Sie Kontakteigenschaften für die Ergebnisse an, zum Beispiel Cold-Leads-Status, Cold-Leads-Score, Cold-Leads-Grund und Vorgeschlagene E-Mail.
  2. Öffnen Sie Automation > Workflows und erstellen Sie einen Kontakt-Workflow. Trigger: das Event Object created (Kategorie CRM) mit einem Verfeinerungsfilter wie Email is known oder ein Filter-Trigger (Met filter criteria) auf Email is known. Verfeinerungsfilter werden nur im Moment des Events ausgewertet; ein Kontakt, der ohne E-Mail-Adresse angelegt wird und erst später eine erhält, wird über den Event-Trigger also nicht aufgenommen.
  3. Klicken Sie auf das Symbol +, suchen Sie nach Custom code und wählen Sie es aus. Custom code erfordert Data Hub Professional oder Enterprise.
  4. Behalten Sie Node.js als Sprache. Klicken Sie auf Add secret, geben Sie als Namen des Secrets COLDLEADS_API_KEY und als Wert Ihren sk_-Schlüssel ein, speichern Sie und haken Sie das Secret an.
  5. Fügen Sie unter Properties to include in code die Eigenschaften Email, First name, Last name und Website URL mit den Namen email, firstname, lastname und website hinzu.
  6. Fügen Sie den Code unten ein. Legen Sie unter Data outputs die oben im Code aufgeführten Ausgaben mit ihren Datentypen an.
  7. Nutzen Sie Test action mit einem Testkontakt. Der Test führt den echten Code für den gewählten Kontakt aus und verbraucht daher einen Credit.
  8. Fügen Sie eine Aktion Edit record hinzu (Symbol +, CRM, Edit record), wählen Sie eine Eigenschaft und klicken Sie dann unter Action data auf More action data, die Custom-Code-Aktion und die Ausgabe. Wiederholen Sie das für jede Eigenschaft.
Custom code (Node.js)
// HubSpot workflow, Custom code action (Node.js)
// Secret: COLDLEADS_API_KEY (your Cold Leads secret key, sk_...)
// Properties to include in code: email, firstname, lastname, website
// Data outputs (add them in the action): verify_status, verify_reason, suggested_email, find_method (String);
// verify_score, find_confidence (Number); mailbox_checked (Boolean)
const axios = require("axios");

const coldleads = axios.create({
  baseURL: "https://coldleads.app/api/v1",
  headers: { "x-api-key": process.env.COLDLEADS_API_KEY },
  timeout: 17000, // the action has 20 seconds in total
});

exports.main = async (event, callback) => {
  const { email, firstname, lastname, website } = event.inputFields;
  try {
    if (email) {
      // 1 credit; timeout_ms keeps the check inside HubSpot's time limit
      const { data } = await coldleads.post("/verify", { email, timeout_ms: 12000 });
      const reason = data.reasons[data.reasons.length - 1];
      return callback({
        outputFields: {
          verify_status: data.status,
          verify_score: data.score,
          verify_reason: reason,
          mailbox_checked: ["ok", "catch_all", "mailbox_missing"].includes(reason),
        },
      });
    }
    if ((firstname || lastname) && website) {
      // 1 credit; website may be a URL, Cold Leads reduces it to the domain
      const { data } = await coldleads.post("/find", { first: firstname || "", last: lastname || "", domain: website });
      return callback({
        outputFields: {
          suggested_email: data.email || "",
          find_method: data.method, // "verified" only when a mail server confirmed the mailbox, otherwise "pattern"
          find_confidence: data.confidence,
        },
      });
    }
    return callback({ outputFields: { verify_status: "skipped" } });
  } catch (err) {
    const status = err.response ? err.response.status : 0;
    // rethrow rate limits and server errors: HubSpot retries them (for up to three days)
    if (status === 429 || status >= 500) throw err;
    const code = err.response && err.response.data && err.response.data.error;
    return callback({ outputFields: { verify_status: "error", verify_reason: code || err.code || String(status) } });
  }
};

HubSpot gibt einer Custom-Code-Aktion 20 Sekunden und 128 MB. Wirft der Code nach einem 429- oder 5xx-Fehler von axios eine Exception, wiederholt HubSpot die Aktion bis zu drei Tage lang, beginnend eine Minute später; deshalb wirft der Code diese Fehler weiter und macht aus jedem anderen Fehler, etwa 402 no_credits, einen Ausgabewert.

Pipedrive: ein Webhook-Empfänger

  1. Legen Sie zwei Textfelder für Personen an, zum Beispiel E-Mail-Prüfung und Vorgeschlagene E-Mail, und kopieren Sie ihre API-Schlüssel unter Company settings > Data fields > Person im Menü des jeweiligen Felds (Copy API key). Notieren Sie für die Adresssuche außerdem den Schlüssel eines Personenfelds, das die Firmenwebsite oder -domain enthält.
  2. Betreiben Sie den Empfänger unten auf einem beliebigen Host mit Node.js 18+ und einer HTTPS-Adresse, mit den Umgebungsvariablen, die er auflistet.
  3. Öffnen Sie in Pipedrive Settings > Tools and apps > Webhooks und legen Sie einen Webhook an: Event action create, Event object person, ein User permission level, dessen Sichtbarkeit neue Personen abdeckt, einen Webhook name, die Endpoint URL Ihres Empfängers (seine HTTPS-Adresse, gefolgt von /pipedrive/person) sowie HTTP Auth username und password passend zu HOOK_USER und HOOK_PASSWORD.
  4. Legen Sie eine Person mit E-Mail-Adresse an; das Feld E-Mail-Prüfung wird kurz darauf ausgefüllt.
pipedrive_receiver.mjs
// npm install express   (Node.js 18 or newer)
// Pipedrive webhook: event action "create", event object "person", endpoint https://<your-host>/pipedrive/person,
// HTTP Auth username and password = HOOK_USER and HOOK_PASSWORD below.
import express from "express";

const {
  COLDLEADS_API_KEY, // Cold Leads secret key (sk_...)
  PIPEDRIVE_API_TOKEN, // Pipedrive personal API token
  HOOK_USER,
  HOOK_PASSWORD,
  STATUS_FIELD, // 40-character key of a person text field, e.g. "E-mail check"
  SUGGESTED_FIELD, // 40-character key of a person text field, e.g. "Suggested e-mail"
  DOMAIN_FIELD, // optional: key of a person field that holds the company website or domain
} = process.env;

async function coldleads(path, body) {
  const res = await fetch(`https://coldleads.app/api/v1${path}`, {
    method: "POST",
    headers: { "x-api-key": COLDLEADS_API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(`Cold Leads ${res.status} ${data.error}`);
  return data;
}

async function updatePerson(id, customFields) {
  const res = await fetch(`https://api.pipedrive.com/api/v2/persons/${id}`, {
    method: "PATCH",
    headers: { "x-api-token": PIPEDRIVE_API_TOKEN, "Content-Type": "application/json" },
    body: JSON.stringify({ custom_fields: customFields }),
  });
  if (!res.ok) throw new Error(`Pipedrive ${res.status} ${await res.text()}`);
}

// webhook payloads carry custom fields as typed objects ({ type, value })
const plain = (v) => (v && typeof v === "object" && "value" in v ? v.value : v);

async function handlePerson(person) {
  const emails = Array.isArray(person.emails) ? person.emails : [];
  const primary = emails.find((e) => e && e.primary) ?? emails[0];
  const email = typeof primary === "string" ? primary : primary?.value;
  if (email) {
    const r = await coldleads("/verify", { email, timeout_ms: 15000 }); // 1 credit
    const reason = r.reasons[r.reasons.length - 1];
    await updatePerson(person.id, { [STATUS_FIELD]: `${r.status} (${reason}, score ${r.score})` });
    return;
  }
  const domain = DOMAIN_FIELD ? plain(person.custom_fields?.[DOMAIN_FIELD]) : "";
  if (domain && (person.first_name || person.last_name)) {
    const r = await coldleads("/find", { first: person.first_name ?? "", last: person.last_name ?? "", domain }); // 1 credit
    if (r.email) await updatePerson(person.id, { [SUGGESTED_FIELD]: `${r.email} (${r.method}, confidence ${r.confidence})` });
  }
}

const seen = new Set(); // a retried delivery must not be charged twice
const app = express();
app.post("/pipedrive/person", express.json(), (req, res) => {
  const expected = "Basic " + Buffer.from(`${HOOK_USER}:${HOOK_PASSWORD}`).toString("base64");
  if (req.get("authorization") !== expected) return res.sendStatus(401);
  const { meta, data } = req.body ?? {};
  res.sendStatus(200); // answer at once: Pipedrive waits 10 seconds, then retries
  if (meta?.entity !== "person" || !data?.id || seen.has(meta.id)) return;
  seen.add(meta.id);
  handlePerson(data).catch((e) => console.error(`person ${data.id}:`, e.message));
});
app.listen(process.env.PORT ?? 3000);
  • Pipedrive wertet jede 2xx-Antwort als zugestellt, wartet bis zu 10 Sekunden und wiederholt eine fehlgeschlagene Zustellung nach 3, 30 und 150 Sekunden. Der Empfänger antwortet sofort und ignoriert Event-IDs, die er schon verarbeitet hat, sodass eine Wiederholung nicht doppelt berechnet wird. Die Menge der IDs liegt im Arbeitsspeicher; nutzen Sie eine Datenbank, wenn Sie mehrere Instanzen betreiben.
  • In der Pipedrive-Oberfläche erstellte Webhooks nutzen das v2-Format: meta beschreibt das Event, data enthält die Person mit first_name, last_name, emails und custom_fields.
  • Personenfelder werden mit PATCH /api/v2/persons/{id} und einem custom_fields-Objekt aktualisiert, authentifiziert mit dem Header x-api-token. Der ältere Endpunkt PUT /v1/persons wird seit dem 1. August 2026 nicht mehr unterstützt.
  • Der Webhook feuert nur, wenn eine Person angelegt wird, sodass das daraus folgende Update ihn nicht erneut auslöst. Wenn Sie auch Änderungen abonnieren, überspringen Sie Events, deren meta.change_source api ist.

Was zurückgeschrieben wird und was damit zu tun ist

ErgebnisEmpfohlene Behandlung im CRM
valid, Grund okBereit zur Ansprache: Ein Mailserver hat das Postfach akzeptiert.
valid, Grund smtp_unreachableDie Domain nimmt E-Mails an, aber das Postfach wurde nicht geprüft. Mit Vorsicht senden und Hard Bounces entfernen.
risky (catch_all, smtp_unknown, timeout oder eine Wegwerf-Domain)Vor dem Senden manuell prüfen.
invalid (mailbox_missing, no_mx, syntax)Keine E-Mail senden; Adresse korrigieren oder entfernen.
Adresssuche, method verifiedEin Mailserver hat das vorgeschlagene Postfach bestätigt.
Adresssuche, method patternEine Vermutung aus gängigen Adressformaten: vor der Verwendung bestätigen.
Adresssuche, method noneKein Kandidat: Die Domain hat keinen Mailserver, oder der Name war nicht verwendbar.

Limits und Kosten

  • 1 Cold-Leads-Credit pro Prüfung oder Aufruf der Adresssuche, unabhängig vom Ergebnis. Der Business-Tarif ($99 im Monat) enthält die API und 10.000 Credits im Monat; zusätzliche Pakete zu 1.000 Credits kosten $5.
  • 120 Anfragen pro Minute und Cold-Leads-Schlüssel; ein 429 enthält Retry-After: 60.
  • Der HubSpot-Code fordert bei Cold Leads ein Budget von 12 Sekunden an (timeout_ms), um innerhalb der 20 Sekunden von HubSpot zu bleiben. Die Adresssuche hat keinen Budget-Parameter; bei einer langsamen Domain kann der Aktion die Zeit ausgehen, und der Credit ist verbraucht.
  • Die Adresssuche entfernt Akzente aus Namen (aus Novák wird novak) und verwirft andere Zeichen außerhalb von a bis z; Namen in nicht lateinischen Schriften müssen daher zuerst transliteriert werden.
  • Pipedrive misst seine API in Tokens pro Tag und Unternehmen; eine Person mit PATCH /api/v2/persons/{id} zu aktualisieren kostet 5 Tokens.

FAQ

Welchen HubSpot-Tarif brauche ich?

Custom-Code-Aktionen (und Webhook-Aktionen) in HubSpot-Workflows erfordern Data Hub Professional oder Enterprise. Aufseiten von Cold Leads braucht die API den Business-Tarif.

Warum die vorgeschlagene Adresse nicht direkt ins E-Mail-Feld schreiben?

Weil sie mit method pattern eine Vermutung aus gängigen Formaten ist und kein bestätigtes Postfach. In einem eigenen Feld kann jemand sie bestätigen, bevor eine Sequenz sie verwendet.

Kann die Adresssuche die Entscheider eines Unternehmens ermitteln?

Nein. Cold Leads hat keine Personendatenbank. Die Adresssuche braucht eine Person, die Sie bereits mit Namen kennen, plus die Firmendomain.

Funktioniert das auch mit Pipedrive-Automatisierungen statt eines Webhooks?

Pipedrive-Automatisierungen können ebenfalls einen Webhook aufrufen (in den Tarifen Growth und höher), senden aber einen Body, den Sie selbst definieren; das Parsing des Empfängers muss dann zu diesem Body passen statt zum v2-Webhook-Format.