Formulário de feedback para um site ou app

Formulário de feedback para um site ou app6 min de leitura

Rode uma pesquisa NPS no seu próprio site, do link no e-mail até a nota

Rode uma pesquisa NPS com a nota de 0 a 10 no e-mail, uma pergunta de acompanhamento na sua própria página que muda com a nota e um script que calcula o NPS.

Preet SutharFundador, Ikiform

Coloque onze links, de 0 a 10, no e-mail da pesquisa, cada um apontando para uma página no seu site com a nota e um token opaco do respondente na URL. A página pré-seleciona a nota, faz uma pergunta de acompanhamento que depende dela e não registra nada até a pessoa clicar em Enviar. Armazene a nota num campo number de 0 a 10 e calcule o NPS como a parcela de 9 e 10 menos a parcela de 0 a 6.

O que muda no NPS

O NPS faz uma pergunta com uma escala fixa: qual a probabilidade de você nos recomendar, de 0 a 10. Três detalhes o diferenciam da nota de 1 a 5 do guia principal:

  • A escala começa em 0. O campo rating do Ikiform vai de 1 a no máximo 10, então não comporta um 0. O NPS usa um campo number com min: 0, max: 10 e integer: true, armazenado como número.
  • Normalmente é enviado por e-mail. Clientes de e-mail não enviam formulários nem rodam scripts de forma confiável, então o e-mail leva links, e o formulário fica no seu site. O Ikiform não tem páginas de formulário hospedadas.
  • Links em e-mails são clicados por máquinas. Muitos scanners de segurança de e-mail abrem cada link de uma mensagem para verificá-lo. Se abrir um link registrasse uma nota, você coletaria notas de scanners. A página só registra quando uma pessoa clica em Enviar.
Como cada faixa de notas do NPS ganha sua própria pergunta de acompanhamento e conta para o resultado0 a 6O que maisdecepcionou você?detratorsubtraído7 ou 8O que faria virar um10?neutroconta só no total9 ou 10O que você maisvaloriza?promotorsomadopontuaçãopergunta seguinteconta comoComo cada faixa de notas do NPS ganha sua própria pergunta de acompanhamento e conta para o resultado0 a 6O quemaisdecepcionouvocê?detratorsubtraído7 ou 8O quefariavirarum 10?neutroconta sóno total9 ou 10O quevocêmaisvaloriza?promotorsomadopontuaçãoperguntaseguinteconta como
  1. Uma nota de 0 a 6 pergunta o que decepcionou a pessoa, e conta como detratora.
  2. Um 7 ou 8 pergunta o que faria virar um 10, e conta só para o total.
  3. Um 9 ou 10 pergunta o que a pessoa mais valoriza, e conta como promotora.
  4. O NPS é a porcentagem de promotores menos a porcentagem de detratores, de menos 100 a 100.
A pergunta muda com a nota porque a resposta útil muda. O motivo de um detrator é um problema para corrigir; o de um promotor é texto para a sua homepage.

Os campos

Prévia

Roda o validador do Ikiform no seu navegador. Nada é enviado.

Tente uma nota 11 ou 7.5: o Ikiform recusa as duas. Um wave ausente também é recusado, então toda resposta pertence a uma rodada de pesquisa.
CampoPor quê
scorenumber, de 0 a 10, só números inteiros. Armazenado como número
reasonA pergunta de acompanhamento. Opcional, porque uma nota sozinha ainda conta
respondentUm token opaco que você gera por pessoa e envia. Leva de volta ao cliente no seu próprio banco de dados
waveQual rodada, como 2026-q4. Permite comparar trimestres sem filtros de data

Não coloque o e-mail nem o id da conta do cliente no link. URLs vão parar no histórico do navegador, em logs de proxy e em e-mails encaminhados. Um token aleatório de 16 caracteres ou mais diz quem respondeu, e nada a mais ninguém.

O e-mail da pesquisa

Monte a linha de links com as merge tags da sua ferramenta de e-mail, ou gere-a no código que envia o e-mail. Esta função devolve o HTML para um respondente:

nps-email.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>`;
}

Limite o e-mail à pergunta e à linha de números. Uma introdução longa empurra a escala para baixo da dobra nos celulares, e a maioria das respostas vem de quem clica num número sem rolar.

A landing page

Coloque isto em /nps no seu site. A página desenha a escala de 0 a 10, pré-seleciona a nota do link, define a pergunta de acompanhamento para essa nota e envia com fetch só quando a pessoa clica em Enviar. Se ela mudar a nota na página, a pergunta muda junto.

nps.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>
A página também funciona sem link: quem abre /nps diretamente vê a escala sem nada selecionado. A resposta é recusada por falta de wave, a menos que você defina um padrão, o que mantém visitas perdidas fora dos seus números.

Calcule a nota

Leia todas as respostas de uma rodada com o SDK, fique com a resposta mais recente de cada respondente e aplique a fórmula. Rode com uma chave secreta em IKIFORM_SECRET_KEY:

nps.mjs
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}`);

O script mantém uma resposta por respondente porque às vezes as pessoas clicam num segundo número depois de mudar de ideia. O Ikiform não junta nem rejeita envios repetidos por e-mail. Uma repetição idêntica em até 24 horas só soma à pontuação de spam. Remova os duplicados onde a lista fica, ou ao exportar.

Confira se funciona

  1. Abra /nps?score=3&r=test_respondent_0001&wave=2026-q4. O 3 aparece selecionado e a pergunta diz "O que mais decepcionou você?"
  2. Clique em 9. A pergunta muda para "O que você mais valoriza nele?"
  3. Abra o mesmo link com curl. Nada novo aparece em Submissions, porque nada foi enviado.
  4. Clique em Enviar. A resposta mostra score como o número 9.
  5. Rode node nps.mjs 2026-q4. Ele informa uma pessoa e um promotor.

Pegadinhas

  • Não faça a pesquisa com o mesmo cliente com frequência demais. Escolha um intervalo, como uma vez por trimestre, e mantenha a lista de respondentes do seu lado.
  • Amostras pequenas oscilam muito. Com 20 respostas, uma pessoa move o NPS em 5 pontos. Informe o número de pessoas ao lado da pontuação.
  • Notas baixas merecem resposta. Mande os detratores para o chat da equipe com um relay de webhook como o alerta de nota baixa do guia principal, usando score <= 6.
  • O template de formulário de pesquisa pergunta a nota dentro de uma pesquisa mais longa. Isso serve para um estudo pontual; uma pergunta de NPS isolada recebe mais respostas.
  • O Ikiform não envia e-mail para quem preenche o formulário. Os e-mails de notificação vão só para você e sua equipe, então não há resposta automática, e-mail de confirmação nem double opt-in. Envie esses e-mails pela sua ferramenta de e-mail, disparados por um webhook.

Solução de problemas

ProblemaO que fazer
422 com Must be at most 10, ou um erro de número inteiroA nota veio de outro lugar que não a escala, como uma URL editada à mão. A página só aceita de 0 a 10 vindo do link; confira os links do seu template de e-mail.
422 citando waveO link não levou wave, ou o valor não segue o formato 2026-q4. Corrija o template de e-mail ou defina um padrão no script da página.
403 origin_not_allowed, ou todo envio cai no spamO domínio da página não está em Allowed domains do formulário. Adicione-o, mais localhost para testes locais. Subdomínios de um domínio listado são permitidos.
429 rate_limitedUm visitante enviou mais do que o limite de taxa do formulário permite. O Standard permite 5 por minuto por visitante. Espere o Retry-After, ou escolha Relaxed ou Custom para formulários que enviam a partir de um único IP compartilhado.

Perguntas

Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.