Form ID

Framer

Send a Framer form to Ikiform through its webhook destination with no code, or render your own form with a code component that posts from the browser.

Framer forms can't post to an outside action URL, but they can send each submission to a webhook. That gives two ways in: keep Framer's form and add Ikiform as its webhook, or drop in a small code component that posts from the visitor's browser.

ItemFramer form with a webhookCode component
Who sends the postFramer's serversThe visitor's browser
CodeNoneOne React file
Allowed domainsLeave empty: server posts carry no OriginChecked against your Framer domain
HoneypotNot availableIncluded
Rate limits countFramer's server addressesEach visitor
Ikiform spam checkLeave offLeave off

Framer form with a webhook

  1. Name the inputs

    Framer sends JSON with each input's name as the key, so the names you give the inputs become the field names in Ikiform. Use lowercase ids like email and message, and match your field definition if you have one.

  2. Add Ikiform as the webhook

    Select the form on the canvas. Next to Send To in the right sidebar, choose Add… > Webhook and paste your endpoint. Framer requires an https:// URL, which the endpoint already is.

    Webhook URL
    https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx

    Framer can sign requests with a secret. Ikiform doesn't check the Framer-Signature header, so the secret is optional here.

  3. Adjust the Ikiform form settings

    • Leave Allowed domains empty. Framer posts from its servers without an Origin header, so with domains set every post is marked spam with origin_mismatch, or refused with Reject.
    • Leave the spam check off. A JSON post without a token gets 403 challenge_required, and Framer has no way to run the check.
    • Choose the Relaxed rate limit. Ikiform counts posts per IP address and form, and every post comes from Framer's servers instead of your visitors.

Framer treats any response other than 2xx as a failure and retries up to 5 times. Ikiform answers 201 once the submission is stored. A post Ikiform rejects, such as 422 validation_failed, fails the same way on every retry, so fix the cause instead of waiting.

Code component

A code component renders a real form on your page, so the browser posts it and Ikiform sees your site as the origin. In the Assets panel, open Code, create a code file, and paste:

IkiformContact.tsx
import { addPropertyControls, ControlType } from "framer";
import { type CSSProperties, type FormEvent, useState } from "react";

const field: CSSProperties = {
  font: "inherit",
  padding: "10px 12px",
  borderRadius: 8,
  border: "1px solid rgba(0, 0, 0, 0.15)",
};

export default function IkiformContact({ formId, successText }: { formId: string; successText: string }) {
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
  const [message, setMessage] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    try {
      const response = await fetch(`https://ikiform.com/f/${formId}`, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(event.currentTarget),
      });
      const result = await response.json();
      if (result.ok) return setStatus("sent");
      setMessage(result.error.message);
    } catch {
      setMessage("Couldn't reach the server. Try again.");
    }
    setStatus("error");
  }

  if (status === "sent") return <p>{successText}</p>;

  return (
    <form onSubmit={onSubmit} style={{ display: "grid", gap: 12, width: "100%" }}>
      <input name="email" type="email" placeholder="Email" autoComplete="email" required style={field} />
      <textarea name="message" placeholder="Message" rows={4} style={field} />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {status === "error" ? <p role="alert">{message}</p> : null}
      <button type="submit" disabled={status === "sending"} style={field}>
        {status === "sending" ? "Sending" : "Send"}
      </button>
    </form>
  );
}

addPropertyControls(IkiformContact, {
  formId: { type: ControlType.String, title: "Form ID", defaultValue: "frm_xxxxxxxxxxxxxxxx" },
  successText: { type: ControlType.String, title: "Success text", defaultValue: "Thanks, we got it." },
});
  • Drag the component onto the page and set Form ID in the properties panel. Style the inputs by editing field, or replace the elements with your own.
  • Accept: application/json makes Ikiform answer with JSON, so the component can show the result in place.
  • The component posts with fetch, so leave the form's spam check off. The honeypot and spam scoring still run.
You can also paste the site builder embed into an Embed component's HTML section. If Framer places it in a frame, Ikiform's thank-you and error pages won't load inside it, which is why the embed uses target="_top". The code component avoids frames altogether.

Allowed domains

This only applies to the code component and the embed. Leave allowed domains empty for the webhook.

Where the form runsAdd to Allowed domains
Framer subdomainyour-project.framer.app, or your-project.framer.website on older projects
Custom domainexample.com, which also covers www.example.com
Framer's editor previewNothing. Test on the published site instead.
  • Never add framer.app or framer.website on its own. Every site on that host would match, including other people's.

Test checklist

  1. Publish the site. Framer only sends webhooks for forms on the published site.
  2. Submit the form and check the submission in Ikiform. With the webhook, the fields should match your input names exactly.
  3. For the code component, turn on allowed domains, submit from the published domain, and confirm it isn't marked spam.

Troubleshooting

What you seeWhyFix
Every webhook submission is under spam with origin_mismatchAllowed domains is set, and Framer's server posts have no originClear allowed domains for this form, or switch to the code component.
Framer reports failed webhook deliveriesIkiform refused the post: 403 challenge_required, 403 origin_not_allowed or 422 validation_failedTurn the spam check off, clear allowed domains, and match input names to your fields.
429 rate_limited on a busy formPosts from Framer's servers share a small number of IP addressesChoose Relaxed, or a Custom limit.
The component says it couldn't reach the serverAn ad blocker or privacy extension blocked the request, or the Form ID is emptyTry a private window and check the property is set.
Submissions arrive under the spam filter with origin_mismatchThe page that posted isn't on the form's allowed domainsAdd the host from the table above. With Reject chosen, the same posts get 403 origin_not_allowed instead.