# Your form on your own site

> Put a Zanfia signup form on any website (WordPress, Webflow, plain HTML) with your own markup and styling: the native form contract, the script, the payload and the error codes.

Source: https://zanfia.com/help/developer-api/forms-embed · Updated: 2026-09-23 · Id: developer-api.forms-embed

A Zanfia signup form does not have to live on a Zanfia page or in an iframe. The same native form that Zanfia pages use works on any site you host yourself: you write the HTML and the styles, one script wires the form to your workspace, and the tags, opt-in mode and thank-you redirect come from the form's settings.

### 1. Get the snippet

The quickest way is the CLI, which prints ready-made markup for a form:

```bash
zanfia forms snippet <formId> --lang pl --first-name
```

Or write it by hand. The minimum is a form tagged with your workspace id and the form id, an e-mail field, a submit button and the script:

```html
<form data-zanfia-form="{workspaceId}/{formId}">
  <label>E-mail<br /><input name="email" type="email" required autocomplete="email" /></label>
  <input name="website" type="text" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px;opacity:0" />
  <button type="submit">Sign me up</button>
  <div data-zanfia-success hidden>Thank you! Check your inbox to confirm.</div>
  <div data-zanfia-error hidden>Something went wrong. Please try again.</div>
</form>
<script src="https://zanfia.co/zanfia-page-forms.js" defer></script>
```

Your workspace id comes from `GET /workspace` (or `zanfia workspace get`), the form id from `GET /forms` (or `zanfia forms list`).

### 2. The contract

The script looks for every `form[data-zanfia-form]` on the page and takes over its submit. Everything else about the form is yours: tags, classes, layout, CSS.

| Element                                   | Required | What it does                                                                                                                       |
| ----------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `data-zanfia-form="{workspaceId}/{formId}"` | yes      | Which form receives the signup.                                                                                                    |
| `input[name="email"]`                     | yes      | The address. The form does nothing when it is empty.                                                                               |
| `input[name="firstName"]`                 | no       | Stored on the client.                                                                                                              |
| `input[name="trackingConsent"]` (checkbox) | no       | Engagement-tracking consent. Only a ticked box grants it.                                                                          |
| `input[name="website"]`                   | no       | Honeypot. Keep it visually hidden; a filled value means a bot and nothing is sent.                                                 |
| `button[type="submit"]`                   | yes      | Gets `aria-busy="true"` and is disabled while the request runs.                                                                    |
| `[data-zanfia-success]`                   | no       | Shown after a successful signup when there is no redirect; the fields are disabled and the button hidden.                          |
| `[data-zanfia-error]`                     | no       | Shown when the request fails; the button is re-enabled.                                                                            |
| `data-zanfia-redirect="url"` (on the form) | no       | Sends the visitor to that URL after a successful signup instead of showing the success state. Absent = the form's own `redirectUrl` is NOT applied here; set the attribute when you want a redirect. |

The script posts to the origin it was loaded from (`https://zanfia.co`), never to your domain, so nothing has to be proxied. The endpoint answers the browser's cross-origin preflight and accepts any origin: the call is anonymous, carries no cookies, and the ids in the markup are public anyway. Abuse is limited per IP on the platform side.

### 3. What is sent

`POST https://zanfia.co/api/submitForm` with a JSON body:

```json
{
  "workspaceId": "work_…",
  "formId": "form_…",
  "email": "reader@example.com",
  "firstName": "Anna",
  "trackingConsent": false,
  "language": "pl"
}
```

`language` is the page's `<html lang>` (or the browser language) and picks the language of the confirmation e-mail. A `200` means the signup was recorded. For a double opt-in form the subscription, the tags and the consent apply only after the reader confirms from the e-mail; a single opt-in form subscribes immediately.

Other responses: `400` invalid payload (e-mail format, unknown form), `404` form not found, `429` too many requests from one IP, `5xx` platform error. The script treats anything but `2xx` as an error state.

### 4. After the signup

- **Thank-you page.** Put `data-zanfia-redirect` on the form to send the reader to your own page. The script also stores the address in `sessionStorage` so a second page carrying a `data-zanfia-phone-form` (the SMS reminder step) can attach a phone number to the same signup.
- **Pixels.** On a successful signup the script dispatches a `zanfia:form-submitted` event on `window` with `{ workspaceId, formId }`, so your own tracking can fire a lead.
- **Confirmation e-mail.** The sender name and the subject come from the form's confirmation e-mail settings (`forms update --confirmation-subject …` or the full copy through `confirmationEmailOverride`).

### 5. Webinar sessions

A form attached to a live room with rolling sessions can offer a session picker. Add `data-zanfia-sessions="{workspaceId}/{roomId}"` to the form; the script loads the next sessions from the platform and inserts a select before the submit button.
