# Buat formulir permintaan konsultasi untuk bisnis jasa

> Formulir permintaan konsultasi untuk akuntan, pengacara, coach, dan klinik yang menanyakan cara dan waktu menghubungi, dan menyimpan detail sensitif untuk panggilannya.

Terbit 2026-10-25 · https://ikiform.com/id/guides/lead-capture/consultation-request

Minta nama, email, cara menghubungi mereka, dan kira-kira kapan, plus topik umum, dan simpan setiap detail situasi mereka untuk panggilan. Catat zona waktu dari browser agar pagi yang diminta berarti pagi di tempat mereka. Terapkan aturan yang tidak bisa diungkapkan schema formulir, seperti nomor telepon hanya jika mereka memilih panggilan telepon, di kode Anda sendiri.

## Apa yang berbeda dari permintaan konsultasi

Permintaan konsultasi datang dari orang yang punya masalah: surat pajak, sengketa sewa, bisnis yang ingin mereka mulai. Mereka ingin menjelaskannya, dan formulir mengundang mereka melakukannya. Itulah jebakannya. Apa pun yang mereka ketik disimpan bersama kiriman, ditampilkan di email notifikasi, dan dikirim lewat webhook ke mana pun Anda mengarahkannya. Nomor rekening dan detail kasus tidak pantas ada di tempat-tempat itu.

Jadi tugas formulir lebih sempit daripada kelihatannya: siapa mereka, bagaimana dan kapan menghubungi mereka, dan kira-kira tentang apa. Sisanya menunggu panggilan, tempat Anda mengendalikan apa yang dicatat dan di mana disimpan.

Diagram: Apa yang ditanyakan formulir konsultasi, dan apa yang menunggu panggilan

1. Formulir menanyakan siapa mereka: nama dan email.
2. Formulir menanyakan cara menghubungi mereka: email, telepon, atau video.
3. Formulir menanyakan kapan: hari yang diinginkan, rentang waktu, dan zona waktu mereka.
4. Formulir menanyakan topiknya, sebagai satu topik umum.
5. Panggilan membahas situasi mereka dengan kata-kata mereka sendiri.
6. Dokumen dikirim lewat saluran yang Anda pilih, bukan formulir.
7. Nomor rekening, ID, dan catatan tidak pernah diketik di formulir.
8. Panggilan diakhiri dengan jawaban Anda tentang kecocokan dan biaya.

Baris atas sudah cukup untuk memesan panggilan dan mempersiapkannya. Baris bawah adalah tujuan panggilannya.

## Field-nya

Formulir: Free consultation

- **Nama** (`name`, text, wajib)
- **Email** (`email`, email, wajib)
- **Bagaimana kami sebaiknya menghubungi Anda?** (`contact_method`, radio, wajib)
- **Telepon** (`phone`, phone)
- **Tentang apa?** (`topic`, select, wajib)
- **Hari yang diinginkan** (`preferred_date`, date)
- **Waktu terbaik** (`time_window`, radio)
- **Zona waktu** (`timezone`, text)
- **Ada yang perlu kami ketahui lebih dulu?** (`note`, textarea)

Pilih **Phone call** dan biarkan telepon kosong: schema menerimanya, karena field tidak bisa diwajibkan hanya kadang-kadang. Kode di bawah menutup celah itu.

| Field | Mengapa bentuknya begini |
| --- | --- |
| `contact_method` | `radio`, agar kontak pertama terjadi sesuai cara yang mereka minta |
| `phone` | Opsional di schema, diwajibkan oleh kode Anda saat mereka memilih panggilan telepon |
| `preferred_date` | Field `date` menyimpan `YYYY-MM-DD`. `min` di schema-nya tetap, jadi halaman mengatur besok sebagai `min` di browser |
| `timezone` | Diisi dari browser dengan `Intl.DateTimeFormat().resolvedOptions().timeZone`, seperti `Europe/Berlin` |
| `note` | Dibatasi 500 karakter. Email notifikasi menampilkan hingga 500 karakter per nilai, jadi seluruh catatan muat |

## Aturan bersyarat di kode Anda sendiri

Ikiform memvalidasi setiap field secara terpisah. Aturan yang bergantung pada field lain, seperti telepon hanya untuk panggilan telepon, berjalan di kode Anda. Versi server bisa benar-benar menegakkannya; versi browser memandu pengunjung dan tidak mengirim apa pun yang tidak perlu.

**HTML**

Script mengatur `required` pada input telepon saat **Phone call** dipilih, mengatur besok sebagai hari paling awal di kalender pengunjung sendiri, dan mengisi zona waktu tersembunyi.

`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**

Server action menolak panggilan telepon tanpa nomor sebelum mengirim, menghapus nomor yang sempat diketik lalu ditinggalkan, dan me-redirect ke halaman terima kasih. Render formulir dengan `useActionState` dan tampilkan `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**

Input telepon hanya ada saat panggilan telepon dipilih, dan nilai kosong dibuang sebelum kiriman, jadi field opsional tiba dalam keadaan tidak ada alih-alih kosong.

`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>
```

Versi sisi server mengirim dengan secret key, jadi Ikiform melewati **Allowed domains** dan pemeriksaan manusianya. Setiap kiriman kemudian berasal dari IP server Anda, dan batas Standard 5 per menit per pengunjung menghitung semua pengunjung Anda bersama-sama. Pilih [rate limit](https://ikiform.com/id/docs/rate-limits.md) Relaxed atau Custom untuk formulir yang Anda kirim lewat server, dan simpan `IKIFORM_SECRET_KEY` di environment variable khusus server. Versi server meneruskan header `Origin` pengunjung, karena fetch di sisi server tidak mengirimnya dan Ikiform mendeteksi uji `localhost` gratis dari header itu.

## Jauhkan detail sensitif dari formulir

- Sebutkan di teks bantuan, seperti formulir di atas. Orang mengikuti instruksi yang jelas lebih sering dari yang Anda kira.
- Jangan tambahkan field untuk nomor identitas, nomor rekening, atau detail medis. Jika profesi Anda punya aturan tentang data klien, periksa dulu sebelum mengumpulkan apa pun selain detail kontak, dan kumpulkan sisanya lewat saluran yang memenuhi aturan itu.
- Nama field juga penting. Ikiform menahan email notifikasi untuk ditinjau saat nama field terlihat seperti kredensial, termasuk nama yang mengandung `password`, `ssn`, `cvv`, atau `seed`. Field bernama `seed_round` di formulir konsultan startup memicu hal ini. Beri nama `funding_stage`.
- Arahkan webhook hanya ke tempat yang seluruh anggotanya boleh membaca nama klien, seperti channel privat, bukan channel seluruh perusahaan.

## Balasan pertama

Email notifikasi tiba dengan **Reply** mengarah ke pengunjung, jadi balasan pertama bisa langsung dari inbox Anda. Buat balasan itu melakukan satu tugas: memastikan waktu. Dua slot konkret di dalam rentang waktu mereka lebih mudah disetujui daripada pertanyaan tentang kapan mereka bisa.

- Konversikan rentang waktu mereka ke waktu Anda sebelum menulis. `morning` di `Australia/Sydney` adalah malam sebelumnya di London.
- Tawarkan dua slot dalam zona waktu mereka, dan sebutkan zonanya di email, agar tidak ada yang datang satu jam lebih awal.
- Sebutkan cara Anda akan menghubungi: nomor yang mereka berikan, atau tautan video yang Anda kirim bersama undangan.
- Jika topiknya di luar bidang Anda, katakan di balasan pertama dan sarankan orang yang menanganinya. Balasan itu tetap layak dikirim.

## Pastikan berfungsi

1. Pilih **Phone call**, biarkan telepon kosong, lalu kirim. Browser atau server Anda menghentikannya dengan pesan.
2. Pilih **Email**, kirim, lalu buka kirimannya. Tidak ada nilai `phone` dan ada zona waktu Anda, seperti `America/Chicago`.
3. Coba pilih hari ini sebagai hari yang diinginkan. Pemilih tanggal tidak menawarkannya.
4. Periksa email notifikasi. Catatan tampil lengkap, dan **Reply** menulis ke pengunjung.
5. Daftar ID field Anda dan pastikan tidak ada yang mengandung `password`, `ssn`, `cvv`, atau `seed`.

## Hal yang sering menjebak

- **Tanggal tanpa zona waktu.** `preferred_date` adalah hari kalender, bukan momen. Baca bersama `timezone` dan `time_window` sebelum Anda menelepon.
- **Radio tanpa pilihan.** Grup radio yang tidak dicentang tidak mengirim apa pun. Itu tidak masalah untuk `time_window`, yang opsional, dan itulah alasan `contact_method` dimulai dengan **Email** tercentang.
- **Tidak ada konfirmasi booking.** Ikiform tidak mengirim email ke orang yang mengisi formulir. Balasan pertama Anda yang memastikan waktunya. Jika Anda ingin orang memilih slot sendiri, serahkan ke penjadwal seperti di [Formulir permintaan demo](https://ikiform.com/id/guides/lead-capture/demo-request.md).
- **Format telepon.** Field telepon menerima 6 sampai 15 digit sesuai yang diketik. Aktifkan `e164` jika Anda menelepon dari sistem yang butuh `+14155550123`, dan nomor tanpa kode negara kemudian ditolak.

## Pertanyaan

### Apa yang sebaiknya ditanyakan formulir konsultasi gratis?

Nama, email, cara mereka ingin dihubungi, hari dan waktu yang diinginkan, dan topik umum. Simpan situasi lengkap mereka, dokumen, dan nomor apa pun untuk konsultasinya sendiri.

### Bisakah nomor telepon diwajibkan hanya saat seseorang memilih panggilan telepon?

Tidak di definisi field, yang memvalidasi setiap field secara terpisah. Atur required pada input di browser saat telepon dipilih, dan periksa di kode server Anda sebelum mengirim.

### Bagaimana saya tahu waktu yang diminta berada di zona waktu mana?

Isi field timezone tersembunyi dari Intl.DateTimeFormat().resolvedOptions().timeZone di browser. Field itu menyimpan nama IANA seperti Europe/London di samping hari dan rentang waktu yang diminta.

### Bisakah Ikiform mengirim email ke orang yang mengisi formulir?

Tidak. Ikiform mengirim email ke Anda dan tim, bukan ke pengirim formulir, jadi tidak ada autoresponder atau email konfirmasi. Kirim webhook ke alat email Anda dan kirim konfirmasinya dari sana.

## Panduan lainnya

- [Formulir lead capture](https://ikiform.com/id/guides/lead-capture.md): Formulir lead capture di situs Anda sendiri yang mengirimi Anda email dengan reply-to, memberi tahu chat tim, dan mengisi CRM, agar Anda menjawab selagi lead masih ada.
- [Formulir permintaan demo](https://ikiform.com/id/guides/lead-capture/demo-request.md): Formulir pesan demo yang menyimpan lead, lalu mengarahkan pengunjung ke Cal.com atau Calendly dengan nama dan email yang sudah terisi.
- [Formulir inquiry proyek](https://ikiform.com/id/guides/lead-capture/project-inquiry.md): Formulir mulai proyek untuk studio dan freelancer yang memilah inquiry menjadi hot, warm, dan tidak cocok berdasarkan anggaran dan waktu sebelum ada yang membaca brief-nya.
- [Formulir permintaan penawaran](https://ikiform.com/id/guides/lead-capture/quote-request.md): Formulir minta penawaran yang mengumpulkan brief sebagai tautan bersama alih-alih unggahan, memberi tenggat pada setiap permintaan, dan menjauhkan lead yang penuh tautan dari spam.
- [Lead ke HubSpot atau Pipedrive](https://ikiform.com/id/guides/lead-capture/leads-to-crm.md): Petakan setiap webhook Ikiform bertanda tangan ke contact upsert HubSpot atau panggilan person dan lead Pipedrive, tanpa kontak duplikat saat pengiriman dicoba ulang.
- [Formulir lead Next.js](https://ikiform.com/id/guides/lead-capture/nextjs.md): Formulir lead App Router Next.js yang memvalidasi dengan Zod di server action, menyimpan UTM first-touch di cookie, dan mengirim lead yang lolos kualifikasi ke kalender Anda.
