# 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.

Publicado el 2026-10-25 · https://ikiform.com/es/guides/waitlist/framer

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 `2xx` como un fallo y reintenta hasta cinco veces. No sigue redirecciones. Ikiform responde a un envío JSON con `201`, 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.

Diagrama: Dos formas en que un registro de lista de espera en Framer llega a Ikiform

1. 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.
2. 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.
3. Con el code component, el navegador del visitante envía directamente a Ikiform.
4. Ikiform ve la IP de cada visitante y tu dominio, así que se aplican todas las protecciones.

Ambas vías guardan el mismo registro. Se diferencian en quién cree Ikiform que lo envió.

## Elige una vía

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

1. Añade a la página un formulario con un input **Email**. En los ajustes del input, pon `email` como nombre. Framer envía el nombre de cada input como clave JSON, e Ikiform usa `email` para la dirección de respuesta y la comprobación de dominios desechables.
2. 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.
3. 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.
4. Pon el [límite de frecuencia](https://ikiform.com/es/docs/rate-limits.md) 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.
5. Dale estilo al estado de éxito del formulario en Framer. Aparece cuando Ikiform responde `201`.

> Un envío que Ikiform rechaza, como uno sin email cuando hay una definición de campos, falla igual en los cinco reintentos de Framer. Revisa la entrega en Framer si dejan de llegar registros.

## 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.

`WaitlistSignup.tsx`:

```tsx
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 `hero` y otra en el footer con `footer`, 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.
- `window` solo se lee dentro del handler de envío. Framer renderiza primero los componentes en el servidor, y tocar `window` durante 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:

`Campos`:

```json
{
  "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.app` y tu dominio personalizado. Nunca añadas `framer.app` solo; coincidiría con todos los sitios de Framer.
- Deja desactivada la verificación antispam. El componente envía con `fetch` y no pide un token, así que un formulario con la verificación activa responde `403 challenge_required`.
- Deja los límites de frecuencia en Standard. Ahora cada visitante se cuenta por separado.

## Comprueba que funciona

1. Publica el sitio. Los webhooks y los code components solo envían desde el sitio publicado, no desde la vista previa del editor.
2. Envía el formulario en tu dominio `framer.app` y otra vez en tu dominio personalizado.
3. Abre **Submissions** en el panel. El nuevo envío está ahí con todos los campos que mandaste.
4. Para el componente, abre la página con `?ref=test` y comprueba que el registro muestra `ref` como `test`.
5. 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`.
6. ¿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](https://ikiform.com/es/docs/guides/framer.md). Exportar la lista y enviar el email de lanzamiento están en la [guía principal](https://ikiform.com/es/guides/waitlist.md#launch-day).

## Preguntas

### ¿Un formulario de Framer puede enviar a una URL externa?

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.

### ¿Por qué mis registros de lista de espera en Framer se marcan como spam?

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.

### ¿Los registros por webhook de Framer cuentan para los límites de formularios de Framer?

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.

### ¿Ikiform es gratis?

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.

## Más guías

- [Lista de espera para un nuevo SaaS](https://ikiform.com/es/guides/waitlist.md): Crea una lista de espera previa al lanzamiento en tu propia landing page: el formulario, el antispam, los duplicados, los emails de confirmación y una exportación limpia el día del lanzamiento.
- [Lista de espera en Webflow](https://ikiform.com/es/guides/waitlist/webflow.md): Apunta un Form Block de Webflow a Ikiform, etiqueta los registros por origen con un campo oculto y conserva el estado de éxito de Webflow con un script breve para todos los formularios.
- [Lista de espera en Carrd](https://ikiform.com/es/guides/waitlist/carrd.md): Recoge registros de lista de espera en una página de Carrd: un formulario Send to URL en Pro Plus con seguimiento UTM, o un formulario HTML incrustado con honeypot en Pro Standard.
- [Lista de espera en HTML puro](https://ikiform.com/es/guides/waitlist/html.md): Una página de lista de espera completa en HTML puro y sin build que funciona con JavaScript desactivado, muestra un agradecimiento en línea cuando carga y recurre a un plan B cuando hace falta.
- [Lista para lanzar en Product Hunt](https://ikiform.com/es/guides/waitlist/product-hunt.md): Reúne una lista semanas antes de Product Hunt, etiqueta cada registro por canal y envía el email del lanzamiento en dos tandas según el reinicio en hora del Pacífico.
- [Registro a una beta con filtro](https://ikiform.com/es/guides/waitlist/beta-signup.md): Un formulario de solicitud para una beta con preguntas de filtro que aplica el servidor, y un script que puntúa a los candidatos y elige cada grupo de la lista.
