Klaviyo

Subscribe or unsubscribe profiles and record Klaviyo events from a form.

Klaviyo is an add-on for Forms. Once your account is connected, every form you edit gains a Klaviyo panel in its sidebar. An action can subscribe or unsubscribe a profile, or record an event that starts a Klaviyo flow. You decide which submitted fields map to profile details and event properties.

Nothing else about the form changes: validation, messages, redirects, notification emails and the Submissions list all behave as they did before.

Where to find it

Two places, because there are two kinds of setting. The account connection is site-wide, at XPAC → Integrations on the Klaviyo tab. Which lists a particular form adds to is per-form, in the Klaviyo panel of the document sidebar while you edit that form.

Forms has to be active

The plugin does nothing on its own. Without XPAC Forms it registers no settings tab, no panel and no sending, and shows an admin notice asking for Forms instead.

Connecting your account

Create an API key in Klaviyo, under Settings → API keys. It has to be a private key — the plugin reads your lists and adds people to them, and the short public key that goes in a website's tracking snippet cannot do either.

Paste it into the Api Key field at XPAC → Integrations → Klaviyo and save. That single key serves every form on the site.

Missing credentials do not hide your actions

The Klaviyo panel and its saved actions remain visible when the key is missing or revoked. Account-backed pickers show the connection error, and a submission still creates its Forms entry before the queued delivery is marked failed. Save a private key with the required scopes, then retry it from XPAC → Forms → Deliveries.

Adding a form to a list

Edit the form and open the Klaviyo panel. If your key is good you will see an empty list with a round + button (Add action); if it is not, you will see Klaviyo's own error message instead.

Press +. Choose whether to subscribe, unsubscribe or track an event, then complete the controls for that action.

Choose the list when the action needs one and map a form field to Email address. Event actions also need a metric name; email-and-SMS consent also needs a phone-number mapping.

Press Save, then update the form. Updating the form is what stores the action.

Add another action for another list or event. With the Conditional Logic plugin installed, each action can also carry conditions so it runs only for matching submissions.

Editing and removing an action

Each row shows Create contact, a pencil (Edit action) that reopens the window, and a red X (Remove action) that asks Are you sure you want to remove the item? before acting.

Use the switch on a row to pause or resume that action without losing its settings. The X removes the selected row after confirmation; other actions keep their order and settings.

What reaches Klaviyo

Only mapped answers are sent. Map an email field to Email address for every action, and add any profile fields, location fields or named custom properties you need. For an event, profile mappings identify the person while custom properties become the event's properties.

Record only consent the form actually collected

A subscribe action can record consent for email, email and SMS, or neither. The SMS choice requires a phone-number mapping. No consent merely adds the profile to the list; it does not make that profile marketable. The integration does not prove that consent was freely given, so make the form wording and stored answer do that.

The chosen list's Klaviyo setting controls confirmation. A single-opt-in list joins immediately; a double-opt-in list sends Klaviyo's confirmation and joins only after the recipient clicks. The editor displays the setting Klaviyo reports for the list.

Deliveries, retries and duplicate limits

Forms writes the entry first and schedules Klaviyo afterwards. The visitor does not wait, and a remote failure cannot erase the submission. The Deliveries screen retains the provider's explanation. Accepted profile, list, consent and event steps are marked done, so a retry resumes the failed step without repeating completed siblings.

Configuration, authentication and validation failures reported as 400/401/403/404/410/422 are left failed for an administrator to fix. Rate limits, 5xx replies and transport failures are retried after about 1, 5, 30 and 120 minutes, for at most five attempts. Restore the private key or action configuration, then retry a permanent failure manually.

Profile upserts and list membership operations converge on Klaviyo state. Each tracked event also carries a stable unique_id based on its Forms entry and action, which Klaviyo uses to collapse a replay of that event.

For developers

The API key is stored in the xpac option under the klaviyo_api_key key. Its password control masks ordinary settings-screen display without changing the stored option name or value. It is read during xpac_forms_init and is never sent to the form editor.

Per-form actions live under a klaviyo key in the form's form_settings post meta on the xpac-form post type, contributed to the Forms meta schema at Addon.php:207-234 and therefore readable and writable over the REST API at /wp-json/wp/v2/xpac-form/<id>:

{
	"klaviyo": {
		"items": [
			{
				"status": true,
				"action": "subscribe",
				"list": "AbCdEf"
			}
		]
	}
}

action may be subscribe, unsubscribe or event. Subscribe actions also store consent and customSource; unsubscribe actions store scope; event actions store metric; all three store explicit map rows. Items are declared with additionalProperties: true, which is how Conditional Logic's condition object rides along without appearing in the schema. status is declared and the editor always writes it true, but the panel passes no withDisableFeature, so no toggle is rendered and there is no UI path to false.

One route backs the editor UI, requiring manage_options:

GET /wp-json/xpac/v1/form/klaviyo/lists

Connection failures are returned as REST errors, and the editor renders their message beside the list control without hiding saved actions.

The requests

Client targets https://a.klaviyo.com/api/, authenticates with Klaviyo's private-key header and sends the 2026-07-15 API revision. Its timeout defaults to ten seconds and is filterable with xpac_klaviyo_request_timeout. The delivery path upserts profiles, changes list subscription state or posts events according to the action. Exceptions keep the HTTP status for the shared delivery classifier.

Addon::subscribe() is still attached through xpac_forms_submit_callbacks, but the Forms dispatcher queues it only after the entry has been written. Each accepted sub-operation records a delivery key. Events additionally send their stable entry and action identity as Klaviyo's unique_id.

Hooks and blocks

The plugin registers no blocks and fires no PHP hooks of its own. That is a fact about its shape rather than a gap in this page: it is a thin adapter, and all of its behaviour is contributed to hooks Forms already owns. It attaches to five of them:

HookFired byUsed for
xpac_forms_initpackages/Forms/Bootstrap.php:93, on init at priority 200Reads the key, registers the editor script, then wires the four below
xpac_forms_post_meta_schemaForms/core/base/PostType.php:126The shape of its per-form settings
xpac_forms_post_meta_default_valuesForms/core/base/PostType.php:118An empty items array
xpac_forms_post_script_dependenciesForms/core/base/PostType.php:329Loads its panel ahead of the Forms editor script
xpac_forms_submit_callbacksForms/core/base/Submission.php:402, as xpac_forms_{$action}_callbacks with $action of submitThe send itself

The last one is worth noting for anyone auditing hook names: it is fired under a runtime-composed name, so a search for the literal string xpac_forms_submit_callbacks in Forms finds nothing. It is the same stage all eleven Forms integrations run in. Outside Forms it also uses plugins_loaded, rest_api_init and admin_notices.

On the editor side the panel is contributed from JavaScript through the xpac-forms-form-panels filter (assets/packages/klaviyo/index.js:16) with no priority, which is why its position among the other add-ons' panels is not fixed. Its action window can be extended through the xpac-forms-after-settings-modal-content filter (assets/packages/klaviyo/Components/Modal.js:58), which receives the action's current options with an onChange callback and is passed xpac-klaviyo as the module it is filtering. Conditional Logic's condition editor is the one thing on it today.

Nothing in the send path is licence-gated.

On this page