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

# Jak działają webhooki

> Otrzymuj powiadomienia webhook w czasie rzeczywistym, gdy wyszukiwania i weryfikacje Enrow zostaną zakończone, zamiast odpytywać API

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>;
};

## Po co używać webhooków?

Webhooki sprawiają, że wyniki trafiają do Ciebie, zamiast tego, żebyś wielokrotnie o nie pytał. Zamiast odpytywać endpoint GET aż do zakończenia wyszukiwania, Enrow wysyła każdy wynik na Twój serwer w momencie, gdy jest gotowy — oszczędzając żądania, zmniejszając opóźnienia i utrzymując prostotę kodu.

**Przestań marnować żądania — pozwól, aby wyniki same do Ciebie trafiały.**

<PollingVsWebhook />

Ponieważ każdy endpoint Enrow jest **asynchroniczny**, webhooki są zalecanym sposobem otrzymywania wyników w [Email Finder](/pl/api-reference/email-finder/find-single), [Email Verifier](/pl/api-reference/email-verifier/verify-single) oraz [Phone Finder](/pl/api-reference/phone/find-single). Webhooki całkowicie omijają również [limity szybkości](/pl/rate-limits), ponieważ to Enrow wywołuje Ciebie, a nie odwrotnie.

## Jak przebiega proces webhooka?

Proces webhooka zamienia pojedyncze żądanie wyszukiwania w automatyczne dostarczenie. Mówisz Enrow, dokąd wysłać wyniki, a Enrow robi resztę:

1. Wysyłasz **POST** z żądaniem wyszukiwania z adresem URL `webhook` w `settings`
2. Enrow natychmiast zwraca ID wyszukiwania
3. Enrow przetwarza wyszukiwanie w tle
4. Po zakończeniu Enrow wysyła **POST** z wynikami na Twój adres URL webhooka

## Jak skonfigurować webhooka?

Webhooka możesz zarejestrować na dwa sposoby, w zależności od tego, czy chcesz go dla jednego wyszukiwania, czy dla każdego:

1. **Dla pojedynczego żądania**: Dołącz adres URL `webhook` w obiekcie `settings` dowolnego wywołania API
2. **Globalnie**: Skonfiguruj domyślny webhook na [stronie integracji](https://app.enrow.io/integrations) w panelu

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

<Warning>
  Twój adres URL webhooka musi być prawidłowym endpointem **HTTPS**, który zwraca kod statusu `200`.
</Warning>

## Które zdarzenia wyzwalają wywołanie webhooka?

Sześć typów zdarzeń może wyzwolić wywołanie webhooka, po jednym na każdy endpoint i typ wyszukiwania:

| Zdarzenie                      | Opis                                                       |
| ------------------------------ | ---------------------------------------------------------- |
| `single_search_finished`       | Pojedyncze wyszukiwanie adresu e-mail zostało zakończone   |
| `bulk_search_finished`         | Masowe wyszukiwanie adresów e-mail zostało zakończone      |
| `verification_finished`        | Pojedyncza weryfikacja adresu e-mail została zakończona    |
| `bulk_verification_finished`   | Masowa weryfikacja adresów e-mail została zakończona       |
| `single_phone_search_finished` | Pojedyncze wyszukiwanie numeru telefonu zostało zakończone |
| `bulk_phone_search_finished`   | Masowe wyszukiwanie numerów telefonu zostało zakończone    |

## Jak wygląda ładunek webhooka?

Ładunek webhooka zależy od endpointu oraz od tego, czy wyszukiwanie było pojedyncze, czy masowe. Pojedyncze wyszukiwania dostarczają pełny wynik bezpośrednio, natomiast masowe wyszukiwania dostarczają powiadomienie o zakończeniu, po którym wykonujesz żądanie GET.

### Email Finder — pojedynczy

W przypadku pojedynczych wyszukiwań otrzymujesz **pełny wynik bezpośrednio** w powiadomieniu webhook. Eliminuje to potrzebę wykonywania żądania GET.

```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 — masowy

W przypadku masowych wyszukiwań otrzymujesz powiadomienie o zakończeniu partii. Następnie wywołaj endpoint [GET /email/find/bulk](/pl/api-reference/email-finder/get-bulk-results) z `id`, aby pobrać wyniki.

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

### Email Verifier — pojedynczy

Pełny wynik jest dołączony bezpośrednio — żądanie GET nie jest potrzebne.

```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 — masowy

To wyłącznie powiadomienie. Wywołaj [GET /email/verify/bulk](/pl/api-reference/email-verifier/get-bulk-verifications) z `id`, aby pobrać wyniki.

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

### Phone Finder — pojedynczy

Pełny wynik jest dołączony bezpośrednio — żądanie GET nie jest potrzebne.

```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 — masowy

To wyłącznie powiadomienie. Wywołaj [GET /phone/bulk](/pl/api-reference/phone/get-bulk-results) z `id`, aby pobrać wyniki.

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

## Czym różnią się webhooki pojedyncze i masowe?

Webhooki pojedynczych wyszukiwań zawierają kompletny wynik, więc nie jest potrzebne dodatkowe wywołanie. Webhooki masowych wyszukiwań jedynie sygnalizują, że partia jest gotowa — następnie pobierasz wyniki za pomocą odpowiadającego endpointu GET.

| Typ                | Pojedyncze wyszukiwania | Masowe wyszukiwania                |
| ------------------ | ----------------------- | ---------------------------------- |
| **Ładunek**        | Dołączony pełny wynik   | Tylko powiadomienie (ID + kredyty) |
| **Potrzebny GET?** | Nie                     | Tak — użyj endpointu GET z `id`    |

<Info>
  W przypadku pojedynczych wyszukiwań webhook zawiera wszystko, czego potrzebujesz. W przypadku masowych wyszukiwań webhook informuje, że partia jest gotowa — wtedy pobierasz wyniki.
</Info>

## Jakie są najlepsze praktyki dla endpointów webhooków?

Niezawodny endpoint webhooka odpowiada szybko, akceptuje wyłącznie HTTPS i toleruje sporadyczne duplikaty. Stosuj te praktyki, aby dostarczanie było niezawodne:

<AccordionGroup>
  <Accordion title="Szybko zwracaj 200">
    Przetwarzaj ładunki webhooków asynchronicznie. Zwróć `200` natychmiast, a następnie obsłuż dane w zadaniu działającym w tle.
  </Accordion>

  <Accordion title="Używaj HTTPS">
    Zawsze używaj endpointów HTTPS. Webhooki HTTP będą odrzucane.
  </Accordion>

  <Accordion title="Obsługuj duplikaty">
    W rzadkich przypadkach webhooki mogą zostać dostarczone więcej niż raz. Użyj pola `id`, aby usuwać duplikaty.
  </Accordion>

  <Accordion title="Używaj pól niestandardowych">
    Przekazuj dane `custom` w swoich żądaniach, aby zidentyfikować, do którego rekordu należy wynik webhooka:

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

    Pole `custom` jest zwracane w ładunku webhooka bez zmian.
  </Accordion>
</AccordionGroup>

## Czy używać webhooków, czy odpytywania?

Używaj webhooków w produkcji, a odpytywania tylko do szybkiego prototypowania lub debugowania. Webhooki dostarczają wyniki w czasie rzeczywistym, nie zużywając Twojego limitu żądań, natomiast odpytywanie wykonuje powtarzające się wywołania GET, które wliczają się do Twoich [limitów szybkości](/pl/rate-limits).

|                              | Webhooki                      | Odpytywanie (GET)               |
| ---------------------------- | ----------------------------- | ------------------------------- |
| **Opóźnienie**               | Czas rzeczywisty              | Zależy od interwału odpytywania |
| **Wywołania API**            | 0 (Enrow wywołuje Ciebie)     | Wiele wywołań na wyszukiwanie   |
| **Wpływ na limit szybkości** | Brak                          | Zużywa limit                    |
| **Złożoność**                | Wymaga konfiguracji endpointu | Prostsze we wdrożeniu           |

<Note>
  Zalecamy webhooki do użytku produkcyjnego. Odpytywania używaj tylko do szybkiego prototypowania lub debugowania.
</Note>

## FAQ

<AccordionGroup>
  <Accordion title="Czy webhooki kosztują dodatkowe kredyty?">
    Nie. Webhooki nie zużywają dodatkowych kredytów — płacisz tylko za samo wyszukiwanie. Koszt w kredytach jest podawany w polu `credits.cost` ładunku. Zobacz [Kredyty i rozliczenia](/pl/credits-billing), aby poznać koszty dla poszczególnych endpointów.
  </Accordion>

  <Accordion title="Co się stanie, jeśli mój endpoint nie zwróci 200?">
    Twój adres URL webhooka musi być prawidłowym endpointem HTTPS, który zwraca kod statusu `200`. Jeśli Twój serwer jest nieosiągalny lub odpowiada innym statusem, dostarczenie jest traktowane jako nieudane. Jako rozwiązanie awaryjne zawsze możesz pobrać wyniki, odpytując odpowiedni endpoint GET z `id` wyszukiwania.
  </Accordion>

  <Accordion title="Jak dopasować webhooka do pierwotnego żądania?">
    Użyj `id` z odpowiedzi wyszukiwania albo przekaż obiekt `custom` w swoim żądaniu — jest on zwracany w ładunku webhooka bez zmian, dzięki czemu możesz przyporządkować wyniki do własnych rekordów, takich jak ID leada w CRM.
  </Accordion>

  <Accordion title="Dlaczego nie otrzymałem webhooka?">
    Najczęstsze przyczyny to adres URL inny niż HTTPS, endpoint, który nie zwraca `200`, lub serwer, który przekracza limit czasu. Upewnij się, że Twój endpoint jest publicznie osiągalny przez HTTPS. Aby uzyskać szersze wskazówki dotyczące rozwiązywania problemów, zobacz [Obsługa błędów](/pl/error-handling) oraz [Kody statusu](/pl/status-codes).
  </Accordion>
</AccordionGroup>

## Następne kroki

<CardGroup cols={2}>
  <Card title="Znajdź adres e-mail" icon="envelope" href="/pl/api-reference/email-finder/find-single">
    Przekaż adres URL webhooka w settings, aby wynik został dostarczony automatycznie.
  </Card>

  <Card title="Pobierz wyniki masowe" icon="layer-group" href="/pl/api-reference/email-finder/get-bulk-results">
    Pobierz wyniki partii po wyzwoleniu webhooka bulk\_search\_finished.
  </Card>

  <Card title="Uwierzytelnianie" icon="key" href="/pl/authentication">
    Jak przekazać swój klucz API w nagłówku x-api-key.
  </Card>

  <Card title="Limity szybkości" icon="gauge-high" href="/pl/rate-limits">
    Zobacz, dlaczego webhooki unikają limitu żądań zużywanego przez odpytywanie.
  </Card>
</CardGroup>
