Guides / Contact formsMenu

Contact forms

Send your website's own contact form to the inbox and answer every message by e-mail.

Updated · Markdown

Your website keeps its own form, with its own look. You only change where it sends: to a Luxivo address like https://luxivo.app/f/fk_…. Every message lands in the inbox as a conversation, your team is notified, and you answer by e-mail from the inbox. When the customer replies, the answer comes back into the same conversation.

Create a form in Contact forms, pick the website it belongs to, and copy the code from its Install section. A form accepts messages from the same websites as that website’s widget (Allowed websites under the widget’s General). The Free plan includes one form, Starter three, Pro ten and Business any number.

Plain HTML

Point the form’s action at your form’s address. After sending, the browser comes back to the page in _redirect.

<form action="https://luxivo.app/f/fk_your_key" method="post">
  <input name="name" required>
  <input name="email" type="email" required>
  <textarea name="message" required></textarea>
  <input type="hidden" name="_redirect" value="https://example.com/contact?sent=1">
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden>
  <button>Send</button>
</form>

Without _redirect, the browser lands on a short thank-you page in your widget’s language. You can also set a default page under the form’s Settings. Redirects only go to your allowed websites.

JavaScript

Send the form with fetch and show the result on the page. Ask for JSON with the Accept header:

const res = await fetch(form.action, {
  method: "POST",
  body: new FormData(form),
  headers: { Accept: "application/json" },
});
const { ok, error } = await res.json();

ok is true when the message was saved. Otherwise error says what’s wrong in one sentence, and errors lists every problem. JSON bodies (Content-Type: application/json) work too.

Fields

Use any field names. Luxivo recognizes the common ones and fills the conversation from them:

What Names
E-mail email, e-mail, mail
Name name, full_name, or first_name and last_name
Phone phone, tel, telephone
Message message, msg, comments
Subject subject, _subject, topic

Polish names (imie, nazwisko, telefon, wiadomosc, temat) work as well. Every other field is kept too, like a hidden listing_id or a select. A message needs an e-mail address or a phone number. Checkboxes that are ticked show as “Yes”, and fields with the same name are joined.

Under the form’s Fields you can give fields labels, put them in order and make them required. Give a checkbox a consent text, and the text is saved with every message where the box is ticked.

Page and campaign

Two hidden fields tell you where the message came from. Fill them when the page loads:

<input type="hidden" name="_page">
<input type="hidden" name="_referrer">
<script>
  document.querySelector('[name="_page"]').value = location.href;
  document.querySelector('[name="_referrer"]').value = document.referrer;
</script>

The inbox then shows the page and its utm_ campaign tags next to the message.

Spam

The hidden _gotcha field is a trap for bots: people never see it, so a message with it filled in is dropped without an error. If your form already has a trap field, enter its name under Settings. Each visitor can send up to 10 messages a minute, and only from your allowed websites.

Newsletter signup

Under Settings → Newsletter signup, pick a newsletter list. Senders who tick a checkbox named newsletter join it, with the list’s e-mail confirmation if it has one. Describe the checkbox in Fields with its consent text, and that text is stored as what they agreed to. See Newsletter.

<label><input type="checkbox" name="newsletter"> Send me the newsletter</label>

Notifications and the automatic reply

Your team is notified like for any new conversation. Under Notifications you can also:

  • send every message to addresses outside the team, such as an office mailbox,
  • send messages to more people depending on a field, for example agent equals anna goes to Anna,
  • answer the customer right away with an automatic reply. Use {{name}}, {{email}}, {{message}} or any field as {{field_name}} in it.

Replies and automatic replies go out with your website’s sender name, logo, color and footer. Set them in the widget’s E-mails section, where you can preview them and send yourself a test.

Replying

Form conversations are answered by e-mail. The reply box shows who it goes to and the subject. Enter starts a new line, and Cmd+Enter (Ctrl+Enter on Windows) sends. Each reply carries your name and company, the customer’s message quoted below and any files you attach. The AI button writes a draft, or improves, shortens, changes the tone of or translates what you wrote.

Export and webhooks

Messages lists everything sent with the form and exports it to CSV.

On Pro and Business, a webhook posts every message as JSON to your URL, for Zapier, Make or a CRM:

{
  "id": "…",
  "form": { "id": "…", "name": "Contact" },
  "createdAt": "2026-10-27T16:43:00.000Z",
  "email": "anna@example.com",
  "name": "Anna Nowak",
  "data": { "email": "anna@example.com", "message": "Is the flat still available?" },
  "fields": [{ "name": "email", "label": "E-mail", "value": "anna@example.com" }],
  "page": { "url": "https://example.com/contact", "utm": { "utm_source": "google" } },
  "conversationUrl": "https://luxivo.app/app/inbox?c=…"
}

Each request has an X-Luxivo-Signature: t=<unix seconds>,v1=<hex> header. v1 is the HMAC-SHA256 of <t>.<raw body> with the form’s webhook secret. Check it, and reject old timestamps:

import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Answer with any 2xx status. Other answers are tried again five times over about an hour, and the form’s Messages show the last status. Send a test posts a sample message.