Capturia mesure ses publicités et la fréquentation du site avec le pixel Meta et Google Analytics. Tu peux refuser en tout temps. En savoir plus

EN

Leads

1 endpoint

post/v1/leads/capture

Capturer un lead depuis un funnel externe

Capture un nouveau lead dans Capturia depuis un funnel, formulaire ou outil tiers (Make, Zapier, n8n, landing custom). Une fois capturé, le lead apparaît immédiatement dans Mes Prospects côté dashboard et le bot SMS prend le relais dans les 30 secondes si le consentement et la configuration sont en ordre.

Contact déjà connu (courriel ou téléphone)

La fiche est résolue par courriel d'abord (insensible à la casse), puis par téléphone, en incluant les coordonnées secondaires déjà rattachées à une fiche. Capturer un lead dont le courriel ou le téléphone existe déjà réutilise et enrichit la fiche existante — le contact_id retourné est celui de cette fiche, jamais un doublon et jamais une erreur. Les coordonnées et champs cœur déjà remplis (nom, courriel, téléphone, pipeline, valeur, adresse...) ne sont pas écrasés ; les réponses de qualification (custom_fields) sont en revanche rafraîchies avec les dernières valeurs envoyées — une clé déjà présente est remplacée, une clé nouvelle s'ajoute. Un nouveau téléphone est conservé comme numéro secondaire quand la fiche en a déjà un. Si le courriel et le téléphone pointent deux fiches différentes, le courriel a priorité et aucune coordonnée n'est retirée à l'autre fiche.

La réponse expose contact_was_new pour distinguer les deux issues : sur une fiche existante (false), pipeline et assignment_status décrivent la demande résolue, pas forcément l'état persisté — la fiche conserve son pipeline, son étape et son vendeur déjà en place, et ignored_fields nomme ceux des champs du bloc pipeline qu'elle n'a pas pris (existing_contact_kept).

Champs reçus non pris (ignored_fields)

Une capture qui répond 200 dit ce qu'elle n'a pas pris. La réponse porte ignored_fields, une liste [{ field, reason }] toujours présente et vide quand tout a été appliqué :

  • unknown_field — un nom que l'API ne connaît pas, à la racine, dans customData ou dans un bloc (pipeline.id, utm.utm_source) ;
  • custom_field_not_defined — une clé de custom_fields sans définition du même nom dans le compte ;
  • reserved_tag — une étiquette au nom réservé (tags[1]) ;
  • system_not_active — un system_id qui n'est pas actif ;
  • existing_contact_kept — un champ du bloc pipeline que la fiche existante a conservé ;
  • sales_rep_not_found — un courriel de vendeur qui ne désigne aucun membre actif du compte ;
  • submission_not_recorded — ce qui ne s'applique qu'avec une soumission enregistrée, quand elle ne l'est pas (existing_pipeline_contact, idempotent_replay) ;
  • duplicate_field — une copie d'un champ reçu par plusieurs sources, écartée au profit d'une autre valeur.

Seule limite : sur une fiche existante, les champs d'identité et d'adresse déjà remplis, et l'attribution publicitaire déjà posée (règle du premier contact), sont conservés sans être nommés un à un (contact_was_new: false le signale).

Le lead est capturé dans tous ces cas : c'est un avertissement de montage, à lire au premier test d'une intégration. La liste est plafonnée à 50 entrées (ignored_fields_truncated), les champs mappés à la main avant ceux qu'un connecteur ajoute d'office.

Pipeline et étape

Le bloc pipeline désigne le pipeline et l'étape par leur identifiant texte (pipeline_slug, stage_slug), à copier dans le dashboard : Administration, Clés API, section « Identifiants de tes pipelines et étapes ». Un connecteur qui n'envoie que des paires clé/valeur (workflow GoHighLevel, Zapier) les envoie à plat : pipeline.pipeline_slug, pipeline.stage_slug. Sans pipeline_slug, l'étape est cherchée dans le pipeline par défaut.

Contact déjà en pipeline ouvert

Un lead dont le contact (résolu par courriel ou téléphone) est déjà dans une étape de pipeline ouverte (déjà pris en charge par un vendeur — non archivé, étape ni gagnée ni perdue) n'est pas re-soumis : la réponse renvoie status: existing_pipeline_contact, aucune nouvelle soumission n'est enregistrée et le bot SMS n'est pas redéclenché, pour ne pas écraser une conversation en cours. Les champs cœur nouvellement fournis enrichissent quand même le contact existant.

La dédup de retry reste prioritaire : un re-push reçu dans la fenêtre de 5 minutes est traité comme un retry (status: idempotent_replay, voir Idempotency) et conserve l'activity_id et l'original_created_at d'origine — existing_pipeline_contact ne s'applique qu'au-delà de cette fenêtre.

Idempotency

Le header Idempotency-Key est optionnel mais recommandé pour les retries côté client. Une 2e requête avec la même clé et le même payload renvoie la réponse d'origine sans dupliquer le lead. La clé est stockée 24h. Une 2e requête avec la même clé mais un payload différent retourne 409 idempotency_conflict. La RPC sous-jacente conserve par ailleurs sa propre fenêtre de dedup à 5 minutes. Cette fenêtre est par contact et indépendante du contenu : toute soumission d'un contact déjà capturé dans les 5 dernières minutes est traitée comme un retry (idempotent_replay) même si le payload diffère — la fiche est tout de même enrichie (dont les custom_fields, rafraîchis), mais aucune nouvelle activité, aucun tag, et pas de relance du bot SMS. Les mises à jour de la fiche restent réelles : une automatisation qui surveille un champ personnalisé réagit au changement de valeur, comme pour toute modification de la fiche. C'est aussi ce qui protège d'une double relance du bot quand un lead re-soumet son formulaire coup sur coup. Un Idempotency-Key différent ne contourne pas cette fenêtre : elle s'applique en base après tout cache miss. Deux soumissions volontairement distinctes du même contact ne produisent deux activités que si elles sont espacées de plus de 5 minutes.

Codes d'erreur custom

Pour des raisons historiques, certains rejets retournent des codes hors catalog standard :

  • 422 — invalid_phone, missing_consent, invalid_email, invalid_payload, pipeline_not_found, stage_not_found (au lieu de validation_error / business_rule_violation). Body shape simplifiée propre à cette route — voir la réponse 422 ci-dessous.
  • 429 — rate_limited_ip quand la limite IP (100 req/min) est atteinte (au lieu de rate_limit_exceeded). Le header X-RateLimit-Scope: ip identifie le scope de la limite déclenchée. Body shape simplifiée (pas de request_id ni details standard) — à harmoniser dans une phase ultérieure.
Scopes requis: leads:capture

Corps de requête

PropriétéTypeObligatoireDescription
phonestring | integerobligatoireTéléphone (sera normalisé E.164). Format final attendu : ^\+[1-9]\d{10,14}$ (entre 11 et 15 chiffres après le +). Aussi accepté : un entier JSON positif dans la zone sûre JavaScript (au plus 9007199254740991 — tout numéro E.164 y tient), converti en chaîne avant normalisation ; connecteurs no-code dont le champ source est numérique. Préférer la chaîne — seule forme qui préserve un + international explicite. Tout autre nombre (négatif, décimal, zéro) est refusé en 422 invalid_phone avec le rappel des formats acceptés.
sms_consentboolean | stringobligatoireConsentement SMS explicite. Doit être true (boolean) ou une chaîne truthy whitelisted (voir description du schema). Les valeurs ambiguës (false, "no", 0, etc.) sont rejetées avec 422 missing_consent. Accepté à la racine du body ou dans l'objet customData (webhooks GoHighLevel).
first_namestringoptionnel—
last_namestringoptionnel—
emailstringoptionnel—
source_labelstringoptionnelLibellé libre identifiant la source du lead (ex funnel-fb-jan, landing-blueprint). Affiché dans le pipeline et le journal d'activité du contact.
utmLeadCaptureUtmoptionnel—
gclidstringoptionnelIdentifiant de clic Google Ads (paramètre gclid de l'URL d'atterrissage, auto-tagging). Conservé avec l'attribution publicitaire du lead (first-touch). Sans utm.source, le lead est attribué à la source google et au support cpc, ce qui le rattache à la campagne dont la provenance web est google / cpc. Quand meta.ads_consent vaut false, l'identifiant n'est jamais conservé (ni dans l'attribution ni dans la copie reçue) : seule la provenance google / cpc reste.
notesstringoptionnelNotes libres associées au lead (visible dans la fiche contact).
tagsstring[]optionnelTags à associer au contact créé. Si un tag n'existe pas pour ce client, il est créé à la volée. Tout nom qui commence par « Campagne : » (casse et espaces libres) est ignoré : ce début de nom est réservé aux tags de campagne, que Capturia pose seul d'après la campagne du lead. La réponse le nomme par sa position dans ignored_fields (tags[1], raison reserved_tag).
qualification_answersarrayoptionnelListe de questions/réponses de qualification capturées en amont.
pipelineLeadCapturePipelineConfigoptionnel—
custom_fieldsobjectoptionnelChamps personnalisés du contact, keyés par le NAME de la définition configurée côté client (ex type_propriete), pas par son UUID interne — l'integrator d'un site ne connaît pas les ids. La capture résout name → définition par client puis remplit la fiche contact et le kanban. Une clé sans définition correspondante est ignorée (pas d'erreur) et nommée dans ignored_fields de la réponse (custom_fields.ma_cle, raison custom_field_not_defined). Pour un champ select, fournir l'option value (ex maison_isolee), pas le label.
addressstringoptionnelAdresse postale du contact (texte libre, ex la propriété à vendre).
postal_codestringoptionnelCode postal du contact.
address_place_idstringoptionnelIdentifiant de lieu (ex Google Place ID) si l'adresse provient d'un autocomplete. Stocké tel quel pour ré-résolution ultérieure.
streetstringoptionnelRue (numéro civique compris). Complète une fiche existante sans écraser une rue déjà remplie.
citystringoptionnelVille. Complète une fiche existante sans écraser une ville déjà remplie.
sectorstringoptionnelSecteur ou quartier. Complète une fiche existante sans écraser un secteur déjà rempli.
provincestringoptionnelProvince. Complète une fiche existante sans écraser une province déjà remplie.
countrystringoptionnelPays. Complète une fiche existante sans écraser un pays déjà rempli.
metaLeadCaptureMetaAttributionoptionnel—
system_idstringoptionnelSystème cible de la capture (UUID d'un Système du compte, visible dans le dashboard Capturia). Le lead capturé est engagé dans ce Système. Absent → Système principal (comportement historique). Un UUID qui ne correspond à aucun Système du compte → erreur system_not_found (422), aucun contact créé. Un Système du compte qui n'est pas actif (archivé, par exemple) → le lead va au Système principal, et la réponse nomme system_id dans ignored_fields (raison system_not_active).

Responses

StatutDescription
200

Lead capturé (status: created), replay idempotent d'une capture précédente (status: idempotent_replay), ou contact déjà pris en charge (status: existing_pipeline_contact — déjà dans une étape de pipeline ouverte : aucune nouvelle soumission n'est enregistrée et le bot SMS n'est pas déclenché, activity_id est null). Dans tous les cas, le contact_id retourné est stable.

400

La requête est mal formée (header manquant, JSON invalide, paramètre invalide).

401

Clé API inconnue ou format invalide.

403

Clé sans le scope leads:capture (insufficient_scope), ou organisation qui n'a pas encore accepté les conditions d'utilisation de l'API (terms_acceptance_required). Enveloppe d'erreur standard.

409

Une requête précédente avec la même Idempotency-Key a utilisé un payload différent, ou est encore en cours de traitement.

422

Payload refusé par la validation (invalid_phone, missing_consent, invalid_email, invalid_payload) ou par la résolution du pipeline (pipeline_not_found, stage_not_found). Forme simplifiée propre à cette route : pas de type ni de doc_url, et details est un tableau [{ field, reason }] (field vide quand le body n'est pas un objet). Seul system_not_found (un system_id qui ne désigne aucun Système du compte) revient dans l'enveloppe d'erreur standard. Pour un champ scalaire fautif, le message renvoie en écho la valeur reçue (bornée à 120 caractères) — elle n'est jamais conservée côté Capturia ; seuls les noms des champs reçus le sont, visibles dans l'onglet Erreurs récentes de la clé API. Le request_id est toujours dans l'en-tête X-Request-ID.

429

Le quota de requêtes par fenêtre est dépassé pour cette clé API ou cet endpoint.

500

Erreur serveur inattendue. Toujours fournir le request_id au support.

Exemple de requête

curl -X POST 'https://api.capturia.io/v1/leads/capture' \
  -H 'Authorization: Bearer cap_live_<your-key>' \
  -H 'Content-Type: application/json' \
  -d '{
  "phone": "+15145551234",
  "sms_consent": true,
  "first_name": "Alex",
  "last_name": "Tremblay",
  "email": "[email protected]",
  "source_label": "funnel-fb-jan",
  "utm": {
    "source": "facebook",
    "medium": "cpc",
    "campaign": "spring-2026"
  },
  "tags": [
    "lead-chaud",
    "vu-webinaire"
  ],
  "pipeline": {
    "pipeline_slug": "ventes-b2b",
    "stage_slug": "nouveau-lead",
    "deal_value": 2500,
    "expected_close_date": "2026-06-30",
    "assigned_sales_rep_email": "[email protected]"
  },
  "custom_fields": {
    "type_propriete": "maison_isolee",
    "delai_vente": "0_3_mois"
  },
  "address": "123 rue Principale, Montréal",
  "postal_code": "H2X 1Y4"
}'