# Jalankan survei NPS di situs Anda sendiri, dari tautan email hingga skor

> Jalankan survei NPS dengan skor 0 sampai 10 di email, pertanyaan lanjutan di halaman Anda sendiri yang berubah sesuai skor, dan script yang menghitung NPS.

Terbit 2026-10-25 · https://ikiform.com/id/guides/feedback-form/nps-survey

Taruh sebelas tautan, 0 sampai 10, di email survei, masing-masing mengarah ke halaman di situs Anda dengan skor dan token responden yang tidak bisa ditebak di URL. Halaman memilih skor itu lebih dulu, mengajukan pertanyaan lanjutan sesuai skornya, dan tidak mencatat apa pun sampai orang itu menekan Kirim. Simpan skor di field number dari 0 sampai 10, lalu hitung NPS sebagai porsi skor 9 dan 10 dikurangi porsi skor 0 sampai 6.

## Apa yang berbeda dari NPS

NPS mengajukan satu pertanyaan dengan skala tetap: seberapa besar kemungkinan Anda merekomendasikan kami, dari 0 sampai 10. Tiga detail membuatnya berbeda dari rating 1 sampai 5 di pilar:

- **Skalanya mulai dari 0.** Field `rating` Ikiform berjalan dari 1 hingga maksimal 10, jadi tidak bisa menampung 0. NPS memakai field `number` dengan `min: 0`, `max: 10`, dan `integer: true`, disimpan sebagai angka.
- **Biasanya dikirim lewat email.** Klien email tidak bisa diandalkan untuk mengirim formulir atau menjalankan script, jadi email membawa tautan, dan formulirnya ada di situs Anda. Ikiform tidak punya halaman formulir yang di-host.
- **Tautan di email diklik oleh mesin.** Banyak pemindai keamanan email membuka setiap tautan dalam pesan untuk memeriksanya. Jika membuka tautan langsung mencatat skor, Anda akan mengumpulkan skor dari pemindai. Halaman hanya mencatat saat seseorang menekan Kirim.

Diagram: Bagaimana setiap rentang skor NPS mendapat pertanyaan lanjutannya sendiri dan dihitung dalam hasil

1. Skor 0 sampai 6 menanyakan apa yang mengecewakan mereka, dan dihitung sebagai detractor.
2. Skor 7 atau 8 menanyakan apa yang akan membuatnya menjadi 10, dan hanya dihitung dalam total.
3. Skor 9 atau 10 menanyakan apa yang paling mereka hargai, dan dihitung sebagai promoter.
4. NPS adalah persentase promoter dikurangi persentase detractor, dari minus 100 hingga 100.

Pertanyaannya berubah sesuai skor karena jawaban yang berguna juga berbeda. Alasan detractor adalah masalah yang perlu diperbaiki; alasan promoter adalah bahan copy untuk homepage Anda.

## Field-nya

Formulir: NPS survey

- **Seberapa besar kemungkinan Anda merekomendasikan kami ke teman atau kolega?** (`score`, number, wajib)
- **Apa alasan utama skor Anda?** (`reason`, textarea)
- **Responden** (`respondent`, text)
- **Gelombang survei** (`wave`, text, wajib)

Coba skor 11 atau 7.5: Ikiform menolak keduanya. `wave` yang tidak ada juga ditolak, jadi setiap jawaban termasuk dalam satu putaran survei.

| Field | Alasan |
| --- | --- |
| `score` | `number`, 0 sampai 10, hanya bilangan bulat. Disimpan sebagai angka |
| `reason` | Pertanyaan lanjutan. Opsional, karena skor saja tetap dihitung |
| `respondent` | Token yang tidak bisa ditebak yang Anda buat per orang lalu kirim. Dipetakan kembali ke pelanggan di database Anda sendiri |
| `wave` | Putaran yang mana, seperti `2026-q4`. Memungkinkan Anda membandingkan kuartal tanpa filter tanggal |

Jangan taruh email atau ID akun pelanggan di tautan. URL berakhir di riwayat browser, log proxy, dan email yang diteruskan. Token acak 16 karakter atau lebih memberi tahu Anda siapa yang menjawab dan tidak memberi tahu apa pun kepada orang lain.

## Email survei

Susun deretan tautan dengan merge tag alat email Anda, atau buat di kode yang mengirim email. Fungsi ini mengembalikan HTML untuk satu responden:

`nps-email.js`:

```js
function npsRow(respondent, wave) {
  const cells = Array.from({ length: 11 }, (_, score) => {
    const link = new URL("https://example.com/nps");
    link.searchParams.set("score", String(score));
    link.searchParams.set("r", respondent);
    link.searchParams.set("wave", wave);
    return `<td><a href="${link}" style="display:block;padding:8px 10px">${score}</a></td>`;
  });
  return `<table role="presentation"><tr>${cells.join("")}</tr></table>
<p>0 is not at all likely, 10 is extremely likely.</p>`;
}
```

Batasi email pada pertanyaan dan deretan angka. Intro yang panjang mendorong skala ke bawah layar di ponsel, dan sebagian besar jawaban datang dari orang yang mengklik angka tanpa menggulir.

## Landing page

Taruh ini di `/nps` di situs Anda. Halaman menggambar skala 0 sampai 10, memilih skor dari tautan, mengatur pertanyaan lanjutan untuk skor itu, dan mengirim dengan `fetch` hanya saat orang itu menekan Kirim. Jika mereka mengubah skor di halaman, pertanyaannya ikut berubah.

`nps.html`:

```html
<form id="nps" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <fieldset>
    <legend>How likely are you to recommend us to a friend or colleague?</legend>
    <div id="nps-scale"></div>
  </fieldset>
  <label for="reason" id="reason-label">What's the main reason for your score?</label>
  <textarea id="reason" name="reason" rows="4" maxlength="4000"></textarea>
  <input type="hidden" name="respondent" />
  <input type="hidden" name="wave" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send</button>
  <p id="nps-status" role="status"></p>
</form>

<script>
  const QUESTIONS = [
    [6, "What disappointed you most?"],
    [8, "What would make it a 10 for you?"],
    [10, "What do you value most about it?"],
  ];
  const params = new URLSearchParams(location.search);
  const form = document.getElementById("nps");
  const label = document.getElementById("reason-label");
  const scale = document.getElementById("nps-scale");

  for (let score = 0; score <= 10; score += 1) {
    scale.insertAdjacentHTML(
      "beforeend",
      `<label><input type="radio" name="score" value="${score}" required /> ${score}</label>`,
    );
  }

  function askFor(score) {
    label.textContent = QUESTIONS.find(([top]) => score <= top)[1];
  }

  const preset = Number(params.get("score"));
  if (params.has("score") && Number.isInteger(preset) && preset >= 0 && preset <= 10) {
    form.elements.score.value = String(preset);
    askFor(preset);
  }
  form.elements.respondent.value = params.get("r") ?? "";
  form.elements.wave.value = params.get("wave") ?? "";
  scale.addEventListener("change", (event) => askFor(Number(event.target.value)));

  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    const status = document.getElementById("nps-status");
    status.textContent = "Sending";
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json" },
      body: new FormData(form),
    }).catch(() => null);
    const result = await response?.json().catch(() => null);
    if (result?.ok) {
      form.replaceChildren(Object.assign(document.createElement("p"), { textContent: "Thanks for answering." }));
      return;
    }
    status.textContent = result?.error?.fields?.score?.[0] ?? result?.error?.message ?? "Couldn't send it. Try again.";
  });
</script>
```

> Halaman ini juga bekerja tanpa tautan: seseorang yang membuka `/nps` langsung melihat skala tanpa pilihan. Jawaban mereka ditolak karena `wave` tidak ada, kecuali Anda mengatur nilai default, sehingga kunjungan nyasar tidak masuk ke angka Anda.

## Hitung skornya

Baca setiap jawaban untuk satu gelombang dengan SDK, simpan jawaban terakhir per responden, dan terapkan rumusnya. Jalankan dengan secret key di `IKIFORM_SECRET_KEY`:

`nps.mjs`:

```js
import { createClient } from "@ikiform/sdk";

const WAVE = process.argv[2] ?? "2026-q4";
const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY });
const latest = new Map();

for await (const submission of ikiform.submissions.iterate({ formId: "frm_xxxxxxxxxxxxxxxx", status: "received" })) {
  const { score, respondent, wave } = submission.fields;
  if (wave !== WAVE) continue;
  const key = respondent || submission.id;
  const seen = latest.get(key);
  if (!seen || seen.at < submission.created_at) latest.set(key, { score, at: submission.created_at });
}

const scores = [...latest.values()].map((answer) => answer.score);
const promoters = scores.filter((value) => value >= 9).length;
const detractors = scores.filter((value) => value <= 6).length;
const nps = Math.round(((promoters - detractors) / scores.length) * 100);

console.log(`${WAVE}: ${scores.length} people, ${promoters} promoters, ${detractors} detractors, NPS ${nps}`);
```

Script menyimpan satu jawaban per responden karena orang kadang mengklik angka kedua setelah berubah pikiran. Ikiform tidak menggabungkan atau menolak kiriman berulang berdasarkan email. Pengulangan identik dalam 24 jam hanya menambah skor spam. Lakukan deduplikasi di tempat daftar itu disimpan, atau saat Anda mengekspor.

## Pastikan berfungsi

1. Buka `/nps?score=3&r=test_respondent_0001&wave=2026-q4`. Angka 3 terpilih dan pertanyaannya berbunyi "What disappointed you most?"
2. Klik 9. Pertanyaannya berubah menjadi "What do you value most about it?"
3. Buka tautan yang sama dengan `curl`. Tidak ada yang baru di **Submissions**, karena tidak ada yang dikirim.
4. Tekan Kirim. Jawaban menampilkan `score` sebagai angka 9.
5. Jalankan `node nps.mjs 2026-q4`. Hasilnya melaporkan satu orang dan satu promoter.

## Hal yang sering menjebak

- Jangan terlalu sering menyurvei pelanggan yang sama. Tentukan interval, misalnya sekali per kuartal, dan simpan daftar responden di sisi Anda.
- Sampel kecil berayun liar. Dengan 20 jawaban, satu orang menggeser NPS sebesar 5 poin. Laporkan jumlah orang di samping skornya.
- Skor rendah layak dibalas. Kirim detractor ke chat tim Anda dengan relay webhook seperti [peringatan skor rendah](https://ikiform.com/id/guides/feedback-form.md#routing) di pilar, memakai `score <= 6`.
- [Template formulir](https://ikiform.com/forms/survey) survei menanyakan skor di dalam survei yang lebih panjang. Itu cocok untuk studi sekali jalan; pertanyaan NPS yang berdiri sendiri mendapat lebih banyak jawaban.
- Ikiform tidak mengirim email ke orang yang mengisi formulir. Email notifikasi hanya dikirim ke Anda dan tim, jadi tidak ada autoresponder, email konfirmasi, atau double opt-in. Kirim semua itu dari alat email Anda, dipicu oleh [webhook](https://ikiform.com/id/docs/webhooks.md).

## Pemecahan masalah

| Masalah | Yang harus dilakukan |
| --- | --- |
| `422` dengan `Must be at most 10`, atau error bilangan bulat | Skornya berasal dari luar skala, misalnya URL yang diedit manual. Halaman hanya menerima 0 sampai 10 dari tautan; periksa tautan di template email Anda. |
| `422` yang menyebut `wave` | Tautan tidak membawa `wave`, atau nilainya tidak cocok dengan format seperti `2026-q4`. Perbaiki template email, atau atur nilai default di script halaman. |
| `403 origin_not_allowed`, atau setiap kiriman masuk ke spam | Domain halaman tidak ada di **Allowed domains** formulir. Tambahkan, ditambah `localhost` untuk uji lokal. Subdomain dari domain yang terdaftar diizinkan. |
| `429 rate_limited` | Satu pengunjung mengirim melebihi rate limit formulir. Standard mengizinkan 5 per menit per pengunjung. Tunggu sesuai `Retry-After`, atau pilih Relaxed atau Custom untuk formulir yang mengirim dari satu IP bersama. |

## Pertanyaan

### Bagaimana cara menaruh pertanyaan NPS di email?

Taruh sebelas tautan, 0 sampai 10, di email, masing-masing membuka halaman di situs Anda dengan skor di URL. Halaman memilih skor itu, mengajukan pertanyaan lanjutan, dan mencatat jawaban saat orang itu menekan Kirim.

### Bisakah saya memakai field rating Ikiform untuk NPS?

Tidak. Rating berjalan dari 1 hingga maksimal 10, dan NPS butuh 0. Gunakan field number dengan min 0, max 10, dan integer true.

### Bagaimana NPS dihitung?

Ambil persentase jawaban yang bernilai 9 atau 10, kurangi dengan persentase yang bernilai 0 sampai 6, dan abaikan 7 dan 8 kecuali dalam total. Hasilnya berkisar dari minus 100 hingga 100.

### Mengapa tidak mencatat skor begitu tautan diklik?

Pemindai keamanan email membuka tautan di email masuk untuk memeriksanya. Mencatat saat klik akan menghitung kunjungan itu sebagai jawaban. Mencatat hanya saat Kirim menjaga angkanya tetap jujur.

## Panduan lainnya

- [Formulir feedback untuk website atau aplikasi](https://ikiform.com/id/guides/feedback-form.md): Pasang formulir feedback di halaman mana pun atau di dalam aplikasi Anda: rating, komentar, halaman asalnya, filter spam, dan peringatan saat skornya rendah.
- [Survei pembatalan](https://ikiform.com/id/guides/feedback-form/cancellation-survey.md): Tanyakan mengapa pelanggan membatalkan di dalam alur pembatalan SaaS Anda, jaga Skip tetap satu klik, simpan alasannya ke Stripe dan Ikiform, dan jangan pernah menunda pembatalan.
- [Feedback beta tester](https://ikiform.com/id/guides/feedback-form/beta-tester-feedback.md): Formulir feedback beta dengan tautan pribadi per tester dan build, dua skala rating, dan ringkasan mingguan yang menunjukkan apakah setiap build makin baik.
- [Widget feedback React](https://ikiform.com/id/guides/feedback-form/react-feedback-widget.md): Tombol feedback mengambang untuk aplikasi React yang membuka panel kecil, mengirim layar, versi aplikasi, dan akun bersama setiap jawaban, dan tidak pernah kehilangan draf.
- [Formulir laporan bug](https://ikiform.com/id/guides/feedback-form/bug-report.md): Formulir laporan bug yang menanyakan langkah dan tingkat keparahan, dan membiarkan halaman menambahkan URL, browser, viewport, dan error terbaru dengan sendirinya.
- [Formulir permintaan fitur](https://ikiform.com/id/guides/feedback-form/feature-request.md): Formulir permintaan fitur yang menanyakan masalahnya alih-alih solusinya, menyimpan permintaan per tema, menghitung orang bukan baris, dan mengirim email saat fiturnya rilis.
