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

# Como funcionam os webhooks

> Receba notificações de webhook em tempo real quando as buscas e verificações da Enrow forem concluídas, em vez de consultar a API repetidamente

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 que usar webhooks?

Os webhooks fazem com que os resultados cheguem até você, em vez de você ter que pedi-los repetidamente. Em vez de consultar um endpoint GET até que uma busca termine, a Enrow envia cada resultado para o seu servidor no momento em que ele está pronto — economizando requisições, reduzindo a latência e mantendo seu código simples.

**Pare de desperdiçar requisições — deixe os resultados chegarem até você.**

<PollingVsWebhook />

Como todos os endpoints da Enrow são **assíncronos**, os webhooks são a forma recomendada de receber resultados em [Email Finder](/pt/api-reference/email-finder/find-single), [Email Verifier](/pt/api-reference/email-verifier/verify-single) e [Phone Finder](/pt/api-reference/phone/find-single). Os webhooks também ignoram completamente os [limites de taxa](/pt/rate-limits), já que é a Enrow quem chama você, e não o contrário.

## Como funciona um fluxo de webhook?

Um fluxo de webhook transforma uma única requisição de busca em uma entrega automática. Você diz à Enrow para onde enviar os resultados, e a Enrow faz o resto:

1. Você faz um **POST** de uma requisição de busca com uma URL de `webhook` nas `settings`
2. A Enrow retorna um ID de busca imediatamente
3. A Enrow processa a busca em segundo plano
4. Quando concluída, a Enrow faz um **POST** dos resultados para a sua URL de webhook

## Como configuro um webhook?

Você pode registrar um webhook de duas maneiras, dependendo se deseja usá-lo para uma busca ou para todas as buscas:

1. **Por requisição**: Inclua uma URL de `webhook` no objeto `settings` de qualquer chamada de API
2. **Global**: Configure um webhook padrão na [página de integrações](https://app.enrow.io/integrations) do painel

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

<Warning>
  Sua URL de webhook deve ser um endpoint **HTTPS** válido que retorne um código de status `200`.
</Warning>

## Quais eventos disparam uma chamada de webhook?

Seis tipos de eventos podem disparar uma chamada de webhook, um por endpoint e tipo de busca:

| Event                          | Descrição                                          |
| ------------------------------ | -------------------------------------------------- |
| `single_search_finished`       | Uma busca de e-mail individual foi concluída       |
| `bulk_search_finished`         | Uma busca de e-mail em massa foi concluída         |
| `verification_finished`        | Uma verificação de e-mail individual foi concluída |
| `bulk_verification_finished`   | Uma verificação de e-mail em massa foi concluída   |
| `single_phone_search_finished` | Uma busca de telefone individual foi concluída     |
| `bulk_phone_search_finished`   | Uma busca de telefone em massa foi concluída       |

## Como é o payload de um webhook?

O payload do webhook depende do endpoint e de a busca ser individual ou em massa. Buscas individuais entregam o resultado completo diretamente, enquanto buscas em massa entregam uma notificação de conclusão que você complementa com uma requisição GET.

### Email Finder — Individual

Para buscas individuais, você recebe o **resultado completo diretamente** na notificação do webhook. Isso elimina a necessidade de fazer uma requisição 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 — Em massa

Para buscas em massa, você recebe uma notificação de que o lote foi concluído. Em seguida, chame o endpoint [GET /email/find/bulk](/pt/api-reference/email-finder/get-bulk-results) com o `id` para recuperar os resultados.

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

### Email Verifier — Individual

O resultado completo é incluído diretamente — não é necessária uma requisição 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 — Em massa

Esta é apenas uma notificação. Chame [GET /email/verify/bulk](/pt/api-reference/email-verifier/get-bulk-verifications) com o `id` para recuperar os resultados.

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

### Phone Finder — Individual

O resultado completo é incluído diretamente — não é necessária uma requisição 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 — Em massa

Esta é apenas uma notificação. Chame [GET /phone/bulk](/pt/api-reference/phone/get-bulk-results) com o `id` para recuperar os resultados.

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

## Qual a diferença entre webhooks individuais e em massa?

Os webhooks de busca individual contêm o resultado completo, então nenhuma chamada extra é necessária. Os webhooks de busca em massa apenas sinalizam que o lote foi concluído — em seguida, você busca os resultados com o endpoint GET correspondente.

| Tipo                | Buscas individuais          | Buscas em massa                     |
| ------------------- | --------------------------- | ----------------------------------- |
| **Payload**         | Resultado completo incluído | Apenas notificação (ID + créditos)  |
| **GET necessário?** | Não                         | Sim — use o endpoint GET com o `id` |

<Info>
  Para buscas individuais, o webhook contém tudo o que você precisa. Para buscas em massa, o webhook informa que o lote foi concluído — então você busca os resultados.
</Info>

## Quais são as boas práticas para endpoints de webhook?

Um endpoint de webhook confiável responde rapidamente, aceita apenas HTTPS e tolera duplicatas ocasionais. Siga estas práticas para manter as entregas confiáveis:

<AccordionGroup>
  <Accordion title="Retorne 200 rapidamente">
    Processe os payloads de webhook de forma assíncrona. Retorne um `200` imediatamente e, em seguida, trate os dados em uma tarefa em segundo plano.
  </Accordion>

  <Accordion title="Use HTTPS">
    Sempre use endpoints HTTPS. Webhooks via HTTP serão rejeitados.
  </Accordion>

  <Accordion title="Trate duplicatas">
    Em casos raros, os webhooks podem ser entregues mais de uma vez. Use o campo `id` para fazer a deduplicação.
  </Accordion>

  <Accordion title="Use campos personalizados">
    Passe dados em `custom` nas suas requisições para identificar a qual registro pertence o resultado de um webhook:

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

    O campo `custom` é retornado inalterado no payload do webhook.
  </Accordion>
</AccordionGroup>

## Devo usar webhooks ou polling?

Use webhooks em produção e polling apenas para prototipagem rápida ou depuração. Os webhooks entregam resultados em tempo real sem consumir sua cota de requisições, enquanto o polling faz chamadas GET repetidas que contam para os seus [limites de taxa](/pt/rate-limits).

|                               | Webhooks                        | Polling (GET)                   |
| ----------------------------- | ------------------------------- | ------------------------------- |
| **Latência**                  | Tempo real                      | Depende do intervalo de polling |
| **Chamadas de API**           | 0 (a Enrow chama você)          | Várias chamadas por busca       |
| **Impacto no limite de taxa** | Nenhum                          | Consome a cota                  |
| **Complexidade**              | Requer configuração de endpoint | Mais simples de implementar     |

<Note>
  Recomendamos webhooks para uso em produção. Use polling apenas para prototipagem rápida ou depuração.
</Note>

## FAQ

<AccordionGroup>
  <Accordion title="Os webhooks custam créditos extras?">
    Não. Os webhooks não consomem créditos adicionais — você paga apenas pela busca em si. O custo em créditos é informado no campo `credits.cost` do payload. Consulte [Créditos e cobrança](/pt/credits-billing) para ver os custos por endpoint.
  </Accordion>

  <Accordion title="O que acontece se meu endpoint não retornar um 200?">
    Sua URL de webhook deve ser um endpoint HTTPS válido que retorne um código de status `200`. Se o seu servidor estiver inacessível ou responder com outro status, a entrega é considerada como falha. Como alternativa, você sempre pode recuperar os resultados consultando o endpoint GET correspondente com o `id` da busca.
  </Accordion>

  <Accordion title="Como faço para associar um webhook à requisição original?">
    Use o `id` da resposta da busca ou passe um objeto `custom` na sua requisição — ele é retornado inalterado no payload do webhook, para que você possa mapear os resultados de volta aos seus próprios registros, como o ID de um lead no CRM.
  </Accordion>

  <Accordion title="Por que não recebi um webhook?">
    As causas mais comuns são uma URL que não usa HTTPS, um endpoint que não retorna `200` ou um servidor que atinge o tempo limite. Confirme que seu endpoint está acessível publicamente por HTTPS. Para uma solução de problemas mais ampla, consulte [Tratamento de erros](/pt/error-handling) e [Códigos de status](/pt/status-codes).
  </Accordion>
</AccordionGroup>

## Próximos passos

<CardGroup cols={2}>
  <Card title="Encontrar um e-mail" icon="envelope" href="/pt/api-reference/email-finder/find-single">
    Passe uma URL de webhook nas settings para receber o resultado automaticamente.
  </Card>

  <Card title="Obter resultados em massa" icon="layer-group" href="/pt/api-reference/email-finder/get-bulk-results">
    Busque os resultados do lote após o disparo de um webhook bulk\_search\_finished.
  </Card>

  <Card title="Autenticação" icon="key" href="/pt/authentication">
    Como passar sua chave de API no cabeçalho x-api-key.
  </Card>

  <Card title="Limites de taxa" icon="gauge-high" href="/pt/rate-limits">
    Veja por que os webhooks evitam a cota de requisições que o polling consome.
  </Card>
</CardGroup>
