Más allá de los formularios del creador

Más allá de los formularios del creador8 min de lectura

Formularios de Framer: cuándo añadir un backend de formularios y cómo

Los formularios de Framer ya envían a email, Sheets o un webhook; añade Ikiform como webhook para tener envíos guardados y filtrados, avisos al equipo y más destinos.

Preet SutharFundador, Ikiform

Los formularios nativos de Framer envían cada envío a email, Google Sheets o un webhook, con protección antispam integrada, así que para una bandeja o una hoja puede que no necesites nada más. Añade Ikiform como destino webhook cuando quieras guardar cada envío con un estado de spam, validarlo contra tus campos, enviarlo por email al equipo y reenviarlo a varios webhooks firmados. Como Framer envía desde sus servidores, o relajas las comprobaciones de origen del formulario o pones en medio un pequeño relay que verifique la firma de Framer.

Qué cambia en Framer

Framer es el único creador de esta guía sin un límite del que escapar. Sus formularios pueden enviar a email, Google Sheets o cualquier webhook, y Framer tiene protección antispam integrada que marca o bloquea los envíos sospechosos. Sus webhooks están firmados: cada petición lleva una cabecera Framer-Signature, un HMAC-SHA256 del cuerpo y del id del envío, y una Framer-Webhook-Submission-Id. Framer reintenta hasta cinco veces cuando tu endpoint no responde 2xx, y no sigue redirecciones.

Así que sé honesto sobre por qué añadirías un backend de formularios. Esto es lo que cubre cada lado:

QuieresSolo FramerCon Ikiform como webhook
Envíos en una bandeja o una hojaSíSí, más el panel
Un registro consultable con el spam marcado, y exportación CSVTu bandeja o tu hoja es el registroSí
Comprobaciones en el servidor de tipos, longitudes y opcionesNoSí, con una definición de campos
Avisos a varias personas, con Responder dirigido a quien envióDestino emailHasta 10 destinatarios, con reply-to
Varias herramientas alimentadas desde un formulario, cada una con reintentosUn webhook por cada destino que añadasHasta 3 endpoints firmados, con reintentos y un registro de entregas
El mismo backend en sitios de Framer, de Webflow y con códigoNoSí

Si la primera fila es todo lo que necesitas, quédate con los destinos de Framer. El resto de esta página es para cuando no lo es.

El problema: Framer envía desde sus servidores

Un destino webhook significa que los servidores de Framer envían el post, no el navegador del visitante. Cambian tres cosas en el lado de Ikiform:

  • Sin origen. Los envíos desde servidor no llevan cabecera Origin, así que con Allowed domains configurado, cada envío se trata como si viniera del sitio equivocado.
  • Direcciones IP compartidas. Ikiform cuenta los límites de frecuencia por dirección IP, y todos los envíos vienen de unas pocas direcciones de Framer en lugar de las de tus visitantes.
  • Sin honeypot. Framer envía los inputs que creaste y nada más, y su formulario no puede ejecutar la verificación humana de Ikiform.

Puedes aceptarlo, o solucionarlo con un relay. Abajo están los dos caminos.

Un formulario de Framer enviando a Ikiform directamente y a través de un relay firmadoWebhook directoRelay firmadoFormulariode FramerServidoresde FramerJSON, sinorigenIkiformdominiosvacíos,límiteRelaxedFormulariode FramerServidoresde Framerfirma cadaenvíotu WorkercompruebaFramer-SignatureIkiformclavesecreta,clave deidempotencia401,descartadoenvíowebhookenvíowebhookreenvíafirma incorrectaUn formulario de Framer enviando a Ikiform directamente y a través de un relay firmadoWebhook directoRelay firmadoFormulario deFramerServidores deFramerJSON, sin origenIkiformdominios vacíos,límite RelaxedFormulario deFramerServidores deFramerfirma cada envíotu WorkercompruebaFramer-SignatureIkiformclave secreta, clavede idempotencia401,descartadoenvíowebhookenvíowebhookreenvíafirma incorrecta
  1. Directo: un visitante envía el formulario de Framer, los servidores de Framer lo mandan como JSON sin cabecera Origin, e Ikiform lo acepta con Allowed domains vacío y un límite de frecuencia Relaxed.
  2. Relay: los servidores de Framer firman cada envío con tu secreto y lo mandan a un Worker que despliegas tú.
  3. El Worker recalcula la firma de Framer. Un envío sin firma válida recibe un 401 y no pasa de ahí.
  4. Un envío válido se reenvía a Ikiform con tu clave secreta y el id de envío de Framer como clave de idempotencia, así que un reintento de Framer nunca guarda una segunda copia.
El relay son una docena de líneas de lógica real. A cambio, el formulario puede mantener unos dominios permitidos estrictos, porque solo tu Worker tiene la clave secreta.

Opción 1: Ikiform como webhook

  1. En Framer, dale a cada input un nombre en minúsculas: name, work_email, company, team_size. Framer usa los nombres como claves JSON.
  2. Selecciona el formulario y, junto a Send To, elige Add… > Webhook. Pega tu endpoint. Deja el secreto vacío; Ikiform no comprueba Framer-Signature.
  3. En los ajustes del formulario de Ikiform, deja Allowed domains vacío, deja Spam check desactivado y pon Rate limit en Relaxed.
  4. Publica. Framer solo envía webhooks desde el sitio publicado.
URL del webhook
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx

Ikiform responde 201 en cuanto el envío está guardado, lo que Framer cuenta como entregado. Un 422 validation_failed falla igual en los cinco reintentos, así que arregla los nombres de los inputs en lugar de esperar. La puntuación de spam sigue aplicándose a cada envío: enlaces, frases de spam, direcciones desechables y repeticiones.

Opción 2: un relay firmado

Despliega un Cloudflare Worker que compruebe la firma de Framer y reenvíe los envíos válidos con tu clave secreta. Así el formulario de Ikiform puede listar tu dominio de Framer en Allowed domains con Reject, y se rechaza todo lo que no sea un navegador en tu sitio o tu Worker.

src/index.ts
interface Env {
  FRAMER_SECRET: string;
  IKIFORM_SECRET_KEY: string;
}

const ENDPOINT = "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx";
const encoder = new TextEncoder();

async function signatureFor(secret: string, body: ArrayBuffer, submissionId: string) {
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const id = encoder.encode(submissionId);
  const message = new Uint8Array(body.byteLength + id.byteLength);
  message.set(new Uint8Array(body), 0);
  message.set(id, body.byteLength);
  const digest = new Uint8Array(await crypto.subtle.sign("HMAC", key, message));
  return `sha256=${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
}

function sameText(a: string, b: string) {
  const left = encoder.encode(a);
  const right = encoder.encode(b);
  return left.byteLength === right.byteLength && crypto.subtle.timingSafeEqual(left, right);
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") return new Response(null, { status: 405 });
    const signature = request.headers.get("framer-signature") ?? "";
    const submissionId = request.headers.get("framer-webhook-submission-id") ?? "";
    const body = await request.arrayBuffer();
    if (!submissionId || !sameText(signature, await signatureFor(env.FRAMER_SECRET, body, submissionId))) {
      return new Response("Bad signature", { status: 401 });
    }

    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Bearer ${env.IKIFORM_SECRET_KEY}`,
        "Idempotency-Key": submissionId,
      },
      body,
    });
    if (response.ok) return new Response(null, { status: 204 });
    return new Response(await response.text(), { status: response.status });
  },
};

Despliégalo

Terminal
npx wrangler secret put FRAMER_SECRET
npx wrangler secret put IKIFORM_SECRET_KEY
npx wrangler deploy
  • FRAMER_SECRET es el secreto que configuras en el destino webhook de Framer. Framer exige al menos 32 caracteres y no volverá a mostrarlo, así que genéralo, guárdalo y pégalo en los dos sitios.
  • IKIFORM_SECRET_KEY es una API key secreta del panel de Ikiform. Nunca la pongas en Framer.
  • La firma es sha256= más el HMAC en hex del cuerpo sin procesar seguido del id del envío. Lee el cuerpo una vez como bytes y usa esos mismos bytes para la comprobación y para el reenvío.
  • Idempotency-Key hace que un reintento de Framer repita la primera respuesta de Ikiform en lugar de guardar un duplicado. Las claves se guardan durante 24 horas.
  • El Worker transmite el estado de Ikiform. Framer reintenta cualquier respuesta que no sea 2xx, hasta cinco veces, así que un 429 o un 5xx tienen otra oportunidad, mientras que un 422 falla igual cada vez hasta que arreglas los nombres de los inputs.

Después apunta el webhook de Framer a la URL del Worker en lugar de al endpoint, con el secreto rellenado.

Ahora todos los envíos vienen de tu Worker. Las versiones de servidor envían con una clave secreta, así que Ikiform se salta Allowed domains y su verificación humana. Entonces todos los envíos vienen de la IP de tu servidor, y el límite Standard de 5 por minuto por visitante cuenta a todos tus visitantes juntos. Elige el límite de frecuencia Relaxed o Custom para un formulario que envías a través de un servidor, y guarda IKIFORM_SECRET_KEY en variables de entorno solo de servidor.

Opción 3: prescindir del formulario de Framer

Un code component renderiza un formulario real y envía desde el navegador del visitante, así que las comprobaciones de origen, el honeypot y los límites de frecuencia por visitante funcionan como en cualquier sitio. Pierdes la edición visual de formularios de Framer para ese formulario. La guía de Framer de la documentación tiene el componente.

Comprueba que funciona en Framer

  1. Publica el sitio de Framer y envía el formulario ahí, no en la vista previa del editor.
  2. Abre Submissions en el panel. El nuevo envío está ahí con todos los campos que enviaste.
  3. Comprueba que los nombres de campo coinciden exactamente con los nombres de tus inputs, sin claves añadidas por Framer.
  4. Para el relay: envía al Worker una petición con un Framer-Signature inventado y confirma que responde 401.
  5. Para el relay: en los detalles del envío, el origen está vacío y el estado es recibido, no spam.
  6. Revisa la bandeja de entrada de cada destinatario de las notificaciones. Cada uno recibe un email con los campos, y Reply va a quien envió el formulario cuando este tiene un campo email.
  7. Si añadiste un webhook, abre el endpoint en Webhooks. La entrega muestra succeeded con la respuesta de tu servidor.

Errores comunes

ProblemaQué hacer
Todos los envíos del webhook están en spam con origin_mismatchAllowed domains está configurado mientras Framer envía directamente. Vacíalo, o pon el relay en medio.
El Worker responde 401 a los envíos realesEl secreto es distinto entre Framer y el Worker, o algo leyó el cuerpo como texto y lo cambió. Vuelve a pegar el secreto y calcula el hash de los bytes sin procesar.
429 rate_limited durante un lanzamientoTodos los envíos comparten las direcciones de Framer o de Cloudflare. Elige Relaxed, o un límite Custom.
422 validation_failedEl envío no coincide con la definición de campos del formulario. error.fields indica cada campo y el motivo. Haz que el name del input coincida con el id del campo.
403 challenge_requiredLa verificación antispam del formulario está en Ikiform y un envío con fetch no llevaba token. Consigue uno con challenge(formId) de @ikiform/sdk antes de cada envío, o desactiva la verificación.

Preguntas

¿Te atascaste en un paso o quieres una guía para tu caso? Escribe a hello@ikiform.com.