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

# Fonctionnement des webhooks

> Receive real-time webhook notifications when Enrow searches and verifications complete, instead of polling the 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>;
};

## Pourquoi utiliser les webhooks ?

Les webhooks font venir les résultats à vous au lieu de vous obliger à les demander sans cesse. Plutôt que d'interroger en boucle un endpoint GET jusqu'à la fin d'une recherche, Enrow envoie chaque résultat à votre serveur dès qu'il est prêt, ce qui économise des requêtes, réduit la latence et garde votre code simple.

**Arrêtez de gaspiller des requêtes : laissez les résultats venir à vous.**

<PollingVsWebhook />

Comme chaque endpoint Enrow est **asynchrone**, les webhooks sont le moyen recommandé pour recevoir les résultats sur [Email Finder](/fr/api-reference/email-finder/find-single), [Email Verifier](/fr/api-reference/email-verifier/verify-single) et [Phone Finder](/fr/api-reference/phone/find-single). Les webhooks contournent également entièrement les [limites de débit](/fr/rate-limits), puisque c'est Enrow qui vous appelle et non l'inverse.

## Comment fonctionne un flux de webhook ?

Un flux de webhook transforme une simple requête de recherche en une livraison automatique. Vous indiquez à Enrow où envoyer les résultats, et Enrow se charge du reste :

1. Vous envoyez en **POST** une requête de recherche avec une URL `webhook` dans les `settings`
2. Enrow renvoie immédiatement un ID de recherche
3. Enrow traite la recherche en arrière-plan
4. Une fois terminée, Enrow envoie en **POST** les résultats vers l'URL de votre webhook

## Comment configurer un webhook ?

Vous pouvez enregistrer un webhook de deux manières, selon que vous le souhaitez pour une seule recherche ou pour toutes les recherches :

1. **Par requête** : incluez une URL `webhook` dans l'objet `settings` de n'importe quel appel d'API
2. **Globale** : configurez un webhook par défaut depuis la [page des intégrations](https://app.enrow.io/integrations) du tableau de bord

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

<Warning>
  L'URL de votre webhook doit être un endpoint **HTTPS** valide qui renvoie un code de statut `200`.
</Warning>

## Quels événements déclenchent un appel de webhook ?

Six types d'événements peuvent déclencher un appel de webhook, un par endpoint et par type de recherche :

| Event                          | Description                                       |
| ------------------------------ | ------------------------------------------------- |
| `single_search_finished`       | Une recherche d'e-mail unique est terminée        |
| `bulk_search_finished`         | Une recherche d'e-mails en masse est terminée     |
| `verification_finished`        | Une vérification d'e-mail unique est terminée     |
| `bulk_verification_finished`   | Une vérification d'e-mails en masse est terminée  |
| `single_phone_search_finished` | Une recherche de téléphone unique est terminée    |
| `bulk_phone_search_finished`   | Une recherche de téléphones en masse est terminée |

## À quoi ressemble une charge utile de webhook ?

La charge utile du webhook dépend de l'endpoint et du caractère unique ou en masse de la recherche. Les recherches uniques livrent directement le résultat complet, tandis que les recherches en masse livrent une notification d'achèvement que vous complétez par une requête GET.

### Email Finder — Unique

Pour les recherches uniques, vous recevez le **résultat complet directement** dans la notification du webhook. Cela évite d'avoir à effectuer une requête 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 — En masse

Pour les recherches en masse, vous recevez une notification indiquant que le lot est terminé. Appelez ensuite l'endpoint [GET /email/find/bulk](/fr/api-reference/email-finder/get-bulk-results) avec l'`id` pour récupérer les résultats.

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

### Email Verifier — Unique

Le résultat complet est inclus directement, aucune requête GET n'est nécessaire.

```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 — En masse

Il s'agit uniquement d'une notification. Appelez [GET /email/verify/bulk](/fr/api-reference/email-verifier/get-bulk-verifications) avec l'`id` pour récupérer les résultats.

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

### Phone Finder — Unique

Le résultat complet est inclus directement, aucune requête GET n'est nécessaire.

```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 — En masse

Il s'agit uniquement d'une notification. Appelez [GET /phone/bulk](/fr/api-reference/phone/get-bulk-results) avec l'`id` pour récupérer les résultats.

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

## Quelle différence entre les webhooks uniques et en masse ?

Les webhooks de recherche unique contiennent le résultat complet, aucun appel supplémentaire n'est donc nécessaire. Les webhooks de recherche en masse signalent uniquement que le lot est terminé : vous récupérez ensuite les résultats avec l'endpoint GET correspondant.

| Type                 | Recherches uniques      | Recherches en masse                       |
| -------------------- | ----------------------- | ----------------------------------------- |
| **Charge utile**     | Résultat complet inclus | Notification uniquement (ID + crédits)    |
| **GET nécessaire ?** | Non                     | Oui — utilisez l'endpoint GET avec l'`id` |

<Info>
  Pour les recherches uniques, le webhook contient tout ce dont vous avez besoin. Pour les recherches en masse, le webhook vous indique que le lot est terminé, puis vous récupérez les résultats.
</Info>

## Quelles sont les bonnes pratiques pour les endpoints de webhook ?

Un endpoint de webhook fiable répond rapidement, n'accepte que le HTTPS et tolère les doublons occasionnels. Suivez ces pratiques pour garder des livraisons fiables :

<AccordionGroup>
  <Accordion title="Renvoyez 200 rapidement">
    Traitez les charges utiles des webhooks de manière asynchrone. Renvoyez immédiatement un `200`, puis traitez les données dans une tâche en arrière-plan.
  </Accordion>

  <Accordion title="Utilisez HTTPS">
    Utilisez toujours des endpoints HTTPS. Les webhooks HTTP seront rejetés.
  </Accordion>

  <Accordion title="Gérez les doublons">
    Dans de rares cas, les webhooks peuvent être livrés plus d'une fois. Utilisez le champ `id` pour dédupliquer.
  </Accordion>

  <Accordion title="Utilisez des champs personnalisés">
    Transmettez des données `custom` dans vos requêtes pour identifier l'enregistrement auquel correspond un résultat de webhook :

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

    Le champ `custom` est renvoyé tel quel dans la charge utile du webhook.
  </Accordion>
</AccordionGroup>

## Faut-il utiliser les webhooks ou le polling ?

Utilisez les webhooks en production et le polling uniquement pour du prototypage rapide ou du débogage. Les webhooks livrent les résultats en temps réel sans consommer votre quota de requêtes, tandis que le polling effectue des appels GET répétés qui sont décomptés de vos [limites de débit](/fr/rate-limits).

|                                   | Webhooks                                 | Polling (GET)                          |
| --------------------------------- | ---------------------------------------- | -------------------------------------- |
| **Latence**                       | Temps réel                               | Dépend de l'intervalle d'interrogation |
| **Appels d'API**                  | 0 (Enrow vous appelle)                   | Plusieurs appels par recherche         |
| **Impact sur la limite de débit** | Aucun                                    | Consomme du quota                      |
| **Complexité**                    | Nécessite la configuration d'un endpoint | Plus simple à mettre en œuvre          |

<Note>
  Nous recommandons les webhooks pour une utilisation en production. N'utilisez le polling que pour le prototypage rapide ou le débogage.
</Note>

## FAQ

<AccordionGroup>
  <Accordion title="Les webhooks coûtent-ils des crédits supplémentaires ?">
    Non. Les webhooks ne consomment pas de crédits supplémentaires : vous ne payez que pour la recherche elle-même. Le coût en crédits est indiqué dans le champ `credits.cost` de la charge utile. Consultez [Crédits et facturation](/fr/credits-billing) pour connaître les coûts par endpoint.
  </Accordion>

  <Accordion title="Que se passe-t-il si mon endpoint ne renvoie pas un 200 ?">
    L'URL de votre webhook doit être un endpoint HTTPS valide qui renvoie un code de statut `200`. Si votre serveur est injoignable ou répond avec un autre statut, la livraison est considérée comme échouée. En solution de repli, vous pouvez toujours récupérer les résultats en interrogeant l'endpoint GET concerné avec l'`id` de la recherche.
  </Accordion>

  <Accordion title="Comment associer un webhook à la requête d'origine ?">
    Utilisez l'`id` de la réponse de recherche, ou transmettez un objet `custom` dans votre requête : il est renvoyé tel quel dans la charge utile du webhook, ce qui vous permet de relier les résultats à vos propres enregistrements, comme un identifiant de lead CRM.
  </Accordion>

  <Accordion title="Pourquoi n'ai-je pas reçu de webhook ?">
    Les causes les plus fréquentes sont une URL non HTTPS, un endpoint qui ne renvoie pas `200`, ou un serveur qui expire. Vérifiez que votre endpoint est accessible publiquement en HTTPS. Pour un dépannage plus approfondi, consultez [Gestion des erreurs](/fr/error-handling) et [Codes de statut](/fr/status-codes).
  </Accordion>
</AccordionGroup>

## Étapes suivantes

<CardGroup cols={2}>
  <Card title="Trouver un e-mail" icon="envelope" href="/fr/api-reference/email-finder/find-single">
    Transmettez une URL de webhook dans les settings pour recevoir le résultat automatiquement.
  </Card>

  <Card title="Récupérer des résultats en masse" icon="layer-group" href="/fr/api-reference/email-finder/get-bulk-results">
    Récupérez les résultats du lot après le déclenchement d'un webhook bulk\_search\_finished.
  </Card>

  <Card title="Authentification" icon="key" href="/fr/authentication">
    Comment transmettre votre clé API dans l'en-tête x-api-key.
  </Card>

  <Card title="Limites de débit" icon="gauge-high" href="/fr/rate-limits">
    Découvrez pourquoi les webhooks évitent le quota de requêtes que le polling consomme.
  </Card>
</CardGroup>
