Contact widget
Replace the generic contact form on your website with a Zooza contact form. Every enquiry becomes a contact in Zooza, together with the page it was sent from and the campaign that brought the visitor, so your team can follow it up.
What the form contains — its fields, consents and what happens after it is sent — is set up in the Zooza app, in a contact form configuration. The embed code only decides where the form appears and, optionally, which configuration it shows. Changes you make to a configuration show up on your website straight away, without touching the embed code.
Installation
WordPress
The [zooza] shortcode does not support the contact widget. Paste the embed code into a Custom HTML block on the page instead.
Embed code
Embed this widget with a placeholder element and the Zooza loader. The loader can sit in the <body> right after the placeholder, or in the <head> so the widget starts loading earlier. See Choosing an embed method if you are not sure which one to use.
| Placeholder | Description | Example Value |
|---|---|---|
YOUR_API_KEY | Replace with the API key found in the application under Publish > Widget. | abc123xyz |
ZOOZA_API_URL | Replace with the Zooza API URL for your region: Europe: https://api.zooza.app, UK: https://uk.api.zooza.app, UAE: https://asia.api.zooza.app | https://api.zooza.app |
- Body only
- Head + body
Place the placeholder and the loader in the <body> of your page, where you want the form to appear.
<div data-zooza-widget='contact' data-zooza-id='YOUR_API_KEY'></div>
<script async src='ZOOZA_API_URL/widgets/v1/loader.js'></script>
Place the loader in the <head> of your page:
<script async src='ZOOZA_API_URL/widgets/v1/loader.js'></script>
Then place the placeholder in the <body>, where you want the form to appear:
<div data-zooza-widget='contact' data-zooza-id='YOUR_API_KEY'></div>
This embed shows the configuration selected for the widget in the app, or your company's default one. To show a specific configuration on a page, add data-zooza-config-id to the placeholder. Initialisation options and hidden field values are also set directly on the placeholder as data-zooza-* attributes.
The loader works out the API host for your region from its own src. In the rare case you need a different host, set it with data-zooza-api-url on the placeholder.
The contact widget has no legacy script snippet. Use the placeholder and loader shown above.
Place only one contact placeholder on a page. If a page contains more than one, the first renders the form and every other one shows the notice "Only one contact form can be shown per page." along with a warning in the browser console.
Which form is shown
The widget decides which configuration to render in this order:
- The
config_idset in the embed code, for exampledata-zooza-config-id='12' - The Contact form configuration selected in
Publish > Widget > Contact form - Your company's default configuration
Every company has a default configuration. It can be edited, but not archived, so a contact placeholder without a config_id always has a form to show.
If config_id points to a configuration that does not exist or has been archived, the widget shows an error instead of falling back to the default. Archived configurations also stop accepting enquiries.
Open the configuration in Settings > Contact forms. Its Embed code card gives you the snippet with the configuration's id and its hidden field attributes already filled in.
Settings
These settings are managed within the Zooza's main application Publish > Widget > Contact form.
URL
The page on your website where the contact form is placed. Its domain is automatically allowed to load the form (see Additional domains).
Contact form configuration
The configuration this widget shows when the embed code does not set a config_id. Leave it on Company default to show your default configuration. A configuration set in the embed code always takes precedence.
Additional domains
The contact form only loads on websites you have allowed. These are always allowed:
- the domain of your widget
- the domain of the URL set above
Add every other website where the form should load. Enter the domain without https:// or a path, for example example.com. Subdomains such as www.example.com are covered automatically.
On any other domain the form does not load and the widget shows an error instead. The configuration in Settings > Contact forms shows where the form was last loaded and where it was last blocked, so you can spot a missing domain.
A staging site on a subdomain of an allowed domain, such as staging.example.com, is already covered. A staging site on a different domain, or a local development copy, is not — add its domain (for example example.netlify.app or localhost) if you want to test the contact form there.
Use CSS
This loads the default Zooza styling. By default this is turned on. The default styling is deliberately minimal: the form inherits your website's font and colours, and any CSS rule on your website overrides it. See Styling.
You can download the default css from this URL:
ZOOZA_API_URL/widgets/v1/css/?widget=YOUR_API_KEY&type=contact
Configuring the form
Contact form configurations are managed in Settings > Contact forms. Each configuration defines:
| Part | What it controls |
|---|---|
| Standard fields | First name, last name, email, phone and message — each can be turned on and made required |
| Custom fields | Your own fields, in the order you choose (see the types below) |
| Hidden fields | Custom fields the visitor does not see, filled in automatically (see Hidden fields) |
| Consents | Which of your contact form consents the visitor is asked to accept. Zooza's platform consents are always included |
| After submit | Show a message, or go to a web page |
| Attribution | Whether to remember where the visitor first came from (see Attribution) |
| Handling | Contact owner, labels, extra notification emails, a to-do for each enquiry and an automatic reply to the visitor |
Custom fields are rendered according to their type:
| Type | Shown as |
|---|---|
text | Single-line text input |
long_text | Multi-line text area |
number | Number input |
date | Date input |
boolean | Single checkbox |
select | Drop-down list — one option |
multiselect | Checkbox list — any number of options |
Custom field labels and options, consent texts and the success message are shown exactly as they are written in the configuration. If the success message is left empty, the widget shows a translated "Thank you, we've received your message." instead. Standard field labels, buttons and validation messages come from the widget's own texts, which you can change with translations.
The email field suggests a correction for common typos — a visitor who types name@gmial.com is offered "Did you mean name@gmail.com?" with one click. Phone is a single input, with no separate country picker.
Required fields and mandatory consents are checked in the browser before the form is sent, and again by Zooza. Errors appear next to the field concerned so the visitor can fix them and send the form again.
Hidden fields
A hidden field is a custom field that is not shown to the visitor. Its value is filled in automatically from the value source chosen for it in the configuration:
| Value source | Where the value comes from | Example |
|---|---|---|
| URL parameter | A query parameter of the page URL | Source name ref reads https://example.com/contact?ref=newsletter |
| Cookie | A cookie on your website | Source name partner_id reads the partner_id cookie |
| Embed code attribute | A data-zooza-field-<source name> attribute on the placeholder | Source name location reads data-zooza-field-location |
| Fixed value | The value entered in the configuration | Always website |
The embed code attribute lets you reuse one configuration on several pages and still tell the enquiries apart. For example, with a hidden field whose source name is location:
<!-- on the London page -->
<div data-zooza-widget='contact'
data-zooza-id='YOUR_API_KEY'
data-zooza-config-id='12'
data-zooza-field-location='london'></div>
<!-- on the Paris page -->
<div data-zooza-widget='contact'
data-zooza-id='YOUR_API_KEY'
data-zooza-config-id='12'
data-zooza-field-location='paris'></div>
- Source names use lowercase letters, numbers, hyphens and underscores only, up to 40 characters. In the attribute name, hyphens and underscores are interchangeable: source name
promo_codeis read fromdata-zooza-field-promo-code. - A cookie can only be read if it is set on your website's domain and is not
HttpOnly. - If the source has no value — the URL parameter is missing, the cookie is not set — the field is sent empty.
Embed code attributes and URL parameters only supply values for hidden fields that the configuration already defines. They cannot add fields, change which fields are required, change consents, or change where the visitor is sent after submitting. Those always come from the configuration in the Zooza app.
Attribution
Every enquiry records where it came from:
| Value | Source |
|---|---|
page_url | The address of the page the form was sent from |
referrer | The page the visitor came from, as reported by the browser |
utm_source, utm_medium, utm_campaign, utm_term, utm_content | UTM parameters in the page URL |
fbclid | Meta (Facebook) click id in the page URL |
gclid | Google Ads click id in the page URL |
By default, campaign parameters are read from the URL of the page where the form is sent. Nothing is stored in the visitor's browser. This covers the common case where a campaign link leads straight to the page with the form.
Remembering where the visitor first came from
When a visitor arrives on one page and sends the form on another, the campaign parameters are no longer in the URL. Turn on Remember where the visitor first came from in the configuration to keep them:
- The first time the widget loads with campaign parameters in the URL, it stores them in the visitor's
localStorageunder the keyzooza_contact_first_touch. Only the campaign parameters listed above are stored — no personal data, page URL or referrer. - Later visits with different campaign parameters do not overwrite them. The first campaign wins.
- They are kept for the number of days set in Remember for (days): from 1 to 90, 30 by default. After that, the next campaign parameters replace them.
Campaign parameters are captured only on pages where the contact widget is embedded. If your campaign links point to a page without the form, add the form to that page, or link to the page with the form.
Storing campaign parameters in the visitor's browser is generally treated as marketing tracking. Turn this option on only if your website asks visitors for consent to it, for example through your cookie consent manager.
Spam protection
The contact widget works with Zooza's invisible spam protection. There is no CAPTCHA and nothing for you to set up.
- Sending can take a moment. A form sent very quickly after the page loads may pause briefly before it goes through. This is expected.
- Don't reveal hidden elements. The form contains elements that are hidden on purpose. Don't add CSS or scripts that make hidden inputs inside the widget visible.
- Test with realistic enquiries. An enquiry Zooza classifies as spam gets the same success message and analytics events as a genuine one, but never appears among your contacts. If a test enquiry is missing, send one that reads like a real message.
- Page caching is fine. The widget fetches everything it needs each time the page loads.
Initialisation options
config_id
Type: Number
The id of the contact form configuration to show. When it is not set, the widget shows the configuration selected in the app or your company's default — see Which form is shown.
| Value | Description | Example Value |
|---|---|---|
CONFIG_ID | Id of a contact form configuration from Settings > Contact forms | 12 |
- Data attribute
- JavaScript
- URL Query
<div data-zooza-widget='contact'
data-zooza-id='YOUR_API_KEY'
data-zooza-config-id='CONFIG_ID'></div>
<script>
window.ZOOZA = {
config_id: CONFIG_ID
}
</script>
https://sample-site.com/contact?config_id=CONFIG_ID
lang
Type: String (BCP 47 locale code)
Language of the widget is defined by your Zooza account. You can change this language in Settings > General > Language. However, you can override widget's language by setting its lang property to one of supported languages.
The language the form is displayed in is also sent with the enquiry, so the automatic reply to the visitor is sent in the same language.
| Value | Description | Example Value |
|---|---|---|
LANGUAGE_CODE | Language code of your choice | en-EN |
- Data attribute
- URL Query
- HTML
<div data-zooza-widget='contact'
data-zooza-id='YOUR_API_KEY'
data-zooza-lang='en-EN'></div>
https://sample-site.com/contact?lang=LANGUAGE_CODE
<html lang="LANGUAGE_CODE">
</html>
The widget's language is determined in this order:
- URL Query
langparameter langoption, set on the placeholder or inwindow.ZOOZAlangattribute ofhtmltag- language set in the Zooza
translations
Type: Object
This option takes an object of custom strings. It cannot be set as a data-zooza-* attribute on the placeholder — HTML attributes are strings. Use a <script> block alongside the placeholder.
Replaces the widget's own texts: buttons, standard field labels and validation messages. Custom field labels, options, consent texts and the success message are written in the configuration, so change them there instead.
| Key | Default text |
|---|---|
contact.submit | Send |
contact.submitting | Sending… |
contact.success_default | Thank you, we've received your message. |
contact.error_required | This field is required. |
contact.error_invalid_email | Please enter a valid email address. |
contact.error_invalid_phone | Please enter a valid phone number. |
contact.error_invalid_number | Please enter a valid number. |
contact.error_invalid_date | Please enter a valid date. |
contact.error_consent_required | Please accept this to continue. |
contact.error_generic | Something went wrong. Please try again. |
contact.email_did_you_mean | Did you mean %1? |
contact.duplicate_notice | Only one contact form can be shown per page. |
global.first_name | First name label |
global.last_name | Last name label |
global.email | Email label |
global.phone | Phone label |
global.note | Message label |
<script>
window.ZOOZA = {
translations: {
'contact.submit' : 'Send enquiry',
'global.note' : 'How can we help?',
}
}
</script>
Enable print_debug and open your browser's console. Every text the widget displays is printed as Text Key: contact.submit Translation: Send.
print_debug
Type: Bool
Prints additional debug information to the browser's console. It is especially useful for finding translation keys.
- Data attribute
- JavaScript
<div data-zooza-widget='contact'
data-zooza-id='YOUR_API_KEY'
data-zooza-print-debug='true'></div>
<script>
window.ZOOZA = {
print_debug: true|false
}
</script>
Events and callbacks
The contact widget has no JavaScript callbacks or custom DOM events. To react to the form being shown or sent, use the analytics events, which the widget pushes to window.dataLayer whenever the page has one.
Styling
The contact widget renders directly into your page (no iframe), and its default styling is built to be overridden:
- Every default rule has zero specificity, so any rule in your stylesheet wins — no
!importantneeded. - By default the form inherits the font and text colour of the surrounding page.
- The form is at most
660pxwide and centred in the placeholder.
Theme variables
The quickest way to match your brand is to set CSS custom properties on .zooza-contact-widget, and style the submit button through its class:
.zooza-contact-widget {
--zooza-accent: #FA6900;
--zooza-border-color: #d0d0d0;
--zooza-radius: 5px;
--zooza-font-family: 'DM Sans', sans-serif;
--zooza-max-width: 100%;
}
.zooza-contact-button__primary {
background: #FA6900;
color: #fff;
}
| Variable | Default | Controls |
|---|---|---|
--zooza-font-family | inherited | Font of the whole form |
--zooza-font-size | 1em | Base font size |
--zooza-line-height | 1.4 | Line height |
--zooza-text-color | currentColor | Text colour |
--zooza-accent | currentColor | Submit button border, checkboxes and radio buttons, focus ring |
--zooza-border-color | currentColor | Borders of inputs, buttons and messages |
--zooza-border-width | 1px | Input border width |
--zooza-radius | 0.25em | Corner radius of inputs and buttons |
--zooza-gap | 1em | Space between fields |
--zooza-field-gap | 0.35em | Space between a label and its input |
--zooza-control-padding | 0.5em 0.75em | Padding inside inputs and buttons |
--zooza-control-background | transparent | Input background |
--zooza-muted-opacity | 0.7 | Opacity of help texts, disabled controls and the loading state |
--zooza-error-color | currentColor | Error messages |
--zooza-success-color | currentColor | Success message |
--zooza-focus-ring | 2px solid var(--zooza-accent) | Focus outline |
--zooza-max-width | 660px | Maximum width of the form |
Class names
Every element carries two classes: a shared one that is the same in all Zooza widgets built this way, and one scoped to the contact widget. Variants add a __variant suffix. Use the zooza-contact-* classes to style only the contact form.
| Element | Classes |
|---|---|
| Widget root | .zooza-widget .zooza-contact-widget |
| Form | .zooza-form .zooza-contact-form |
| Field wrapper | .zooza-field .zooza-contact-field |
| Label | .zooza-label .zooza-contact-label |
| Required marker | .zooza-required .zooza-contact-required |
| Text, number and date inputs | .zooza-input .zooza-contact-input, e.g. .zooza-contact-input__date |
| Message and long text | .zooza-textarea .zooza-contact-textarea |
| Drop-down | .zooza-select .zooza-contact-select |
| Checkbox list | .zooza-fieldset__multiselect .zooza-contact-fieldset__multiselect |
| Consents | .zooza-agreements .zooza-contact-agreements, each .zooza-agreement |
| Field error | .zooza-error .zooza-contact-error |
| Submit button | .zooza-button__primary .zooza-contact-button__primary |
| Messages | .zooza-message__error, .zooza-message__success, .zooza-message__notice |
The widget root also has a data-status attribute — loading, ready, error or success — which you can use to style each state:
.zooza-contact-widget[data-status='success'] {
padding: 2em;
background: #f4faf9;
}
Analytics
The contact widget triggers the following events for Google Tag Manager's DataLayer, Google Analytics (gtag) and Meta's Pixel (as a custom event). Nothing needs to be set up in Zooza — the events are sent to whichever of these tools are already on the page.
| Event | Description | Event data |
|---|---|---|
zooza_event_contact_form_view | Triggered once when the form is displayed to the visitor | zooza_contact_form_view: true; zooza_contact_form_id: id of the configuration shown |
zooza_event_contact_form_submit_start | Triggered when the visitor submits a form that passed validation, before it is sent | zooza_contact_form_submit_start: true; zooza_contact_form_id |
zooza_event_contact_form_submitted | Triggered when the enquiry is accepted, before the success message is shown or the visitor is redirected | zooza_contact_form_submitted: true; zooza_contact_form_id |
Event data never contains the visitor's name, email, phone or message.
Use zooza_event_contact_form_submitted as your lead conversion. In Google Tag Manager, create a Custom Event trigger with that event name and use it for your GA4 or Google Ads conversion tag. Use zooza_contact_form_id to tell apart forms on different pages.
If the configuration redirects the visitor after submitting, the page changes right after the event is pushed. Tags that must fire on the conversion should use that trigger, not a later page view.