# Créer un formulaire de demande de consultation pour une entreprise de services

> Un formulaire de demande de consultation pour comptables, avocats, coachs et cabinets, qui demande comment et quand appeler, et garde les détails sensibles pour l'appel.

Publié le 2026-10-25 · https://ikiform.com/fr/guides/lead-capture/consultation-request

Demandez le nom, l'e-mail, le moyen de joindre la personne et à peu près quand, plus un sujet général, et gardez tous les détails de sa situation pour l'appel. Enregistrez son fuseau horaire depuis le navigateur pour qu'une matinée demandée corresponde à sa matinée. Appliquez dans votre propre code les règles que le schéma du formulaire ne peut pas exprimer, comme un numéro de téléphone obligatoire seulement si la personne a choisi un appel téléphonique.

## Ce qui change avec une demande de consultation

Une demande de consultation vient d'une personne qui a un problème : un courrier des impôts, un litige de bail, une entreprise à créer. Elle veut l'expliquer, et le formulaire l'y invite. C'est là le piège. Tout ce qu'elle tape est stocké avec la soumission, affiché dans les e-mails de notification et envoyé par webhook partout où vous les dirigez. Les numéros de compte et les détails d'un dossier n'ont leur place nulle part là-dedans.

La mission du formulaire est donc plus étroite qu'il n'y paraît : qui est la personne, comment et quand la joindre, et à peu près de quoi il s'agit. Tout le reste attend l'appel, où vous contrôlez ce qui est noté et où c'est conservé.

Schéma : Ce que demande un formulaire de consultation, et ce qui attend l'appel

1. Le formulaire demande qui est la personne : nom et e-mail.
2. Il demande comment la joindre : e-mail, téléphone ou visio.
3. Il demande quand : un jour préféré, un créneau et son fuseau horaire.
4. Il demande de quoi il s'agit, sous la forme d'un sujet général.
5. L'appel couvre sa situation, dans ses propres mots.
6. Les documents passent par un canal que vous choisissez, pas par le formulaire.
7. Les numéros de compte, identifiants et dossiers ne sont jamais tapés dans le formulaire.
8. L'appel se termine par votre réponse sur l'adéquation et les honoraires.

La ligne du haut suffit pour réserver l'appel et le préparer. La ligne du bas, c'est ce à quoi sert l'appel.

## Les champs

Formulaire : Free consultation

- **Nom** (`name`, text, obligatoire)
- **E-mail** (`email`, email, obligatoire)
- **Comment souhaitez-vous être contacté ?** (`contact_method`, radio, obligatoire)
- **Téléphone** (`phone`, phone)
- **De quoi s'agit-il ?** (`topic`, select, obligatoire)
- **Jour préféré** (`preferred_date`, date)
- **Meilleur moment** (`time_window`, radio)
- **Fuseau horaire** (`timezone`, text)
- **Quelque chose que nous devrions savoir avant ?** (`note`, textarea)

Choisissez **Appel téléphonique** et laissez le téléphone vide : le schéma l'accepte, car un champ ne peut pas être obligatoire seulement dans certains cas. Le code ci-dessous comble ce manque.

| Champ | Pourquoi cette forme |
| --- | --- |
| `contact_method` | Un `radio`, pour que le premier contact se fasse comme la personne l'a demandé |
| `phone` | Facultatif dans le schéma, exigé par votre code quand la personne choisit un appel téléphonique |
| `preferred_date` | Un champ `date` stocke `YYYY-MM-DD`. Le `min` de son schéma est fixe, donc la page définit demain comme `min` dans le navigateur |
| `timezone` | Rempli depuis le navigateur avec `Intl.DateTimeFormat().resolvedOptions().timeZone`, comme `Europe/Berlin` |
| `note` | Limité à 500 caractères. Les e-mails de notification affichent jusqu'à 500 caractères par valeur, la note tient donc en entier |

## Des règles conditionnelles dans votre propre code

Ikiform valide chaque champ séparément. Les règles qui dépendent d'un autre champ, comme le téléphone seulement pour les appels téléphoniques, s'exécutent dans votre code. Les versions serveur peuvent les imposer réellement ; les versions navigateur guident le visiteur et n'envoient rien d'inutile.

**HTML**

Le script met `required` sur l'input du téléphone quand **Appel téléphonique** est choisi, définit demain comme premier jour possible dans le calendrier du visiteur et remplit le fuseau horaire masqué.

`consultation.html`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="POST" id="consult">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required />
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />

  <fieldset>
    <legend>How should we reach you?</legend>
    <label><input type="radio" name="contact_method" value="email" checked /> Email</label>
    <label><input type="radio" name="contact_method" value="phone" /> Phone call</label>
    <label><input type="radio" name="contact_method" value="video" /> Video call</label>
  </fieldset>
  <label for="phone">Phone</label>
  <input id="phone" name="phone" type="tel" autocomplete="tel" />

  <label for="topic">What is it about?</label>
  <select id="topic" name="topic" required>
    <option value="new_business">Starting a business</option>
    <option value="tax">Tax and bookkeeping</option>
    <option value="other">Something else</option>
  </select>

  <label for="preferred_date">Preferred day</label>
  <input id="preferred_date" name="preferred_date" type="date" />
  <fieldset>
    <legend>Best time</legend>
    <label><input type="radio" name="time_window" value="morning" /> Morning</label>
    <label><input type="radio" name="time_window" value="afternoon" /> Afternoon</label>
    <label><input type="radio" name="time_window" value="evening" /> Evening</label>
  </fieldset>
  <input type="hidden" name="timezone" />

  <label for="note">Anything we should know first?</label>
  <textarea id="note" name="note" rows="3" maxlength="500"></textarea>
  <p>A sentence or two. Please don't include account numbers or personal records.</p>

  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Request a consultation</button>
</form>

<script>
  const form = document.getElementById("consult");
  const phone = form.elements.phone;
  const tomorrow = new Date(Date.now() + 86400000);
  const local = new Date(tomorrow.getTime() - tomorrow.getTimezoneOffset() * 60000);

  form.elements.timezone.value = Intl.DateTimeFormat().resolvedOptions().timeZone;
  form.elements.preferred_date.min = local.toISOString().slice(0, 10);
  form.addEventListener("change", () => {
    phone.required = form.elements.contact_method.value === "phone";
  });
</script>
```

**Next.js**

La server action refuse un appel téléphonique sans numéro avant d'envoyer, efface un numéro tapé puis abandonné au profit d'un autre choix, et redirige vers une page de remerciement. Affichez le formulaire avec `useActionState` et montrez `state.error`.

`app/consultation/actions.ts`:

```ts
"use server";

import { headers } from "next/headers";
import { redirect } from "next/navigation";

const KEYS = ["name", "email", "contact_method", "phone", "topic", "preferred_date", "time_window", "timezone", "note", "_gotcha"];

export async function requestConsultation(_previous: Record<string, string>, formData: FormData) {
  const values = Object.fromEntries(KEYS.map((key) => [key, String(formData.get(key) ?? "").trim()]));

  if (values.contact_method === "phone" && !values.phone) {
    return { ...values, error: "Add a phone number, or choose email or video." };
  }
  if (values.contact_method !== "phone") values.phone = "";
  const origin = (await headers()).get("origin");

  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
      ...(origin ? { Origin: origin } : {}),
    },
    body: JSON.stringify(values),
  });
  const result = await response.json().catch(() => null);
  if (!result?.ok) {
    const fields: Record<string, string[]> = result?.error?.fields ?? {};
    const [field, messages] = Object.entries(fields)[0] ?? [];
    return { ...values, error: field ? `${field}: ${messages?.[0]}` : (result?.error?.message ?? "Couldn't send. Try again.") };
  }
  redirect("/consultation/thanks");
}
```

**Vue**

L'input du téléphone n'existe que si un appel téléphonique est choisi, et les valeurs vides sont retirées avant l'envoi : les champs facultatifs arrivent donc absents plutôt que vides.

`src/components/ConsultationForm.vue`:

```vue
<script setup lang="ts">
import { computed, reactive, ref } from "vue";

const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";

const request = reactive({
  name: "",
  email: "",
  contact_method: "email",
  phone: "",
  topic: "new_business",
  preferred_date: "",
  time_window: "",
  note: "",
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
});
const status = ref<"idle" | "sending" | "sent">("idle");
const error = ref("");
const wantsCall = computed(() => request.contact_method === "phone");

async function send() {
  status.value = "sending";
  error.value = "";
  const body = Object.fromEntries(Object.entries(request).filter(([, value]) => value !== ""));
  if (!wantsCall.value) delete body.phone;
  try {
    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: { Accept: "application/json", "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    const result = await response.json();
    if (result.ok) {
      status.value = "sent";
      return;
    }
    const first = Object.entries(result.error.fields ?? {})[0] as [string, string[]] | undefined;
    error.value = first ? `${first[0]}: ${first[1][0]}` : result.error.message;
  } catch {
    error.value = "Couldn't reach us. Try again, or call the office.";
  }
  status.value = "idle";
}
</script>

<template>
  <p v-if="status === 'sent'" role="status">Thanks, {{ request.name }}. We'll contact you by {{ request.contact_method }} within one working day.</p>
  <form v-else @submit.prevent="send">
    <label for="name">Name</label>
    <input id="name" v-model="request.name" autocomplete="name" required />
    <label for="email">Email</label>
    <input id="email" v-model="request.email" type="email" autocomplete="email" required />
    <fieldset>
      <legend>How should we reach you?</legend>
      <label><input v-model="request.contact_method" type="radio" value="email" /> Email</label>
      <label><input v-model="request.contact_method" type="radio" value="phone" /> Phone call</label>
      <label><input v-model="request.contact_method" type="radio" value="video" /> Video call</label>
    </fieldset>
    <template v-if="wantsCall">
      <label for="phone">Phone</label>
      <input id="phone" v-model="request.phone" type="tel" autocomplete="tel" required />
    </template>
    <label for="topic">What is it about?</label>
    <select id="topic" v-model="request.topic">
      <option value="new_business">Starting a business</option>
      <option value="tax">Tax and bookkeeping</option>
      <option value="other">Something else</option>
    </select>
    <label for="preferred_date">Preferred day</label>
    <input id="preferred_date" v-model="request.preferred_date" type="date" />
    <label for="note">Anything we should know first?</label>
    <textarea id="note" v-model="request.note" rows="3" maxlength="500" />
    <p v-if="error" role="alert">{{ error }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Sending" : "Request a consultation" }}</button>
  </form>
</template>
```

Les versions côté serveur envoient avec une clé secrète : Ikiform ignore donc **Allowed domains** et sa vérification humaine. Chaque envoi vient alors de l'IP de votre serveur, et la limite Standard de 5 par minute par visiteur compte tous vos visiteurs ensemble. Choisissez la [limite de débit](https://ikiform.com/fr/docs/rate-limits.md) Relaxed ou Custom pour un formulaire que vous envoyez via un serveur, et gardez `IKIFORM_SECRET_KEY` dans des variables d'environnement réservées au serveur. Les versions serveur transmettent l'en-tête `Origin` du visiteur, car un fetch côté serveur n'en envoie aucun, et c'est grâce à lui qu'Ikiform détecte les tests gratuits depuis `localhost`.

## Garder les détails sensibles hors du formulaire

- Dites-le dans le texte d'aide, comme le fait le formulaire ci-dessus. Les gens suivent une consigne claire plus souvent qu'on ne le croit.
- N'ajoutez pas de champs pour des numéros d'identité, des numéros de compte ou des informations médicales. Si votre profession encadre les données clients, vérifiez ces règles avant de collecter autre chose que des coordonnées, et recueillez le reste par un canal qui les respecte.
- Les noms de champ comptent aussi. Ikiform retient l'e-mail de notification pour vérification quand un nom de champ ressemble à un identifiant sensible, y compris les noms contenant `password`, `ssn`, `cvv` ou `seed`. Un champ nommé `seed_round` dans le formulaire d'un cabinet de conseil pour startups déclenche ce mécanisme. Nommez-le `funding_stage`.
- Ne dirigez les webhooks que là où toute l'équipe a le droit de lire les noms des clients, comme un canal privé, pas un canal ouvert à toute l'entreprise.

## La première réponse

L'e-mail de notification arrive avec **Reply** pointant vers le visiteur : la première réponse peut donc partir directement de votre boîte. Donnez-lui une seule mission : confirmer un horaire. Deux créneaux concrets dans la plage indiquée sont plus faciles à accepter qu'une question sur ce qui l'arrange.

- Convertissez sa plage horaire dans votre heure avant d'écrire. Un `morning` à `Australia/Sydney` correspond à la soirée de la veille à Londres.
- Proposez deux créneaux dans son fuseau horaire, et nommez ce fuseau dans l'e-mail, pour que personne n'arrive une heure en avance.
- Dites comment vous appellerez : le numéro donné, ou un lien de visio que vous envoyez avec l'invitation.
- Si le sujet sort de votre domaine, dites-le dans la première réponse et suggérez quelqu'un qui s'en occupe. Cette réponse vaut quand même la peine d'être envoyée.

## Vérifiez que tout fonctionne

1. Choisissez **Appel téléphonique**, laissez le téléphone vide et envoyez. Le navigateur ou votre serveur l'arrête avec un message.
2. Choisissez **E-mail**, envoyez et ouvrez la soumission. Elle n'a pas de valeur `phone` et contient votre fuseau horaire, comme `America/Chicago`.
3. Essayez de choisir aujourd'hui comme jour préféré. Le sélecteur de date ne le propose pas.
4. Vérifiez l'e-mail de notification. La note s'affiche en entier, et **Reply** écrit au visiteur.
5. Listez vos id de champ et vérifiez qu'aucun ne contient `password`, `ssn`, `cvv` ou `seed`.

## Pièges

- **Une date sans fuseau horaire.** `preferred_date` est un jour du calendrier, pas un instant. Lisez-le avec `timezone` et `time_window` avant d'appeler.
- **Des boutons radio sans choix.** Un groupe de boutons radio sans sélection n'envoie rien. Ça convient pour `time_window`, qui est facultatif, et c'est pourquoi `contact_method` commence avec **E-mail** coché.
- **Pas de confirmation de rendez-vous.** Ikiform n'envoie pas d'e-mail à la personne qui remplit le formulaire. Votre première réponse confirme l'horaire. Si vous voulez que les gens choisissent eux-mêmes un créneau, passez la main à un outil de réservation comme dans le [formulaire de demande de démo](https://ikiform.com/fr/guides/lead-capture/demo-request.md).
- **Le format du téléphone.** Le champ téléphone accepte de 6 à 15 chiffres tels que tapés. Activez `e164` si vous appelez depuis un système qui exige `+14155550123` ; les numéros sans indicatif pays sont alors refusés.

## Questions

### Que doit demander un formulaire de consultation gratuite ?

Le nom, l'e-mail, le moyen de contact souhaité, un jour et un moment préférés, et un sujet général. Gardez la situation complète, les documents et tous les numéros pour la consultation elle-même.

### Puis-je rendre le numéro de téléphone obligatoire seulement quand quelqu'un choisit un appel téléphonique ?

Pas dans la définition des champs, qui valide chaque champ séparément. Mettez required sur l'input dans le navigateur quand le téléphone est choisi, et vérifiez-le dans votre code serveur avant d'envoyer.

### Comment savoir dans quel fuseau horaire est l'horaire demandé ?

Remplissez un champ timezone masqué avec Intl.DateTimeFormat().resolvedOptions().timeZone dans le navigateur. Il stocke un nom IANA comme Europe/London à côté du jour et du créneau demandés.

### Ikiform peut-il envoyer un e-mail à la personne qui a rempli le formulaire ?

Non. Ikiform envoie des e-mails à vous et à votre équipe, pas à l'expéditeur : il n'y a donc ni réponse automatique ni e-mail de confirmation. Envoyez un webhook à votre outil d'e-mailing et envoyez la confirmation depuis celui-ci.

## Autres guides

- [Formulaire de capture de leads](https://ikiform.com/fr/guides/lead-capture.md): Un formulaire de capture de leads sur votre propre site, qui vous écrit avec le lead en adresse de réponse, alerte le chat de l'équipe et alimente votre CRM, pour répondre tant que le lead est encore là.
- [Formulaire de demande de démo](https://ikiform.com/fr/guides/lead-capture/demo-request.md): Un formulaire de réservation de démo qui stocke le lead, puis envoie le visiteur vers Cal.com ou Calendly avec son nom et son e-mail déjà remplis.
- [Formulaire de demande de projet](https://ikiform.com/fr/guides/lead-capture/project-inquiry.md): Un formulaire de démarrage de projet pour studios et freelances, qui trie les demandes en chaudes, tièdes et hors cible selon le budget et le calendrier, avant que quiconque ne lise le brief.
- [Formulaire de demande de devis](https://ikiform.com/fr/guides/lead-capture/quote-request.md): Un formulaire de demande de devis qui reçoit les briefs sous forme de liens partagés plutôt qu'en pièces jointes, fixe une échéance à chaque demande et garde les leads riches en liens hors du spam.
- [Leads vers HubSpot ou Pipedrive](https://ikiform.com/fr/guides/lead-capture/leads-to-crm.md): Convertissez chaque webhook Ikiform signé en upsert de contact HubSpot ou en appels person et lead de Pipedrive, sans contacts en double quand une livraison est réessayée.
- [Formulaire de leads Next.js](https://ikiform.com/fr/guides/lead-capture/nextjs.md): Un formulaire de leads Next.js App Router qui valide avec Zod dans une server action, garde les UTM du premier contact dans un cookie et envoie les leads qualifiés vers votre agenda.
