LPLeadProof

Framer form CRM delivery

Turn every signed Framer form submission into a verified CRM receipt.

Verify Framer's signed webhook, preserve the submission ID through retries, and deliver only approved form fields to the CRM with duplicate protection.

Protect 25 live leads free →Start Builder · $49/monthAudit this workflow
Paid plan uses secure Stripe checkout · plan assigned after verified payment · activate with the checkout email
THE FAILURE GAP

A successful automation run is not proof of delivery.

Framer retries a form webhook up to five times when the destination does not return a direct 2xx response, but successful webhook receipt still does not prove the CRM stored the lead. A production bridge should verify Framer-Signature against the raw body plus Framer-Webhook-Submission-Id, acknowledge the form promptly, and keep that submission ID stable through downstream recovery.

Framer form -> signed mapping handler -> durable queue -> LeadProof -> CRM
01

Form success is not CRM acceptance

The visitor can finish the form while a later CRM request times out, rejects the data, or disappears between systems.

02

Framer retries can create duplicates

A non-2xx response can trigger up to five retries, so every attempt needs the original submission ID as one business event.

03

Arbitrary form fields need an allowlist

Framer sends input names as JSON keys; only the fields explicitly authorized for the destination should enter the delivery job.

Implementation

Put LeadProof in the delivery path.

Keep the tools that create and process the lead. Standardize only the fragile handoff between them.

Platform reference: Framer form webhook documentationOpen sandbox instructions →
  1. Set the Framer form destination to a direct HTTPS webhook, create a secret of at least 32 characters, and name the approved contact inputs consistently.
  2. Read the untouched request body and Framer-Webhook-Submission-Id, then verify the sha256-prefixed HMAC in Framer-Signature before parsing.
  3. Select only the approved name, email, and phone keys, enqueue the compact job with the submission ID, and return a direct 2xx response promptly.
  4. Let the worker send the mapped lead through LeadProof with the Framer submission ID as the Idempotency-Key, then retain the receipt ID.
POSThttps://leadproof.jessesay.chatgpt.site/api/v1/leads
import crypto from "node:crypto";

app.use(
  "/framer/form",
  express.raw({ type: "application/json" })
);

app.post("/framer/form", async (req, res) => {
  const signature = req.headers["framer-signature"];
  const submissionId = req.headers["framer-webhook-submission-id"];
  const secret = process.env.FRAMER_WEBHOOK_SECRET;
  if (
    typeof signature !== "string" ||
    typeof submissionId !== "string" ||
    !secret ||
    !/^sha256=[0-9a-f]{64}$/i.test(signature)
  ) {
    return res.sendStatus(401);
  }

  const expected = crypto
    .createHmac("sha256", secret)
    .update(req.body)
    .update(submissionId)
    .digest();
  const supplied = Buffer.from(signature.slice(7), "hex");
  const authentic =
    supplied.length === expected.length &&
    crypto.timingSafeEqual(supplied, expected);
  if (!authentic) return res.sendStatus(401);

  const form = JSON.parse(req.body.toString("utf-8"));
  const lead = {
    name: form.name,
    email: form.email,
    phone: form.phone
  };

  if (lead.name && lead.email) {
    await deliveryQueue.send({ submissionId, lead });
  }
  return res.sendStatus(204);
});

export async function deliverFramerLead({ submissionId, lead }) {
  const response = await fetch(
    "https://leadproof.jessesay.chatgpt.site/api/v1/leads",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LEADPROOF_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": submissionId
      },
      body: JSON.stringify({
        destination: process.env.CRM_WEBHOOK_URL,
        ...lead,
        source: "framer_form"
      })
    }
  );

  if (!response.ok) throw new Error("Lead delivery failed");
  const delivery = await response.json();
    await persistReceipt(submissionId, delivery.receipt?.id);
}

Questions

What teams ask before adding LeadProof.

Why use Framer-Webhook-Submission-Id for idempotency?

Framer defines it as the unique identity for the form submission, so retries should keep that value instead of creating a new event key.

What does the Framer signature cover?

Framer documents an HMAC-SHA256 over the raw form payload followed by the submission ID, formatted as sha256 plus the hexadecimal digest.

Can the webhook redirect to another endpoint?

No. Framer documents that webhook redirects are not followed; the configured destination must return a direct 2xx response.

FREE · NO SIGNUP

See the gaps in your real workflow.

Get an explainable risk score, prioritized fixes, and the right LeadProof plan.

Protect 25 live leads free →Run the reliability auditStart Builder · $49/month