> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enrow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# So funktionieren Webhooks

> Erhalten Sie Webhook-Benachrichtigungen in Echtzeit, sobald Enrow-Suchen und -Verifizierungen abgeschlossen sind, statt die API abzufragen

export const PollingVsWebhook = () => {
  const [pollingStep, setPollingStep] = useState(0);
  const [webhookPhase, setWebhookPhase] = useState("idle");
  useEffect(() => {
    const timers = [];
    const addTimer = (fn, ms) => {
      const id = setTimeout(fn, ms);
      timers.push(id);
    };
    const runCycle = () => {
      setPollingStep(0);
      for (let i = 0; i < 5; i++) {
        addTimer(() => setPollingStep(i + 1), (i + 1) * 900);
      }
      addTimer(() => setPollingStep(0), 6500);
      setWebhookPhase("idle");
      addTimer(() => setWebhookPhase("sent"), 400);
      addTimer(() => setWebhookPhase("processing"), 1200);
      addTimer(() => setWebhookPhase("callback"), 2800);
      addTimer(() => setWebhookPhase("idle"), 6500);
    };
    runCycle();
    const loop = setInterval(runCycle, 7200);
    return () => {
      clearInterval(loop);
      timers.forEach(clearTimeout);
    };
  }, []);
  const POLLING_STEPS = [{
    response: "pending",
    success: false
  }, {
    response: "pending",
    success: false
  }, {
    response: "pending",
    success: false
  }, {
    response: "pending",
    success: false
  }, {
    response: "complete",
    success: true
  }];
  const svgIcon = ({className, circle, paths}) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
      {circle ? <circle cx={circle.cx} cy={circle.cy} r={circle.r} /> : null}
      {paths.map((d, i) => <path key={i} d={d} />)}
    </svg>;
  const icons = {
    arrowRight: className => svgIcon({
      className,
      paths: ["M5 12h14", "m12 5 7 7-7 7"]
    }),
    bell: className => svgIcon({
      className,
      paths: ["M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9", "M10.3 21a1.94 1.94 0 0 0 3.4 0"]
    }),
    checkCircle: className => svgIcon({
      className,
      circle: {
        cx: 12,
        cy: 12,
        r: 10
      },
      paths: ["m9 12 2 2 4-4"]
    }),
    xCircle: className => svgIcon({
      className,
      circle: {
        cx: 12,
        cy: 12,
        r: 10
      },
      paths: ["m15 9-6 6", "m9 9 6 6"]
    }),
    loader2: className => svgIcon({
      className,
      paths: ["M21 12a9 9 0 1 1-6.219-8.56"]
    })
  };
  return <div className="my-6 grid grid-cols-1 sm:grid-cols-2 gap-4 not-prose">
      {}
      <div className="rounded-xl border border-rose-500/20 bg-zinc-50/50 dark:bg-zinc-900/40 p-4">
        <div className="flex items-start gap-2.5 mb-3">
          <div className="size-8 rounded-lg bg-rose-500/10 flex items-center justify-center shrink-0">
            {icons.xCircle("size-4 text-rose-600 dark:text-rose-400")}
          </div>
          <div className="min-w-0">
            <h3 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100 m-0 leading-8">
              Polling
            </h3>
            <div className="text-[11px] text-zinc-500 dark:text-zinc-400" style={{
    lineHeight: 1.5
  }}>
              Your app repeatedly asks "is it done yet?" until the result is ready.
            </div>
          </div>
        </div>

        <div className="space-y-1.5 mb-3 min-h-[140px]">
          {POLLING_STEPS.map((step, i) => <div key={i} className="flex items-center gap-2 text-xs font-mono transition-all duration-300" style={{
    opacity: i < pollingStep ? 1 : 0.2,
    transform: i < pollingStep ? "translateX(0)" : "translateX(-4px)"
  }}>
              <span className="text-zinc-500 dark:text-zinc-400 w-20 shrink-0">
                GET /status
              </span>
              {icons.arrowRight("size-3 text-zinc-400/70 shrink-0")}
              {step.success ? <span className="flex items-center gap-1 text-emerald-600 dark:text-emerald-400">
                  {icons.checkCircle("size-3")}
                  {step.response}
                </span> : <span className="flex items-center gap-1 text-rose-600 dark:text-rose-400">
                  {icons.xCircle("size-3")}
                  {step.response}
                </span>}
            </div>)}
        </div>

        <div className="border-t border-zinc-200 dark:border-zinc-800 pt-3 space-y-1">
          {["5+ requests per result", "Wasted bandwidth", "Delayed results"].map(stat => <div key={stat} className="flex items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
              {icons.xCircle("size-3 text-rose-500/60 shrink-0")}
              {stat}
            </div>)}
        </div>
      </div>

      {}
      <div className="rounded-xl border border-emerald-500/20 bg-zinc-50/50 dark:bg-zinc-900/40 p-4">
        <div className="flex items-start gap-2.5 mb-3">
          <div className="size-8 rounded-lg bg-emerald-500/10 flex items-center justify-center shrink-0">
            {icons.checkCircle("size-4 text-emerald-600 dark:text-emerald-400")}
          </div>
          <div className="min-w-0">
            <h3 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100 m-0 leading-8">
              Webhook
            </h3>
            <div className="text-[11px] text-zinc-500 dark:text-zinc-400" style={{
    lineHeight: 1.5
  }}>
              Enrow notifies your server the instant the result is ready.
            </div>
          </div>
        </div>

        <div className="space-y-1.5 mb-3 min-h-[140px]">
          <div className="flex items-center justify-between text-xs font-mono transition-all duration-500" style={{
    opacity: webhookPhase !== "idle" ? 1 : 0.2,
    transform: webhookPhase !== "idle" ? "translateX(0)" : "translateX(-4px)"
  }}>
            <span className="text-emerald-700 dark:text-emerald-400 font-semibold whitespace-nowrap">
              POST /email/find/single
            </span>
            <span className="text-zinc-500 dark:text-zinc-400 shrink-0 ml-2">
              {webhookPhase === "sent" && "sent ✓"}
              {webhookPhase === "processing" && <span className="inline-flex items-center gap-1">
                  {icons.loader2("size-3 animate-spin")}
                  Processing...
                </span>}
              {webhookPhase === "callback" && <span className="text-emerald-600 dark:text-emerald-400">✓ done</span>}
            </span>
          </div>

          {webhookPhase === "callback" && <div className="mt-3 rounded-md border border-emerald-400/30 bg-emerald-500/5 px-3 py-2 font-mono text-xs">
              <div className="flex items-center gap-1.5 mb-1">
                {icons.bell("size-3 text-emerald-500")}
                <span className="text-emerald-600 dark:text-emerald-400 font-semibold text-[10px] uppercase tracking-wider">
                  Webhook callback
                </span>
              </div>
              <span className="text-zinc-600 dark:text-zinc-400">
                {'{"email":"found","status":"valid"}'}
              </span>
            </div>}
        </div>

        <div className="border-t border-zinc-200 dark:border-zinc-800 pt-3 space-y-1">
          {["1 request, 1 callback", "Zero waste", "Real-time delivery"].map(stat => <div key={stat} className="flex items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
              {icons.checkCircle("size-3 text-emerald-500/70 shrink-0")}
              {stat}
            </div>)}
        </div>
      </div>
    </div>;
};

## Warum Webhooks verwenden?

Mit Webhooks kommen die Ergebnisse zu Ihnen, anstatt dass Sie sie wiederholt abfragen müssen. Statt einen GET-Endpunkt so lange abzufragen, bis eine Suche abgeschlossen ist, sendet Enrow jedes Ergebnis in dem Moment an Ihren Server, in dem es bereit ist — das spart Anfragen, reduziert die Latenz und hält Ihren Code einfach.

**Verschwenden Sie keine Anfragen mehr — lassen Sie die Ergebnisse zu Ihnen kommen.**

<PollingVsWebhook />

Da jeder Enrow-Endpunkt **asynchron** ist, sind Webhooks die empfohlene Methode, um Ergebnisse über [Email Finder](/de/api-reference/email-finder/find-single), [Email Verifier](/de/api-reference/email-verifier/verify-single) und [Phone Finder](/de/api-reference/phone/find-single) hinweg zu erhalten. Webhooks umgehen außerdem die [Ratenbegrenzungen](/de/rate-limits) vollständig, da Enrow Sie aufruft und nicht umgekehrt.

## Wie läuft ein Webhook-Ablauf ab?

Ein Webhook-Ablauf macht aus einer einzelnen Suchanfrage eine automatische Zustellung. Sie teilen Enrow mit, wohin die Ergebnisse gesendet werden sollen, und Enrow erledigt den Rest:

1. Sie senden eine Suchanfrage per **POST** mit einer `webhook`-URL in den `settings`
2. Enrow gibt sofort eine Such-ID zurück
3. Enrow verarbeitet die Suche im Hintergrund
4. Nach Abschluss sendet Enrow die Ergebnisse per **POST** an Ihre Webhook-URL

## Wie richte ich einen Webhook ein?

Sie können einen Webhook auf zwei Arten registrieren, je nachdem, ob Sie ihn für eine einzelne Suche oder für jede Suche verwenden möchten:

1. **Pro Anfrage**: Fügen Sie eine `webhook`-URL in das `settings`-Objekt eines beliebigen API-Aufrufs ein
2. **Global**: Konfigurieren Sie einen Standard-Webhook auf der [Integrationsseite](https://app.enrow.io/integrations) im Dashboard

```json theme={null}
{
  "fullname": "Dwight Schrute",
  "company_domain": "dundermifflin.com",
  "settings": {
    "webhook": "https://your-app.com/webhooks/enrow"
  }
}
```

<Warning>
  Ihre Webhook-URL muss ein gültiger **HTTPS**-Endpunkt sein, der einen `200`-Statuscode zurückgibt.
</Warning>

## Welche Ereignisse lösen einen Webhook-Aufruf aus?

Sechs Arten von Ereignissen können einen Webhook-Aufruf auslösen, eines pro Endpunkt und Suchtyp:

| Ereignis                       | Beschreibung                                           |
| ------------------------------ | ------------------------------------------------------ |
| `single_search_finished`       | Eine einzelne E-Mail-Suche wurde abgeschlossen         |
| `bulk_search_finished`         | Eine E-Mail-Sammelsuche wurde abgeschlossen            |
| `verification_finished`        | Eine einzelne E-Mail-Verifizierung wurde abgeschlossen |
| `bulk_verification_finished`   | Eine E-Mail-Sammelverifizierung wurde abgeschlossen    |
| `single_phone_search_finished` | Eine einzelne Telefonsuche wurde abgeschlossen         |
| `bulk_phone_search_finished`   | Eine Telefon-Sammelsuche wurde abgeschlossen           |

## Wie sieht ein Webhook-Payload aus?

Der Webhook-Payload hängt vom Endpunkt ab und davon, ob es sich um eine einzelne Suche oder eine Sammelsuche handelt. Einzelsuchen liefern das vollständige Ergebnis direkt, während Sammelsuchen eine Abschlussbenachrichtigung liefern, der Sie mit einer GET-Anfrage nachgehen.

### Email Finder — Einzeln

Bei Einzelsuchen erhalten Sie das **vollständige Ergebnis direkt** in der Webhook-Benachrichtigung. Dadurch entfällt die Notwendigkeit, eine GET-Anfrage durchzuführen.

```json theme={null}
{
  "event": "single_search_finished",
  "id": "910f3e13-b2bf-442d-ab0b-4cf44dfrij84fjrt",
  "credits": {
    "cost": 1
  },
  "result": {
    "email": "dwight.schrute@dundermifflin.com",
    "qualification": "valid",
    "custom": "your_custom_data",
    "info": {
      "company_domain": "dundermifflin.com",
      "fullname": "Dwight Schrute",
      "firstname": "Dwight",
      "lastname": "Schrute"
    }
  }
}
```

### Email Finder — Sammel

Bei Sammelsuchen erhalten Sie eine Benachrichtigung, dass der Batch abgeschlossen ist. Rufen Sie anschließend den Endpunkt [GET /email/find/bulk](/de/api-reference/email-finder/get-bulk-results) mit der `id` auf, um die Ergebnisse abzurufen.

```json theme={null}
{
  "event": "bulk_search_finished",
  "id": "910f3e13-b2bf-442d-ab0b-4cf44dfrij84fjrt",
  "credits": {
    "cost": 2284
  }
}
```

### Email Verifier — Einzeln

Das vollständige Ergebnis ist direkt enthalten — keine GET-Anfrage erforderlich.

```json theme={null}
{
  "event": "verification_finished",
  "id": "910f3e13-b2bf-442d-ab0b-4cf44dfrij84fjrt",
  "email": "pam.beesly@dundermifflin.com",
  "qualification": "valid",
  "custom": "your_custom_data"
}
```

### Email Verifier — Sammel

Dies ist nur eine Benachrichtigung. Rufen Sie [GET /email/verify/bulk](/de/api-reference/email-verifier/get-bulk-verifications) mit der `id` auf, um die Ergebnisse abzurufen.

```json theme={null}
{
  "event": "bulk_verification_finished",
  "id": "910f3e13-b2bf-442d-ab0b-4cf44dfrij84fjrt",
  "credits": {
    "cost": 386.25
  }
}
```

### Phone Finder — Einzeln

Das vollständige Ergebnis ist direkt enthalten — keine GET-Anfrage erforderlich.

```json theme={null}
{
  "event": "single_phone_search_finished",
  "id": "910f3e13-b2bf-442d-ab0b-4cf44dfrij84fjrt",
  "credits": {
    "cost": 50
  },
  "result": {
    "number": "+15705551234",
    "params": {
      "linkedin_url": "https://www.linkedin.com/in/michael-scott"
    },
    "qualification": "found"
  }
}
```

### Phone Finder — Sammel

Dies ist nur eine Benachrichtigung. Rufen Sie [GET /phone/bulk](/de/api-reference/phone/get-bulk-results) mit der `id` auf, um die Ergebnisse abzurufen.

```json theme={null}
{
  "event": "bulk_phone_search_finished",
  "id": "910f3e13-b2bf-442d-ab0b-4cf44dfrij84fjrt"
}
```

## Worin unterscheiden sich Einzel- und Sammel-Webhooks?

Webhooks für Einzelsuchen enthalten das vollständige Ergebnis, sodass kein zusätzlicher Aufruf erforderlich ist. Webhooks für Sammelsuchen signalisieren nur, dass der Batch abgeschlossen ist — Sie rufen die Ergebnisse anschließend mit dem passenden GET-Endpunkt ab.

| Typ                   | Einzelsuchen                     | Sammelsuchen                                     |
| --------------------- | -------------------------------- | ------------------------------------------------ |
| **Payload**           | Vollständiges Ergebnis enthalten | Nur Benachrichtigung (ID + Credits)              |
| **GET erforderlich?** | Nein                             | Ja — verwenden Sie den GET-Endpunkt mit der `id` |

<Info>
  Bei Einzelsuchen enthält der Webhook alles, was Sie benötigen. Bei Sammelsuchen teilt Ihnen der Webhook mit, dass der Batch abgeschlossen ist — anschließend rufen Sie die Ergebnisse ab.
</Info>

## Was sind die Best Practices für Webhook-Endpunkte?

Ein zuverlässiger Webhook-Endpunkt antwortet schnell, akzeptiert ausschließlich HTTPS und toleriert gelegentliche Duplikate. Befolgen Sie diese Praktiken, um zuverlässige Zustellungen sicherzustellen:

<AccordionGroup>
  <Accordion title="Schnell 200 zurückgeben">
    Verarbeiten Sie Webhook-Payloads asynchron. Geben Sie sofort einen `200` zurück und verarbeiten Sie die Daten anschließend in einem Hintergrundjob.
  </Accordion>

  <Accordion title="HTTPS verwenden">
    Verwenden Sie stets HTTPS-Endpunkte. HTTP-Webhooks werden abgelehnt.
  </Accordion>

  <Accordion title="Duplikate behandeln">
    In seltenen Fällen können Webhooks mehr als einmal zugestellt werden. Verwenden Sie das Feld `id`, um Duplikate zu entfernen.
  </Accordion>

  <Accordion title="Benutzerdefinierte Felder verwenden">
    Übergeben Sie `custom`-Daten in Ihren Anfragen, um zu identifizieren, zu welchem Datensatz ein Webhook-Ergebnis gehört:

    ```json theme={null}
    {
      "fullname": "Dwight Schrute",
      "company_domain": "dundermifflin.com",
      "custom": { "crm_id": "lead_001" }
    }
    ```

    Das Feld `custom` wird unverändert im Webhook-Payload zurückgegeben.
  </Accordion>
</AccordionGroup>

## Sollte ich Webhooks oder Polling verwenden?

Verwenden Sie Webhooks für die Produktion und Polling nur für schnelles Prototyping oder zum Debuggen. Webhooks liefern Ergebnisse in Echtzeit, ohne Ihr Anfragekontingent zu verbrauchen, während Polling wiederholte GET-Aufrufe durchführt, die auf Ihre [Ratenbegrenzungen](/de/rate-limits) angerechnet werden.

|                               | Webhooks                       | Polling (GET)                 |
| ----------------------------- | ------------------------------ | ----------------------------- |
| **Latenz**                    | Echtzeit                       | Abhängig vom Abfrageintervall |
| **API-Aufrufe**               | 0 (Enrow ruft Sie auf)         | Mehrere Aufrufe pro Suche     |
| **Auswirkung auf Rate-Limit** | Keine                          | Verbraucht Kontingent         |
| **Komplexität**               | Erfordert Endpunkt-Einrichtung | Einfacher umzusetzen          |

<Note>
  Wir empfehlen Webhooks für den Produktiveinsatz. Verwenden Sie Polling nur für schnelles Prototyping oder zum Debuggen.
</Note>

## FAQ

<AccordionGroup>
  <Accordion title="Kosten Webhooks zusätzliche Credits?">
    Nein. Webhooks verbrauchen keine zusätzlichen Credits — Sie zahlen nur für die Suche selbst. Die Credit-Kosten werden im Feld `credits.cost` des Payloads angegeben. Siehe [Credits & Abrechnung](/de/credits-billing) für die Kosten pro Endpunkt.
  </Accordion>

  <Accordion title="Was passiert, wenn mein Endpunkt keine 200 zurückgibt?">
    Ihre Webhook-URL muss ein gültiger HTTPS-Endpunkt sein, der einen `200`-Statuscode zurückgibt. Wenn Ihr Server nicht erreichbar ist oder mit einem anderen Status antwortet, wird die Zustellung als fehlgeschlagen behandelt. Als Ausweichlösung können Sie die Ergebnisse jederzeit abrufen, indem Sie den entsprechenden GET-Endpunkt mit der Such-`id` abfragen.
  </Accordion>

  <Accordion title="Wie ordne ich einen Webhook der ursprünglichen Anfrage zu?">
    Verwenden Sie die `id` aus der Suchantwort oder übergeben Sie ein `custom`-Objekt in Ihrer Anfrage — es wird unverändert im Webhook-Payload zurückgegeben, sodass Sie Ergebnisse Ihren eigenen Datensätzen zuordnen können, etwa einer CRM-Lead-ID.
  </Accordion>

  <Accordion title="Warum habe ich keinen Webhook erhalten?">
    Die häufigsten Ursachen sind eine Nicht-HTTPS-URL, ein Endpunkt, der keine `200` zurückgibt, oder ein Server mit Zeitüberschreitung. Stellen Sie sicher, dass Ihr Endpunkt öffentlich über HTTPS erreichbar ist. Für eine umfassendere Fehlerbehebung siehe [Fehlerbehandlung](/de/error-handling) und [Statuscodes](/de/status-codes).
  </Accordion>
</AccordionGroup>

## Nächste Schritte

<CardGroup cols={2}>
  <Card title="E-Mail finden" icon="envelope" href="/de/api-reference/email-finder/find-single">
    Übergeben Sie eine Webhook-URL in den Settings, um das Ergebnis automatisch zugestellt zu bekommen.
  </Card>

  <Card title="Sammelergebnisse abrufen" icon="layer-group" href="/de/api-reference/email-finder/get-bulk-results">
    Rufen Sie Batch-Ergebnisse ab, nachdem ein bulk\_search\_finished-Webhook ausgelöst wurde.
  </Card>

  <Card title="Authentifizierung" icon="key" href="/de/authentication">
    So übergeben Sie Ihren API-Schlüssel im x-api-key-Header.
  </Card>

  <Card title="Ratenbegrenzungen" icon="gauge-high" href="/de/rate-limits">
    Erfahren Sie, warum Webhooks das Anfragekontingent vermeiden, das Polling verbraucht.
  </Card>
</CardGroup>
