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

# Cómo funcionan los webhooks

> Recibe notificaciones de webhook en tiempo real cuando se completan las búsquedas y verificaciones de Enrow, en lugar de consultar la API de forma repetida

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

## ¿Por qué usar webhooks?

Los webhooks permiten que los resultados lleguen a ti en lugar de tener que pedirlos una y otra vez. En vez de consultar un endpoint GET hasta que una búsqueda termine, Enrow envía cada resultado a tu servidor en el momento en que está listo, lo que ahorra solicitudes, reduce la latencia y mantiene tu código simple.

**Deja de desperdiciar solicitudes: que los resultados lleguen a ti.**

<PollingVsWebhook />

Como cada endpoint de Enrow es **asíncrono**, los webhooks son la forma recomendada de recibir resultados en [Email Finder](/es/api-reference/email-finder/find-single), [Email Verifier](/es/api-reference/email-verifier/verify-single) y [Phone Finder](/es/api-reference/phone/find-single). Los webhooks también evitan por completo los [límites de tasa](/es/rate-limits), ya que es Enrow quien te llama y no al revés.

## ¿Cómo funciona un flujo de webhook?

Un flujo de webhook convierte una sola solicitud de búsqueda en una entrega automática. Le indicas a Enrow dónde enviar los resultados y Enrow hace el resto:

1. Haces un **POST** con una solicitud de búsqueda que incluye una URL de `webhook` en los `settings`
2. Enrow devuelve un ID de búsqueda de inmediato
3. Enrow procesa la búsqueda en segundo plano
4. Cuando termina, Enrow envía los resultados mediante **POST** a tu URL de webhook

## ¿Cómo configuro un webhook?

Puedes registrar un webhook de dos maneras, según si lo quieres para una búsqueda o para todas:

1. **Por solicitud**: Incluye una URL de `webhook` en el objeto `settings` de cualquier llamada a la API
2. **Global**: Configura un webhook predeterminado desde la [página de integraciones](https://app.enrow.io/integrations) del panel

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

<Warning>
  Tu URL de webhook debe ser un endpoint **HTTPS** válido que devuelva un código de estado `200`.
</Warning>

## ¿Qué eventos activan una llamada de webhook?

Seis tipos de eventos pueden activar una llamada de webhook, uno por endpoint y tipo de búsqueda:

| Evento                         | Descripción                                     |
| ------------------------------ | ----------------------------------------------- |
| `single_search_finished`       | Una búsqueda de un solo email ha finalizado     |
| `bulk_search_finished`         | Una búsqueda masiva de emails ha finalizado     |
| `verification_finished`        | Una verificación de un solo email ha finalizado |
| `bulk_verification_finished`   | Una verificación masiva de emails ha finalizado |
| `single_phone_search_finished` | Una búsqueda de un solo teléfono ha finalizado  |
| `bulk_phone_search_finished`   | Una búsqueda masiva de teléfonos ha finalizado  |

## ¿Qué aspecto tiene un payload de webhook?

El payload del webhook depende del endpoint y de si la búsqueda fue individual o masiva. Las búsquedas individuales entregan el resultado completo directamente, mientras que las búsquedas masivas entregan una notificación de finalización que debes complementar con una solicitud GET.

### Email Finder — Individual

En las búsquedas individuales, recibes el **resultado completo directamente** en la notificación del webhook. Esto elimina la necesidad de realizar una solicitud 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 — Masivo

En las búsquedas masivas, recibes una notificación de que el lote ha finalizado. Luego llama al endpoint [GET /email/find/bulk](/es/api-reference/email-finder/get-bulk-results) con el `id` para recuperar los resultados.

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

### Email Verifier — Individual

El resultado completo se incluye directamente: no se necesita ninguna solicitud GET.

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

Esto es solo una notificación. Llama a [GET /email/verify/bulk](/es/api-reference/email-verifier/get-bulk-verifications) con el `id` para recuperar los resultados.

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

### Phone Finder — Individual

El resultado completo se incluye directamente: no se necesita ninguna solicitud GET.

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

Esto es solo una notificación. Llama a [GET /phone/bulk](/es/api-reference/phone/get-bulk-results) con el `id` para recuperar los resultados.

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

## ¿En qué se diferencian los webhooks individuales y masivos?

Los webhooks de búsqueda individual contienen el resultado completo, por lo que no se necesita ninguna llamada adicional. Los webhooks de búsqueda masiva solo indican que el lote ha terminado: luego recuperas los resultados con el endpoint GET correspondiente.

| Tipo                  | Búsquedas individuales      | Búsquedas masivas                   |
| --------------------- | --------------------------- | ----------------------------------- |
| **Payload**           | Resultado completo incluido | Solo notificación (ID + créditos)   |
| **¿Se necesita GET?** | No                          | Sí, usa el endpoint GET con el `id` |

<Info>
  En las búsquedas individuales, el webhook contiene todo lo que necesitas. En las búsquedas masivas, el webhook te indica que el lote ha terminado y luego recuperas los resultados.
</Info>

## ¿Cuáles son las mejores prácticas para los endpoints de webhook?

Un endpoint de webhook fiable responde rápido, acepta solo HTTPS y tolera algún duplicado ocasional. Sigue estas prácticas para que las entregas sean confiables:

<AccordionGroup>
  <Accordion title="Devuelve 200 rápidamente">
    Procesa los payloads de los webhooks de forma asíncrona. Devuelve un `200` de inmediato y luego maneja los datos en una tarea en segundo plano.
  </Accordion>

  <Accordion title="Usa HTTPS">
    Usa siempre endpoints HTTPS. Los webhooks HTTP serán rechazados.
  </Accordion>

  <Accordion title="Gestiona los duplicados">
    En casos poco frecuentes, los webhooks pueden entregarse más de una vez. Usa el campo `id` para eliminar duplicados.
  </Accordion>

  <Accordion title="Usa campos personalizados">
    Pasa datos en `custom` en tus solicitudes para identificar a qué registro pertenece el resultado de un webhook:

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

    El campo `custom` se devuelve tal cual en el payload del webhook.
  </Accordion>
</AccordionGroup>

## ¿Debo usar webhooks o polling?

Usa webhooks en producción y polling solo para prototipado rápido o depuración. Los webhooks entregan resultados en tiempo real sin consumir tu cuota de solicitudes, mientras que el polling realiza llamadas GET repetidas que cuentan para tus [límites de tasa](/es/rate-limits).

|                                  | Webhooks                        | Polling (GET)                     |
| -------------------------------- | ------------------------------- | --------------------------------- |
| **Latencia**                     | En tiempo real                  | Depende del intervalo de consulta |
| **Llamadas a la API**            | 0 (Enrow te llama)              | Varias llamadas por búsqueda      |
| **Impacto en el límite de tasa** | Ninguno                         | Consume cuota                     |
| **Complejidad**                  | Requiere configurar un endpoint | Más simple de implementar         |

<Note>
  Recomendamos los webhooks para uso en producción. Usa polling solo para prototipado rápido o depuración.
</Note>

## FAQ

<AccordionGroup>
  <Accordion title="¿Los webhooks cuestan créditos adicionales?">
    No. Los webhooks no consumen créditos adicionales: solo pagas por la búsqueda en sí. El costo en créditos se informa en el campo `credits.cost` del payload. Consulta [Créditos y facturación](/es/credits-billing) para ver los costos por endpoint.
  </Accordion>

  <Accordion title="¿Qué ocurre si mi endpoint no devuelve un 200?">
    Tu URL de webhook debe ser un endpoint HTTPS válido que devuelva un código de estado `200`. Si tu servidor es inaccesible o responde con otro estado, la entrega se considera fallida. Como alternativa, siempre puedes recuperar los resultados consultando el endpoint GET correspondiente con el `id` de la búsqueda.
  </Accordion>

  <Accordion title="¿Cómo asocio un webhook con la solicitud original?">
    Usa el `id` de la respuesta de búsqueda, o pasa un objeto `custom` en tu solicitud: se devuelve tal cual en el payload del webhook para que puedas mapear los resultados con tus propios registros, como el ID de un lead de tu CRM.
  </Accordion>

  <Accordion title="¿Por qué no recibí un webhook?">
    Las causas más comunes son una URL que no es HTTPS, un endpoint que no devuelve `200` o un servidor que agota el tiempo de espera. Confirma que tu endpoint sea accesible públicamente a través de HTTPS. Para una resolución de problemas más amplia, consulta [Manejo de errores](/es/error-handling) y [Códigos de estado](/es/status-codes).
  </Accordion>
</AccordionGroup>

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Buscar un email" icon="envelope" href="/es/api-reference/email-finder/find-single">
    Pasa una URL de webhook en settings para recibir el resultado de forma automática.
  </Card>

  <Card title="Obtener resultados masivos" icon="layer-group" href="/es/api-reference/email-finder/get-bulk-results">
    Recupera los resultados del lote después de que se dispare un webhook bulk\_search\_finished.
  </Card>

  <Card title="Autenticación" icon="key" href="/es/authentication">
    Cómo pasar tu clave de API en la cabecera x-api-key.
  </Card>

  <Card title="Límites de tasa" icon="gauge-high" href="/es/rate-limits">
    Descubre por qué los webhooks evitan la cuota de solicitudes que consume el polling.
  </Card>
</CardGroup>
