Lista de espera para un nuevo SaaS7 min de lectura
Cómo añadir una lista de espera a un sitio de Framer
Dos formas de recoger registros de lista de espera en Framer: el formulario de Framer con un webhook a Ikiform, o un code component que conserva los límites por visitante y el antispam.
Los formularios de Framer no pueden enviar a una URL externa, pero sí pueden mandar cada envío a un webhook, así que la vía sin código es el formulario de Framer con tu endpoint de Ikiform como webhook. Para un lanzamiento público, usa mejor un pequeño code component: el navegador envía directamente, así que el límite de frecuencia por visitante, los dominios permitidos y el honeypot de Ikiform siguen funcionando.
En Framer se crean muchas páginas previas al lanzamiento, porque la página en sí lleva una tarde. El formulario de lista de espera es donde la cosa se complica. El bloque de formulario de Framer no puede cambiar su action, así que eliges entre dos vías que se comportan distinto en cuanto llega tráfico real.
Qué cambia en Framer
- Sin action personalizada. Un formulario de Framer envía a los destinos de Framer: su propio almacenamiento, el email o un Webhook. Un webhook es la única forma de llegar a un backend externo sin código.
- Los webhooks salen de los servidores de Framer, no del visitante. Ikiform ve las direcciones IP de Framer y ninguna cabecera
Origin. Eso desactiva tres protecciones: los dominios permitidos, el límite de frecuencia por visitante y la verificación humana. - Reintentos. Framer trata cualquier cosa que no sea un
2xxcomo un fallo y reintenta hasta cinco veces. No sigue redirecciones. Ikiform responde a un envío JSON con201, así que un registro guardado es un éxito a la primera. - Los code components son React de verdad. Se ejecutan en el navegador del visitante en el sitio publicado, así que un formulario dentro de uno envía como cualquier otra página web.
- Con la vía del webhook, el visitante envía el formulario de Framer y los servidores de Framer reenvían los campos a Ikiform como JSON.
- Ikiform ve a los servidores de Framer como remitente, así que no puede distinguir a los visitantes ni comprobar en qué sitio está el formulario.
- Con el code component, el navegador del visitante envía directamente a Ikiform.
- Ikiform ve la IP de cada visitante y tu dominio, así que se aplican todas las protecciones.
Elige una vía
| Item | Formulario de Framer con webhook | Code component |
|---|---|---|
| Configuración | Dos campos en la barra lateral derecha | Un archivo de código, pegado una vez |
| Estilos | Los estilos y estados del formulario de Framer | Estilos inline que editas, o tus propias clases |
| Límite de frecuencia por visitante | No. Todos los envíos comparten las IP de Framer | Sí, 5 por minuto cada uno |
| Dominios permitidos | Dejar vacío | Con tus dominios de Framer y personalizado |
| Honeypot | No | Sí |
| Verificación humana | Desactivada | Desactivada, porque envía con fetch |
| Seguimiento del origen | Ninguna | ?ref= de la URL, o un valor por defecto por instancia |
La vía del webhook está bien para una página que compartes con unos cientos de personas. Si vas a publicar la página en un sitio grande, usa el code component: una ráfaga de registros de bots a través de los servidores de Framer solo se puede frenar con la puntuación de spam.
Vía 1: el formulario de Framer con un webhook
- Añade a la página un formulario con un input Email. En los ajustes del input, pon
emailcomo nombre. Framer envía el nombre de cada input como clave JSON, e Ikiform usaemailpara la dirección de respuesta y la comprobación de dominios desechables. - Selecciona el formulario. Junto a Send To, elige Add… › Webhook y pega tu endpoint,
https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx. El secreto de firma es opcional; Ikiform no comprueba la firma de Framer. - En Ikiform, deja Allowed domains vacío y la verificación antispam desactivada. Con cualquiera de las dos activa, cada envío de Framer se marca o se rechaza, porque no lleva origen y no puede ejecutar una verificación en el navegador.
- Pon el límite de frecuencia en Relaxed, o en Custom el día del lanzamiento. Standard cuenta todos los envíos de Framer como un solo visitante, y todo el formulario tiene un tope de 300 por minuto.
- Dale estilo al estado de éxito del formulario en Framer. Aparece cuando Ikiform responde
201.
Vía 2: un code component
En el panel Assets, abre Code, crea un archivo de código llamado WaitlistSignup y pega esto. Arrástralo a la página y configura Form ID en el panel de propiedades.
import { addPropertyControls, ControlType } from "framer";
import { type CSSProperties, type FormEvent, useRef, useState } from "react";
const box: CSSProperties = {
font: "inherit",
padding: "12px 14px",
borderRadius: 10,
border: "1px solid rgba(0, 0, 0, 0.15)",
};
interface Props {
formId: string;
buttonLabel: string;
joinedText: string;
source: string;
}
export default function WaitlistSignup({ formId, buttonLabel, joinedText, source }: Props) {
const [status, setStatus] = useState<"idle" | "sending" | "joined">("idle");
const [error, setError] = useState("");
const key = useRef("");
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const body = new FormData(event.currentTarget);
const ref = new URLSearchParams(window.location.search).get("ref");
body.set("ref", (ref || source).slice(0, 40));
key.current ||= crypto.randomUUID();
setStatus("sending");
setError("");
try {
const response = await fetch(`https://ikiform.com/f/${formId}`, {
method: "POST",
headers: { Accept: "application/json", "Idempotency-Key": key.current },
body,
});
const result = await response.json();
if (result.ok) return setStatus("joined");
setError(result.error.fields?.email?.[0] ?? result.error.message);
} catch {
setError("Couldn't reach the server. Try again.");
}
setStatus("idle");
}
if (status === "joined") return <p role="status" style={{ margin: 0 }}>{joinedText}</p>;
return (
<form onSubmit={onSubmit} style={{ display: "flex", flexWrap: "wrap", gap: 8, width: "100%" }}>
<input
name="email"
type="email"
placeholder="you@company.com"
aria-label="Email"
autoComplete="email"
required
style={{ ...box, flex: "1 1 220px" }}
/>
<input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
<button type="submit" disabled={status === "sending"} style={{ ...box, cursor: "pointer" }}>
{status === "sending" ? "Joining" : buttonLabel}
</button>
{error ? <p role="alert" style={{ flexBasis: "100%", margin: 0 }}>{error}</p> : null}
</form>
);
}
addPropertyControls(WaitlistSignup, {
formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
buttonLabel: { type: ControlType.String, title: "Button", defaultValue: "Join the waitlist" },
joinedText: { type: ControlType.String, title: "Joined text", defaultValue: "You're on the list." },
source: { type: ControlType.String, title: "Source", defaultValue: "framer" },
});- El panel de propiedades expone el texto del botón, el texto tras unirse y un Source por defecto. Pon una instancia en el hero con
heroy otra en el footer confooter, y verás cuál convierte. - Un
?ref=en la URL de la página tiene prioridad sobre el valor por defecto, así que los enlaces de campaña se etiquetan solos. - La clave de idempotencia se crea en el primer envío y se reutiliza en los reintentos de la misma visita, así que un doble toque en un teléfono guarda un solo registro.
windowsolo se lee dentro del handler de envío. Framer renderiza primero los componentes en el servidor, y tocarwindowdurante el renderizado lo rompería.
Ajustes de Ikiform para el componente
Añade esto en la pestaña Fields del formulario, para que el email sea obligatorio y ref esté permitido:
{
"version": 1,
"title": "Waitlist",
"fields": [
{ "id": "email", "type": "email", "label": "Email", "required": true },
{ "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
]
}- En Allowed domains, añade
your-project.framer.appy tu dominio personalizado. Nunca añadasframer.appsolo; coincidiría con todos los sitios de Framer. - Deja desactivada la verificación antispam. El componente envía con
fetchy no pide un token, así que un formulario con la verificación activa responde403 challenge_required. - Deja los límites de frecuencia en Standard. Ahora cada visitante se cuenta por separado.
Comprueba que funciona
- Publica el sitio. Los webhooks y los code components solo envían desde el sitio publicado, no desde la vista previa del editor.
- Envía el formulario en tu dominio
framer.appy otra vez en tu dominio personalizado. - Abre Submissions en el panel. El nuevo envío está ahí con todos los campos que mandaste.
- Para el componente, abre la página con
?ref=testy comprueba que el registro muestrarefcomotest. - Revisa la bandeja de entrada de cada destinatario de las notificaciones. Cada uno recibe un email con los campos, y Reply responde a quien envió el formulario cuando este tiene un campo
email. - ¿No llegó nada? Cambia el filtro de estado del panel a Spam. El spam recibe la misma respuesta de éxito que un envío real.
Problemas habituales
| Problema | Qué hacer |
|---|---|
Todos los registros del webhook están en spam con origin_mismatch | Allowed domains está configurado, y los envíos desde el servidor de Framer no tienen origen. Vacíalo para este formulario, o cambia al code component. |
| Framer informa de entregas de webhook fallidas | Ikiform rechazó el envío: 403 challenge_required con la verificación antispam activa, o 422 porque el nombre de un input no es email. Corrige la causa; los reintentos fallan igual. |
429 rate_limited | Un visitante envió más de lo que permite el límite de frecuencia del formulario. Standard permite 5 por minuto por visitante. Espera lo que indique Retry-After, o elige Relaxed o Custom para formularios que envían desde una IP compartida. |
El componente muestra Unknown field "ref" | La definición de campos no incluye ref. Añádelo como en la definición de arriba. |
403 origin_not_allowed, o todos los envíos acaban en spam | El dominio de la página no está en los Allowed domains del formulario. Añádelo, y también localhost para las pruebas locales. Los subdominios de un dominio de la lista están permitidos. |
La configuración completa de Framer, incluida la versión de formulario de contacto, está en la guía de Framer de la documentación. Exportar la lista y enviar el email de lanzamiento están en la guía principal.
Preguntas
No como action del formulario. Los formularios de Framer envían a los destinos propios de Framer, y uno de ellos es un webhook a cualquier URL https. Úsalo para Ikiform, o renderiza tu propio formulario con un code component.
Con la vía del webhook, Framer envía desde sus servidores sin cabecera Origin, así que un formulario con Allowed domains configurado marca cada envío como origin_mismatch. Deja Allowed domains vacío en los formularios con webhook.
Framer dice que los envíos de formularios son ilimitados, así que los registros por webhook no chocan con ningún tope de Framer. La vía del code component no usa los formularios de Framer en absoluto.
No, no hay plan gratuito. Probar es gratis: puedes enviar 50 envíos de prueba al mes desde localhost antes de pagar. Para pasar a producción, Pro empieza en 6 $/mes por 500 envíos, o puedes pagar una sola vez 10.000 envíos por 49 $ o 50.000 envíos por 149 $, con créditos que nunca caducan.
¿Te atascaste en un paso o quieres una guía para tu caso? Escribe a hello@ikiform.com.