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.
| Item | Framer form with a webhook | Code component |
|---|---|---|
| Who sends the post | Framer's servers | The visitor's browser |
| Code | None | One React file |
| Allowed domains | Leave empty: server posts carry no Origin | Checked against your Framer domain |
| Honeypot | Not available | Included |
| Rate limits count | Framer's server addresses | Each visitor |
| Ikiform spam check | Leave off | Leave off |
Framer form with a webhook
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
emailandmessage, and match your field definition if you have one.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_xxxxxxxxxxxxxxxxFramer can sign requests with a secret. Ikiform doesn't check the
Framer-Signatureheader, so the secret is optional here.Adjust the Ikiform form settings
- Leave Allowed domains empty. Framer posts from its servers without an
Originheader, so with domains set every post is marked spam withorigin_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.
- Leave Allowed domains empty. Framer posts from its servers without an
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:
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/jsonmakes 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.
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 runs | Add to Allowed domains |
|---|---|
| Framer subdomain | your-project.framer.app, or your-project.framer.website on older projects |
| Custom domain | example.com, which also covers www.example.com |
| Framer's editor preview | Nothing. Test on the published site instead. |
- Never add
framer.apporframer.websiteon its own. Every site on that host would match, including other people's.
Test checklist
- Publish the site. Framer only sends webhooks for forms on the published site.
- Submit the form and check the submission in Ikiform. With the webhook, the fields should match your input names exactly.
- For the code component, turn on allowed domains, submit from the published domain, and confirm it isn't marked spam.
Troubleshooting
| What you see | Why | Fix |
|---|---|---|
Every webhook submission is under spam with origin_mismatch | Allowed domains is set, and Framer's server posts have no origin | Clear allowed domains for this form, or switch to the code component. |
| Framer reports failed webhook deliveries | Ikiform refused the post: 403 challenge_required, 403 origin_not_allowed or 422 validation_failed | Turn the spam check off, clear allowed domains, and match input names to your fields. |
429 rate_limited on a busy form | Posts from Framer's servers share a small number of IP addresses | Choose Relaxed, or a Custom limit. |
| The component says it couldn't reach the server | An ad blocker or privacy extension blocked the request, or the Form ID is empty | Try a private window and check the property is set. |
Submissions arrive under the spam filter with origin_mismatch | The page that posted isn't on the form's allowed domains | Add the host from the table above. With Reject chosen, the same posts get 403 origin_not_allowed instead. |