Envoyer des données par webhook
Cette page explique exactement quoi envoyer à DURUM.ai pour que l'app fonctionne à son plein potentiel. Que vous utilisiez GHL, Typeform, Calendly, Stripe, Zoho, Pipedrive ou un outil custom, les règles sont les mêmes.
Les 3 choses absolument essentielles
Sans ces 3 éléments, votre événement ne sera pas traité correctement.
1. Identifier votre compte (client_key)
Chaque webhook doit être associé à votre compte. Il y a deux façons de le faire :
Option A : Dans l'URL avec bid (recommandé) :
https://app.durum.ai/api/webhook/lead?bid=VOTRE_BUSINESS_IDC'est le format que l'app vous donne dans Réglages -> Intégrations. Le bid est un identifiant unique impossible à deviner, donc plus sûr qu'une clé lisible. Les 8 endpoints standard l'acceptent.
Option B : Dans l'URL avec client_key :
https://app.durum.ai/api/webhook/lead?client_key=VOTRE_CLEToujours supporté, aucune dépréciation.
Option C : Dans le payload JSON :
{ "client_key": "VOTRE_CLE", "email": "..." }Sans client_key
L'événement est mis en quarantaine. Il ne sera pas perdu, mais il devra être classé manuellement par l'agence. Vos KPIs ne seront pas à jour en temps réel.
2. Identifier le contact (email ou téléphone)
DURUM.ai doit pouvoir identifier qui est le prospect. Envoyez au minimum un de ces champs :
| Champ | Exemple | Priorité |
|---|---|---|
email | jean.tremblay@email.com | Idéal : sert aussi à la déduplication et à l'attribution |
phone | +15145551234 | Bon backup : format E.164 ou 10 chiffres |
name | Jean Tremblay | Utile mais insuffisant seul |
Sans email ni téléphone
Pour booking, no-show et sale (envoi JSON générique), l'événement est refusé (réponse rejected: missing_contact_info) : un rendez-vous ou une vente impossible à rattacher à un contact ne sert à rien et fausserait vos KPIs. Pour lead et application, l'événement sera enregistré, mais le système ne pourra pas :
- Détecter les doublons (risque de compter 2 fois le même lead)
- Relier le lead à ses futures actions (booking, vente)
- Enrichir les données avec l'historique du contact
3. Envoyer au bon endpoint
Chaque type d'événement a son propre endpoint. C'est ce qui détermine le type dans DURUM.ai.
| Quand | Endpoint | Ce qui est créé |
|---|---|---|
| Un prospect montre de l'intérêt | /api/webhook/lead | Lead |
| Un prospect remplit un formulaire détaillé | /api/webhook/application | Application |
| Un rendez-vous est réservé | /api/webhook/booking | Booking |
| Un prospect ne se présente pas | /api/webhook/no-show | No-show |
| Une vente est conclue | /api/webhook/sale | Vente |
| Un paiement est reçu | /api/webhook/payment | Paiement |
| Un remboursement est émis | /api/webhook/refund | Remboursement |
| Un visiteur ajoute au panier | /api/webhook/add-to-cart | Ajout au panier |
| Un événement à vous (hors des 8 standard) | /api/webhook/event | Événement custom (nommé) |
Événements custom (vos propres événements)
En plus des 8 événements standard, vous pouvez envoyer n'importe quel événement, sous le nom que vous voulez (« Démo complétée », « Deal déplacé en colonne X », « Webinaire regardé à 80% »...).
Le nom de l'événement se met dans l'URL, vous n'envoyez que les données du contact dans le corps :
POST https://app.durum.ai/api/webhook/event?client_key=VOTRE_CLE&event_name=demo_completee&source=custom
Content-Type: application/json
{
"email": "client@exemple.com",
"phone": "+15145550199",
"value": 250,
"occurred_at": "2026-07-07T14:00:00Z"
}Champs reconnus : email, phone, value, occurred_at, contact_id, calendar_name, plus les 13 champs d'attribution si vous voulez rattacher l'événement à une publicité. Tout autre champ est conservé dans le payload brut.
Où créer un événement custom dans l'app
Réglages -> Événements -> « Ajouter un événement custom » : nommez-le, obtenez son URL de webhook dédiée + le cadre JSON. Il apparaît ensuite dans vos Conversions (page Conversions) et vos mappings d'intégration (ex. « ce calendrier Calendly = cet événement »).
Un événement != trois événements
Si 3 intégrations envoient toutes des leads, c'est un seul événement Lead alimenté par 3 sources, pas 3 événements. Pour distinguer par source, utilisez un filtre au niveau de la conversion, jamais un événement séparé. Un événement custom = seulement un signal qui n'existe pas dans les 8 standard.
Les 13 champs d'attribution publicitaire
Ces champs permettent à DURUM.ai de relier chaque lead, booking et vente à la publicité exacte qui l'a généré. C'est ce qui alimente le tableau Marketing, le calcul du CPA, ROAS, ROI, et toute l'attribution dans l'app.
Sans ces champs, l'app perd sa valeur principale
Un lead sans UTMs arrive dans DURUM.ai mais il est impossible de savoir de quelle publicité il vient. Vos KPIs marketing (CPA, ROAS, ROI) seront faussés. Vous ne saurez pas quel ad performe et lequel gaspille votre budget.
Les 5 UTMs (noms des éléments publicitaires)
Ce sont les noms lisibles de vos campagnes, publicités et adsets. DURUM.ai les utilise pour matcher les events avec vos publicités Meta.
| # | Champ | Alias acceptés | Ce que ça fait dans l'app | Exemple | Priorité |
|---|---|---|---|---|---|
| 1 | utm_campaign | utmcampaign, UTM_Campaign, campaign_name | Lie l'event à la campagne. Utilisé dans le tableau Marketing, le filtre par campagne, et le calcul du spend par campagne. | bootcamp-mars-2026 | Non négociable |
| 2 | utm_content | utmcontent, UTM_Content, ad_name, utm_ad_name | Lie l'event à la publicité (ad). C'est LE champ qui connecte un lead à un ad dans Meta. Sans lui, pas d'attribution par ad. | ad-temoignage-tx1-ix2 | Non négociable |
| 3 | utm_source | utmsource, UTM_Source | Identifie la source du trafic. Permet de distinguer Meta vs Google vs organique dans les rapports. | meta, google, tiktok, organic | Important |
| 4 | utm_medium | utmmedium, UTM_Medium | Identifie le type de trafic. Distingue le paid du organique, de l'email, du social. | paid, cpc, email, social, organic | Important |
| 5 | utm_term | utmterm, UTM_Term, utm_adset_name, adset_name | Lie l'event à l'adset. Permet l'analyse de performance par audience/adset. | adset-lookalike-1pct, adset-retarget-30j | Recommandé |
Les 3 IDs Meta (identifiants numériques)
Ce sont les IDs numériques de Meta Ads. Ils permettent un match exact au lieu de matcher par nom (plus fiable si vous renommez une campagne).
| # | Champ | Alias acceptés | Ce que ça fait dans l'app | Exemple | Priorité |
|---|---|---|---|---|---|
| 6 | utm_campaign_id | campaign_id | Match exact avec la campagne Meta par ID au lieu du nom | 120210987654321 | Recommandé |
| 7 | utm_ad_id | ad_id | Match exact avec la publicité Meta par ID au lieu du nom | 120210123456789 | Recommandé |
| 8 | utm_adset_id | adset_id | Match exact avec l'adset Meta par ID au lieu du nom | 120210111222333 | Recommandé |
Noms vs IDs : pourquoi les deux?
Les noms (utm_campaign, utm_content, utm_term) sont lisibles et utiles dans les tableaux. Les IDs (utm_campaign_id, utm_ad_id, utm_adset_id) sont fiables même si vous renommez une campagne. Idéalement, envoyez les deux. Si vous ne pouvez envoyer qu'un seul, envoyez les noms.
Les 2 champs de tracking avancé
Pour l'attribution first-party et les outils tiers comme Hyros.
| # | Champ | Ce que ça fait dans l'app | Exemple | Priorité |
|---|---|---|---|---|
| 9 | fbc_id | Facebook Click ID : identifiant unique du clic publicitaire Meta. Permet l'attribution même si les cookies sont bloqués. Capturé par le paramètre fbclid dans l'URL. | fb.1.1612345678.abc123def456 | Optionnel (bonus) |
| 10 | h_ad_id | Hyros Ad ID : si vous utilisez Hyros pour le tracking, cet ID permet le lien direct. | 120210123456789 | Optionnel (si Hyros) |
Les 3 champs complémentaires
| # | Champ | Alias acceptés | Ce que ça fait dans l'app | Exemple | Priorité |
|---|---|---|---|---|---|
| 11 | product_key | product, funnel_key | Identifie le produit ou service. Alimente le funnel par produit, les revenus par produit, et les commissions. | bootcamp_ai | Important |
| 12 | assigned_user_name | assigned_to, Deal_Owner, rep_name | Identifie le rep de vente assigné. Alimente le tableau Ventes Reps et les commissions par rep. | Marie Dupont | Recommandé |
| 13 | amount | value, total, Amount, Grand_Total | Montant en dollars de la vente ou du paiement. Sans ce champ, le ROI, ROAS et les revenus sont à $0. | 2500 | Non négociable (ventes/paiements) |
Produits reconnus automatiquement
bootcamp_ai, scaler_vos_ventes, propulsion, club_prive, mastermind_ai, formation_croisiere, accelerateur, acquisition_entreprises. Tout autre nom est accepté et converti en slug (ex : Mon Produit devient mon_produit).
Comment configurer les UTMs dans Meta Ads
Quand vous créez une publicité Meta, configurez les paramètres URL au niveau de la publicité (Ad) :
URL Parameters (dans le champ "URL Parameters" de l'ad) :
utm_source=meta&utm_medium=paid&utm_campaign={campaign.name}&utm_content={ad.name}&utm_term={adset.name}&utm_campaign_id={campaign.id}&utm_ad_id={ad.id}&utm_adset_id={adset.id}Dans Meta Ads, les variables dynamiques entre accolades ({campaign.name}, {ad.name}, etc.) sont remplacées automatiquement par les vraies valeurs. Vos outils (GHL, Typeform, Calendly) capturent ensuite ces paramètres.
Où placer les UTMs selon votre outil
| Outil | Où les mettre | Détail |
|---|---|---|
| GHL | Custom fields du contact | utm_source, utm_campaign, utm_content, utm_term, utm_ad_id, utm_campaign_id, utm_adset_id dans le tableau customFields |
| Typeform | Hidden fields du formulaire | Injectés via l'URL du formulaire (#utm_source=meta&utm_campaign=...) |
| Calendly | Paramètres UTM de la page | Calendly capture automatiquement les UTMs de la page où le lien est affiché |
| Stripe | Metadata du checkout | client_key, product_key, utm_campaign, utm_content dans l'objet metadata |
| Zoho CRM | Champs du deal | UTM_Source, UTM_Campaign, UTM_Content, UTM_Term, utm_ad_id, utm_campaign_id |
| Zoho Books | Custom fields (cf_*) | cf_product_key, cf_source, cf_referent |
| Générique / Make / Zapier | Champs directs dans le JSON | Tous les champs au premier niveau du payload |
Exemple complet avec TOUS les champs d'attribution
{
"email": "jean.tremblay@email.com",
"phone": "+15145551234",
"name": "Jean Tremblay",
"product_key": "bootcamp_ai",
"amount": 2500,
"assigned_user_name": "Marie Dupont",
"utm_source": "meta",
"utm_medium": "paid",
"utm_campaign": "bootcamp-mars-2026",
"utm_content": "ad-temoignage-tx1-ix2",
"utm_term": "adset-lookalike-1pct",
"utm_campaign_id": "120210987654321",
"utm_ad_id": "120210123456789",
"utm_adset_id": "120210111222333",
"fbc_id": "fb.1.1612345678.abc123def456",
"h_ad_id": "120210123456789"
}Les réponses aux questions de vos formulaires
Chaque client pose des questions différentes dans ses formulaires (GHL, Typeform, Calendly). Toutes les réponses sont capturées et stockées, même si elles ne correspondent pas à des champs standard. Rien n'est perdu.
Ce qui est stocké
Toutes les réponses arrivent dans le champ raw_payload de l'événement (format JSONB). C'est un stockage complet du payload original : chaque question, chaque réponse, chaque champ custom.
En plus du stockage brut, certains champs sont extraits automatiquement vers des colonnes dédiées (email, téléphone, nom, UTMs). Les autres restent dans raw_payload et sont accessibles pour des analyses, des exports ou des filtres avancés.
Typeform : toutes les réponses du formulaire
Chaque réponse est capturée avec la question posée (le titre du champ) et la réponse donnée, quel que soit le type.
Exemples de questions custom que vos clients posent dans leurs formulaires Typeform :
| Question dans le formulaire | Type | Exemple de réponse | Stocké dans |
|---|---|---|---|
| Quel est votre chiffre d'affaires annuel? | number | 500000 | raw_payload |
| Quel est votre secteur d'activité? | choice | E-commerce | raw_payload |
| Quelles plateformes publicitaires utilisez-vous? | choices | ["Facebook Ads", "Google Ads"] | raw_payload |
| Quel est votre objectif principal? | text | Doubler mes ventes en 6 mois | raw_payload |
| Combien d'employés avez-vous? | number | 15 | raw_payload |
| Avez-vous un site web? | url | https://mon-entreprise.com | raw_payload |
| Date souhaitée pour commencer? | date | 2026-04-15 | raw_payload |
| Acceptez-vous d'être contacté? | boolean | true | raw_payload |
| Votre courriel | email | jean@email.com | raw_payload + contact_email |
| Votre téléphone | phone_number | +15145551234 | raw_payload + contact_phone |
| Votre nom complet | text (si le titre contient "nom") | Jean Tremblay | raw_payload + contact_name |
Structure dans le payload : chaque réponse est stockée comme ceci :
{
"field_ref": {
"label": "Quel est votre chiffre d'affaires annuel?",
"type": "number",
"value": 500000
}
}Vous pouvez ajouter autant de questions que vous voulez dans votre Typeform. Elles seront toutes là.
GHL : tous les custom fields du contact
GHL envoie les custom fields dans un tableau customFields. Tout le tableau est lu et stocké.
Les champs UTM et produit sont extraits vers des colonnes dédiées (voir section précédente). Tous les autres custom fields sont conservés dans raw_payload.
Exemples de custom fields que vos clients configurent dans GHL :
| Custom Field GHL | Description | Exemple |
|---|---|---|
ville | Ville du prospect | Montreal |
entreprise | Nom de l'entreprise | Tremblay Inc. |
chiffre_affaires | CA annuel | 500000 |
nb_employes | Taille de l'équipe | 15 |
budget_mensuel | Budget marketing | 5000 |
secteur_activite | Secteur | E-commerce |
objectif_principal | Objectif | Augmenter mes ventes de 50% |
comment_avez_vous_entendu | Source déclarée | Publicite Facebook |
qualification_score | Score attribué par le rep | 8 |
revenue_attendu | Revenue estimé de la vente | 2500 |
type_entreprise | B2B, B2C, D2C | B2C |
experience_pub | Expérience publicitaire | Debutant |
outil_crm_actuel | CRM utilisé | HubSpot |
delai_decision | Urgence | Ce mois-ci |
Comment les envoyer :
{
"contact": {
"email": "jean@email.com",
"customFields": [
{ "field_name": "ville", "value": "Montreal" },
{ "field_name": "entreprise", "value": "Tremblay Inc." },
{ "field_name": "chiffre_affaires", "value": "500000" },
{ "field_name": "nb_employes", "value": "15" },
{ "field_name": "objectif_principal", "value": "Augmenter mes ventes" },
{ "field_name": "budget_mensuel", "value": "5000" },
{ "field_name": "qualification_score", "value": "8" }
]
}
}Pas de limite
Vous pouvez ajouter autant de custom fields que nécessaire. Il n'y a pas de liste prédéterminée : tout ce que vous envoyez dans customFields est stocké et accessible.
Calendly : les questions de réservation
Calendly permet de poser des questions custom quand quelqu'un réserve un rendez-vous. Elles arrivent dans le tableau questions_and_answers.
Exemples de questions configurées dans Calendly :
| Question Calendly | Réponse type |
|---|---|
| Quel est le nom de votre entreprise? | Tremblay Inc. |
| Quel est votre chiffre d'affaires annuel? | 500 000$ |
| Quel est votre objectif principal pour cet appel? | Valider si le bootcamp est pour moi |
| Comment avez-vous entendu parler de nous? | Publicite Facebook |
| Nombre d'employés? | 15 |
| Avez-vous déjà fait de la publicité en ligne? | Oui, Facebook Ads depuis 6 mois |
| Quel est votre budget mensuel en publicité? | 3000-5000$ |
Structure dans le payload Calendly :
{
"questions_and_answers": [
{
"question": "Quel est le nom de votre entreprise?",
"answer": "Tremblay Inc."
},
{
"question": "Quel est votre chiffre d'affaires annuel?",
"answer": "500 000$"
},
{
"question": "Objectif principal pour cet appel?",
"answer": "Valider si le bootcamp est pour moi"
}
]
}Zoho Books : custom fields et line items
Zoho Books envoie les custom fields (préfixés cf_) et les line items (produits facturés).
| Custom Field Zoho | Description |
|---|---|
cf_product_key | Identifiant du produit |
cf_source | Source de la vente (DURUM, ORGANIC) |
cf_referent | Vendeur/référent |
cf_af_contact_id | ID GHL du contact (via ActiveFunnel) |
Tout autre cf_* | Stocké dans raw_payload |
Les line items (produits facturés) sont aussi capturés :
{
"line_items": [
{
"name": "Bootcamp IA - Session Mars 2026",
"rate": 2500,
"quantity": 1,
"item_total": 2500
},
{
"name": "Materiel supplementaire",
"rate": 200,
"quantity": 1,
"item_total": 200
}
]
}Le système identifie automatiquement le produit principal (item avec le plus gros montant, en excluant les frais bancaires comme Stripe Processing Fees ou Compte Stripe).
Générique / Make / Zapier : champs libres
Si vous construisez le JSON vous-même, ajoutez vos champs custom directement dans le payload. Ils seront tous stockés :
{
"email": "jean@email.com",
"client_key": "avego",
"utm_campaign": "bootcamp-mars-2026",
"entreprise": "Tremblay Inc.",
"chiffre_affaires": 500000,
"nb_employes": 15,
"secteur": "E-commerce",
"objectif": "Augmenter mes ventes de 50%",
"budget_pub": 5000,
"source_declaree": "Publicite Facebook",
"questions_custom": {
"experience_pub": "Facebook Ads depuis 6 mois",
"outil_actuel": "HubSpot",
"delai": "Ce mois-ci"
}
}Tout est conservé dans raw_payload. Les champs standard (email, utm_campaign, etc.) sont en plus extraits vers des colonnes dédiées.
Par type d'événement : quoi envoyer
Lead ou Application
Un prospect montre de l'intérêt ou remplit un formulaire.
POST /api/webhook/lead?client_key=VOTRE_CLE
Content-Type: application/json{
"email": "jean.tremblay@email.com",
"phone": "+15145551234",
"name": "Jean Tremblay",
"product_key": "bootcamp_ai",
"utm_source": "meta",
"utm_campaign": "bootcamp-mars-2026",
"utm_content": "ad-temoignage-tx1"
}| Champ | Statut | Pourquoi |
|---|---|---|
email | Essentiel | Identifie le contact et permet la déduplication |
utm_campaign | Essentiel | Attribution à la campagne |
utm_content | Essentiel | Attribution à la publicité |
phone | Recommandé | Backup d'identification + enrichissement |
name | Recommandé | Affichage dans les tableaux |
product_key | Recommandé | Classification par produit/funnel |
utm_source | Recommandé | Source du trafic |
utm_ad_id | Optionnel | Lien direct avec Meta |
Booking (rendez-vous)
Un prospect réserve un rendez-vous.
POST /api/webhook/booking?client_key=VOTRE_CLE{
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"calendar_name": "Appel Decouverte",
"booking_date": "2026-04-05",
"booking_time": "14:00",
"assigned_user_name": "Marie Dupont",
"utm_campaign": "bootcamp-mars-2026",
"utm_content": "ad-temoignage-tx1"
}| Champ | Statut | Pourquoi |
|---|---|---|
email | Essentiel | Relie le booking au lead original |
booking_date | Essentiel | Distingue "date de création" vs "date du RDV" |
booking_time | Recommandé | Analyse des créneaux no-show |
calendar_name | Recommandé | Type de rendez-vous dans les tableaux |
assigned_user_name | Recommandé | Associe au rep pour le suivi de performance |
| UTMs | Important | Si pas déjà sur le lead, c'est la dernière chance de les capturer |
Booking date vs event date
DURUM.ai distingue la date de création du booking (quand le prospect a réservé) de la date du rendez-vous (quand le meeting aura lieu). C'est critique pour calculer correctement les taux de no-show et le pipeline.
No-show
Un prospect ne se présente pas à son rendez-vous.
POST /api/webhook/no-show?client_key=VOTRE_CLE{
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"calendar_name": "Appel Decouverte",
"booking_date": "2026-04-05",
"booking_time": "14:00"
}Vente
Un deal est conclu (gagné ou perdu).
POST /api/webhook/sale?client_key=VOTRE_CLE{
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"amount": 2500,
"product_key": "bootcamp_ai",
"status": "won",
"assigned_user_name": "Marie Dupont",
"utm_campaign": "bootcamp-mars-2026",
"utm_content": "ad-temoignage-tx1"
}| Champ | Statut | Pourquoi |
|---|---|---|
email | Essentiel | Relie la vente au lead et au booking |
amount | Essentiel | Calcul du revenu, ROI, ticket moyen |
status | Important | won = vente, lost = perdue. Par défaut : vente. |
product_key | Recommandé | Revenu par produit, commissions |
assigned_user_name | Recommandé | Performance par rep |
| UTMs | Important | Attribution de la vente à la publicité |
Sans montant
La vente apparaît dans le funnel mais avec $0. Le ROI, le ROAS et les revenus seront faussés.
Paiement
Un paiement est reçu (facture payée).
POST /api/webhook/payment?client_key=VOTRE_CLE{
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"amount": 2500,
"product_key": "bootcamp_ai",
"invoice_number": "INV-001234",
"payment_status": "paid"
}| Champ | Statut | Pourquoi |
|---|---|---|
email | Essentiel | Relie le paiement au client |
amount | Essentiel | Montant encaissé |
invoice_number | Recommandé | Déduplication (évite de compter 2 fois si le webhook est renvoyé) |
payment_status | Recommandé | paid = encaissé, overdue = en retard |
product_key | Recommandé | Revenu par produit |
Remboursement
POST /api/webhook/refund?client_key=VOTRE_CLE{
"email": "jean.tremblay@email.com",
"amount": 2500
}Ajout au panier
Un panier abandonné, c'est un ajout au panier qui n'est pas suivi d'une vente. Envoyez donc l'ajout au panier ici, et la vente sur /api/webhook/sale comme d'habitude : DURUM.ai fait la différence, et peut pousser la conversion AddToCart aux régies (Meta, TikTok, Google, Snapchat, LinkedIn) depuis Réglages > Conversions.
POST /api/webhook/add-to-cart?client_key=VOTRE_CLE{
"email": "jean.tremblay@email.com",
"value": 97,
"currency": "CAD",
"product_key": "formation-impots",
"product_name": "Formation Impôts",
"utm_source": "facebook",
"utm_campaign": "Impots-Sept",
"utm_content": "Ad-Temoignage-12"
}| Champ | Statut | Pourquoi |
|---|---|---|
email ou phone | Requis (sauf pixel, voir ci-dessous) | Identifier la personne et relier au lead, à la vente |
value + currency | Recommandé | Valeur du panier, envoyée telle quelle à la régie |
product_key (ou content_ids, sku) | Recommandé | Produit ajouté (content_ids côté Meta) |
product_name (ou content_name) | Optionnel | Nom lisible du produit |
utm_*, fbc_id, gclid | Recommandé | Attribution à la publicité (sinon, DURUM.ai reprend celle du lead) |
Depuis une page avec le pixel DURUM.ai installé, aucun webhook à brancher : appelez durum.track('AddToCart', { value: 97, currency: 'CAD', content_ids: ['formation-impots'] }) au clic sur le bouton, ou posez data-durum-event="AddToCart" (avec data-durum-value, data-durum-currency, data-durum-content-id) sur le bouton. Le pixel envoie l'événement au serveur avec le visiteur, ses UTM et ses identifiants de clic ; l'email est retrouvé si ce visiteur est déjà connu (formulaire rempli plus tôt). La conversion part aux régies depuis le serveur, une seule fois, jamais depuis le navigateur.
Récapitulatif : hiérarchie d'importance
| Niveau | Champs | Impact si absent |
|---|---|---|
| Non négociable | client_key (URL ou payload) | Événement en quarantaine, pas dans vos KPIs |
| Non négociable | email ou phone | Pas de déduplication, pas de lien entre les étapes du funnel |
| Non négociable | utm_campaign + utm_content | Pas d'attribution. Impossible de savoir quel ad a généré le lead/vente. CPA, ROAS et ROI non calculables. |
| Non négociable | amount (ventes et paiements) | Revenus à $0, ROI et ROAS faussés |
| Important | utm_source + utm_medium | Impossible de distinguer Meta vs Google vs organique |
| Important | product_key | Pas de funnel par produit, pas de commissions par produit |
| Recommandé | utm_term | Pas d'analyse par adset/audience |
| Recommandé | utm_campaign_id + utm_ad_id + utm_adset_id | Match par nom au lieu d'ID exact (fragile si vous renommez) |
| Recommandé | name, assigned_user_name | Moins de détails dans les tableaux, pas de perf par rep |
| Bonus | fbc_id, h_ad_id | Attribution avancée first-party et Hyros |
Ce que DURUM.ai fait automatiquement
Vous n'avez pas besoin d'envoyer toutes les données dans chaque webhook. Le système fait un travail important en arrière-plan.
Enrichissement UTM automatique
Si un webhook arrive sans UTMs (par exemple un paiement Zoho Books), DURUM.ai cherche automatiquement les UTMs dans cet ordre :
- Custom fields GHL : si le contact existe dans GHL avec des champs UTM configurés, ils sont récupérés
- Tracker navigateur : si le prospect a visité votre site avec le tracker DURUM.ai installé, ses UTMs ont été capturés automatiquement (premier clic)
- Events précédents : si le même email a déjà un lead ou un booking avec des UTMs, ils sont hérités
Concrètement : si un prospect remplit un formulaire avec les bons UTMs, puis réserve un booking, puis paie via Stripe, même si Stripe n'envoie aucun UTM, la vente sera attribuée à la bonne publicité grâce au lead original.
TIP
C'est pourquoi les UTMs sur le premier point de contact (lead ou application) sont si importants. Même si les étapes suivantes n'en ont pas, l'attribution fonctionnera.
Déduplication intelligente
DURUM.ai empêche automatiquement les doublons. Vous n'avez pas à vous en soucier si un webhook est envoyé 2 fois.
| Source | Méthode de dedup | Ce qui est vérifié |
|---|---|---|
| Typeform | Token de soumission | Chaque soumission a un token unique |
| Calendly | URI de l'invité | Chaque invité a une URI unique |
| Zoho Books | Numéro de facture | Même invoice_number = même événement |
| GHL / Générique | Email + type + fenêtre temps | Même email + même type dans les 24h (leads) ou 2 min (autres) |
Rep assigné automatiquement
Si vous n'envoyez pas le nom du rep (assigned_user_name), le système le cherche automatiquement :
- Dans le contact GHL (champ
assigned_to) - Dans les events précédents du même contact
- Via le ID utilisateur GHL mappé à un rep dans DURUM.ai
Client identifié automatiquement
Si le client_key n'est pas dans l'URL, le système tente de l'identifier via :
- L'email du contact (s'il existe déjà dans un compte)
- Le numéro de téléphone
- L'ID de contact GHL
- Le numéro de facture
- L'ID client Zoho ou Stripe Connect
Alias acceptés par champ
DURUM.ai accepte plusieurs noms pour le même champ. Utilisez celui qui correspond à votre outil.
| Ce que DURUM.ai stocke | Noms acceptés |
|---|---|
contact_email | email, contact_email, customer_email |
contact_phone | phone, contact_phone, customer_phone |
contact_name | name, contact_name, customer_name |
client_key | client_key, clientKey |
product_key | product_key, product, funnel_key |
utm_source | utm_source, utmsource, UTM_Source |
utm_campaign | utm_campaign, utmcampaign, UTM_Campaign |
utm_content | utm_content, utmcontent, UTM_Content |
utm_term | utm_term, utmterm, UTM_Term, utm_adset_name |
meta_ad_id | utm_ad_id, ad_id |
meta_campaign_id | utm_campaign_id, campaign_id |
meta_adset_id | utm_adset_id, adset_id |
value (montant) | amount, value, total, Amount, Grand_Total |
assigned_user_name | assigned_user_name, assigned_to, Deal_Owner, rep_name |
Spécificités par plateforme
Chaque outil envoie les données dans un format différent. Voici où placer les champs importants selon votre outil.
GHL (GoHighLevel)
Les UTMs et le produit vont dans les custom fields du contact :
{
"contact": {
"email": "jean@email.com",
"customFields": [
{ "field_name": "utm_campaign", "value": "bootcamp-mars-2026" },
{ "field_name": "utm_content", "value": "ad-temoignage-tx1" },
{ "field_name": "product_key", "value": "bootcamp_ai" }
]
}
}L'URL doit inclure ?client_key=VOTRE_CLE&source=ghl.
Vous pouvez ajouter n'importe quel custom field supplémentaire (ville, entreprise, chiffre_affaires, etc.). Ils seront tous conservés dans les données brutes.
Voir le payload GHL complet (lead)
{
"contact": {
"id": "abc123",
"firstName": "Jean",
"lastName": "Tremblay",
"email": "jean.tremblay@email.com",
"phone": "+15145551234",
"dateAdded": "2026-03-30T14:30:00.000Z",
"tags": ["lead", "bootcamp-ai"],
"assignedTo": "rep-user-id",
"customFields": [
{ "field_name": "utm_source", "value": "meta" },
{ "field_name": "utm_campaign", "value": "bootcamp-mars-2026" },
{ "field_name": "utm_content", "value": "ad-temoignage-tx1" },
{ "field_name": "utm_term", "value": "adset-lookalike-1pct" },
{ "field_name": "utm_ad_id", "value": "120210123456789" },
{ "field_name": "utm_campaign_id", "value": "120210987654321" },
{ "field_name": "product_key", "value": "bootcamp_ai" },
{ "field_name": "ville", "value": "Montreal" },
{ "field_name": "entreprise", "value": "Tremblay Inc." }
]
},
"locationId": "ghl-location-abc123",
"form": {
"id": "form-xyz-789",
"name": "Formulaire Bootcamp IA"
}
}Voir le payload GHL complet (booking)
{
"id": "appt-abc123",
"contactId": "contact-xyz789",
"contact": {
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"customFields": [
{ "field_name": "utm_campaign", "value": "bootcamp-mars-2026" }
]
},
"calendarName": "Appel Decouverte",
"startTime": "2026-04-05T14:00:00.000Z",
"appointmentStatus": "scheduled",
"assignedUserId": "user-rep-123",
"locationId": "ghl-location-abc123"
}Voir le payload GHL complet (vente)
{
"opportunity": {
"id": "opp-abc123",
"status": "won",
"monetaryValue": 2500
},
"contact": {
"id": "contact-xyz789",
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"customFields": [
{ "field_name": "product_key", "value": "bootcamp_ai" }
]
},
"locationId": "ghl-location-abc123"
}Typeform
Les UTMs et le client_key vont dans les hidden fields du formulaire :
https://form.typeform.com/to/XXXXX#client_key=VOTRE_CLE&utm_source=meta&utm_campaign=bootcamp-mars-2026&utm_content=ad-temoignage-tx1&product_key=bootcamp_aiLes réponses du formulaire (email, téléphone, nom, questions custom) sont extraites automatiquement depuis le tableau answers.
Voir le payload Typeform complet
{
"form_response": {
"form_id": "sJ5lztlq",
"token": "unique-submission-token",
"definition": { "title": "Formulaire Bootcamp IA" },
"hidden": {
"client_key": "VOTRE_CLE",
"utm_source": "meta",
"utm_campaign": "bootcamp-mars-2026",
"utm_content": "ad-temoignage-tx1",
"utm_ad_id": "120210123456789",
"product_key": "bootcamp_ai"
},
"answers": [
{ "type": "email", "email": "jean@email.com", "field": { "title": "Votre courriel" } },
{ "type": "phone_number", "phone_number": "+15145551234", "field": { "title": "Telephone" } },
{ "type": "text", "text": "Jean Tremblay", "field": { "title": "Votre nom" } },
{ "type": "choice", "choice": { "label": "E-commerce" }, "field": { "title": "Secteur" } },
{ "type": "number", "number": 500000, "field": { "title": "Chiffre d'affaires" } }
]
}
}Calendly
Les UTMs sont capturés automatiquement dans l'objet tracking si l'URL de la page de réservation contient les paramètres UTM. Les questions personnalisées sont dans questions_and_answers.
L'URL doit inclure ?client_key=VOTRE_CLE.
Voir le payload Calendly complet
{
"event": "invitee.created",
"payload": {
"event_type": { "name": "Appel Decouverte" },
"invitee": {
"email": "jean.tremblay@email.com",
"name": "Jean Tremblay",
"text_reminder_number": "+15145551234",
"uri": "https://api.calendly.com/scheduled_events/xxx/invitees/yyy"
},
"scheduled_event": {
"start_time": "2026-04-05T14:00:00.000Z",
"event_memberships": [{ "user_name": "Marie Dupont" }]
},
"tracking": {
"utm_source": "meta",
"utm_campaign": "bootcamp-mars-2026",
"utm_content": "ad-temoignage-tx1"
},
"questions_and_answers": [
{ "question": "Votre entreprise?", "answer": "Tremblay Inc." },
{ "question": "Chiffre d'affaires?", "answer": "500 000$" }
]
}
}Stripe
Le client_key et le product_key vont dans les metadata du checkout session ou de la facture. Les montants sont en cents (divisés automatiquement par 100).
Pas besoin de client_key dans l'URL : Stripe est détecté par sa signature.
Voir le payload Stripe complet
{
"type": "checkout.session.completed",
"data": {
"object": {
"customer_email": "jean.tremblay@email.com",
"amount_total": 250000,
"payment_status": "paid",
"customer_details": {
"name": "Jean Tremblay",
"email": "jean.tremblay@email.com"
},
"metadata": {
"client_key": "VOTRE_CLE",
"product_key": "bootcamp_ai",
"utm_campaign": "bootcamp-mars-2026"
}
}
}
}Zoho CRM
Les UTMs sont dans des champs du deal (UTM_Source, UTM_Campaign, UTM_Content). Le client_key doit être dans l'URL.
Voir le payload Zoho CRM complet
{
"data": [{
"Deal_Name": "Jean Tremblay - Bootcamp AI",
"Stage": "Closed Won",
"Amount": 2500,
"Email": "jean.tremblay@email.com",
"Contact_Name": "Jean Tremblay",
"Owner": { "name": "Marie Dupont" },
"Product": "bootcamp_ai",
"UTM_Source": "meta",
"UTM_Campaign": "bootcamp-mars-2026",
"UTM_Content": "ad-temoignage-tx1"
}]
}Zoho Books
Le client_key doit être dans l'URL (Zoho Books ne l'envoie pas dans le payload). Les custom fields Zoho sont préfixés par cf_.
Voir le payload Zoho Books complet
{
"payment": {
"amount": 2500,
"payment_status": "paid",
"customer_name": "Jean Tremblay",
"customer_email": "jean.tremblay@email.com",
"invoices": [{ "invoice_number": "INV-001234", "amount_applied": 2500 }],
"custom_fields": [
{ "api_name": "cf_client_key", "value": "VOTRE_CLE" },
{ "api_name": "cf_product_key", "value": "bootcamp_ai" },
{ "api_name": "cf_source", "value": "DURUM" },
{ "api_name": "cf_referent", "value": "Marie Dupont" }
]
}
}Pipedrive
L'URL doit inclure ?client_key=VOTRE_CLE&source=pipedrive. Les emails et téléphones sont des tableaux avec primary: true.
Voir le payload Pipedrive complet (deal gagné)
{
"meta": { "action": "updated", "object": "deal", "company_id": 12345 },
"current": {
"title": "Jean Tremblay - Bootcamp IA",
"status": "won",
"value": 2500,
"person_id": {
"name": "Jean Tremblay",
"email": [{ "value": "jean@email.com", "primary": true }],
"phone": [{ "value": "+15145551234", "primary": true }]
},
"user_id": { "name": "Marie Dupont" },
"won_time": "2026-03-30T15:00:00Z"
},
"previous": { "status": "open" }
}Tester votre webhook
Copiez-collez cette commande dans votre terminal pour envoyer un lead de test :
curl -X POST "https://app.durum.ai/api/webhook/lead?client_key=VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"email": "test-webhook@votredomaine.com",
"name": "Test Webhook",
"utm_source": "test",
"utm_campaign": "test-webhook",
"utm_content": "test-ad"
}'Puis vérifiez dans DURUM.ai > Logs Data que l'événement apparaît.
Dépannage rapide
| Symptôme | Cause | Solution |
|---|---|---|
| Événement absent de l'app | client_key manquant ou invalide | Ajoutez ?client_key=VOTRE_CLE à l'URL |
Statut quarantined | Formulaire ou client non reconnu | Normal pour un nouveau formulaire. L'agence le catégorise. |
Réponse deduped: true | Événement déjà reçu | Normal. Le doublon a été détecté. |
| UTMs vides dans le dashboard | Pas d'UTMs dans le webhook | Configurez les hidden fields (Typeform), custom fields (GHL) ou tracking (Calendly) |
| Montant à $0 sur une vente | Champ amount absent | Ajoutez le montant dans le payload |
| Réponse 401 | Authentification échouée | Pour GHL : ajoutez &source=ghl. Pour Stripe : vérifiez la signature. |
| Réponse 429 | Trop de requêtes | Maximum 60 par minute par IP |
| Réponse 202 | Serveur temporairement occupé | L'événement est en file d'attente et sera traité sous quelques minutes |