> This page is for version 2025-12-08.
> For other versions, use one of these documentation indexes:
> - 2026-09-29 (default): https://docs.withpersona.com/2026-09-29/llms.txt
> - 2025-12-08: https://docs.withpersona.com/2025-12-08/llms.txt
> - 2025-10-27: https://docs.withpersona.com/2025-10-27/llms.txt
> - 2023-01-05: https://docs.withpersona.com/2023-01-05/llms.txt
> - 2022-09-01: https://docs.withpersona.com/2022-09-01/llms.txt
> - 2021-07-05: https://docs.withpersona.com/2021-07-05/llms.txt
> - 2021-05-14: https://docs.withpersona.com/2021-05-14/llms.txt
> - 2020-05-18: https://docs.withpersona.com/2020-05-18/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withpersona.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withpersona.com/_mcp/server.

# Google Tag Manager

> Forward Embedded Flow events to Google Tag Manager for analytics and conversion tracking.

You can forward Persona Inquiry SDK events to Google Tag Manager's data layer for use in analytics, conversion tracking, and other GTM-powered workflows.

This integration uses a Persona-provided GTM template that receives events from the Embedded Flow SDK and pushes them to the data layer. No changes to the Persona SDK itself are required — you only need to add a small callback to your existing SDK configuration.

## Step 1: Import the Persona GTM template

Copy the [template code](#template-code) at the bottom of this page into a file named `persona-gtm-template.tpl`.

Once you have the file:

1. In your GTM workspace, go to **Templates** > **Tag Templates** > **New**.
2. Click the three-dot menu in the top right and select **Import**.
3. Select the `.tpl` file.
4. Click **Save**.

## Step 2: Create a tag

1. Go to **Tags** > **New**.
2. Under Tag Configuration, select **Persona Inquiry Events** as the tag type.
3. Choose which events to forward:
   * **All events** — forwards every event from the SDK.
   * **Lifecycle events only** — forwards `ready`, `start`, `complete`, `cancel`, and `error`.
   * **Custom selection** — choose specific events to forward.
4. Set a trigger (e.g. **All Pages** or **Page Initialization**) so the tag is active before the Persona SDK loads.
5. Click **Save**, then **Publish** your workspace.

## Step 3: Add the onEvent callback

In your existing `Persona.Client` configuration, add the `onEvent` callback:

```javascript
const client = new Persona.Client({
  // ...your existing config
  onEvent: (name, meta) => {
    window.PersonaGTM?.push(name, meta);
  },
});
```

Optionally, if you want to receive all events including `click`, `form-update`, and `step-transitioning`, add the `eventsAllowlist` parameter (requires SDK 5.8.0+):

```javascript
const client = new Persona.Client({
  // ...your existing config
  eventsAllowlist: "all",
  onEvent: (name, meta) => {
    window.PersonaGTM?.push(name, meta);
  },
});
```

### Passing custom attributes

You can pass custom attributes (e.g. internal user IDs, plan info, or any application-specific data) to every data layer push by providing a third argument to `push`. These are merged into the top level of the data layer event:

```javascript
const client = new Persona.Client({
  // ...your existing config
  onEvent: (name, meta) => {
    window.PersonaGTM?.push(name, meta, {
      userId: "user-123",
      plan: "enterprise",
    });
  },
});
```

This produces data layer events like:

```json
{
  "event": "persona_complete",
  "personaEvent": {
    "name": "complete",
    "metadata": { "inquiryId": "inq_abc123" }
  },
  "userId": "user-123",
  "plan": "enterprise"
}
```

To use these values in other tags, create a **Data Layer Variable** with the name `userId` (or whichever key you need).

## Step 4: Use events in GTM

Events appear in the data layer as `persona_<eventName>` (e.g. `persona_complete`). Dashes in SDK event names are automatically converted to underscores for GA4 compatibility (e.g. `document-camera-select` becomes `persona_document_camera_select`).

The `persona` prefix is customizable in the tag's **Advanced Settings** — you can change it to any prefix that fits your naming conventions.

Each event includes a `personaEvent` object with:

* `personaEvent.name` — the original SDK event name (e.g. `complete`, `cancel`, `click`)
* `personaEvent.metadata` — the event metadata

For the full list of events and their metadata, see the [Client Callbacks](/embedded-flow-client-callbacks#onevent) reference.

### Create a trigger

To fire another tag (e.g. Google Analytics, a conversion pixel) when an inquiry completes:

1. Go to **Triggers** > **New**.
2. Select **Custom Event** as the trigger type.
3. Set the event name to `persona_complete`.
4. Save the trigger and attach it to the tag you want to fire.

### Create a data layer variable

To access event data in other tags:

1. Go to **Variables** > **User-Defined Variables** > **New**.
2. Select **Data Layer Variable** as the variable type.
3. Set the variable name to `personaEvent.metadata.inquiryId` (or any other property you need).
4. Use this variable in your other tags.

### Google Ads Enhanced Conversions

You can use Persona's hashed PII fields to power [Google Ads Enhanced Conversions](https://support.google.com/google-ads/answer/11062876) without exposing raw user data in the data layer.

To get started, contact [Persona support](https://support.withpersona.com) to enable hashed fields on your inquiry template. Once enabled, the `complete` event metadata will include a `hashedFields` object containing SHA-256 hashed values for the configured fields (e.g. `email_address`, `phone_number`).

To set up Enhanced Conversions in GTM:

1. Create **Data Layer Variables** for each hashed field you need:
   * `personaEvent.metadata.hashedFields.email_address`
   * `personaEvent.metadata.hashedFields.phone_number`
2. Create a **Google Ads: Conversion Tracking** tag.
   * Enter your Conversion ID and Conversion Label.
   * Check **Include user-provided data from your website**.
   * Map the user-provided data fields (Email, Phone, etc.) to the data layer variables you created above.
   * Set the trigger to `persona_complete`.

Google Ads Enhanced Conversions accepts pre-hashed SHA-256 values, so no additional transformation is needed in GTM.

### Forwarding raw field values

If you need raw field values in the data layer (e.g. any non-PII values the applicant entered), you can configure an allowlist of field keys on your inquiry template. Values for allowlisted fields are then included in the following events:

* **`form-update`** — metadata includes `value` (the current input value) alongside `fieldName`.
* **`complete`** — metadata includes an `analyticsFields` object keyed by field key, containing the final submitted values.

To enable this, contact [Persona support](https://support.withpersona.com) with the specific field keys you want passed through (e.g. `plan_tier`, `referral_code`). Persona will configure the allowlist on your inquiry template.

> **Warning**
>
> Only fields on the allowlist are forwarded. Do not request PII fields (name, date of birth, address, government ID, etc.) — those should be handled via [hashed fields](#google-ads-enhanced-conversions) instead. Field values on the allowlist are sent to your analytics in plaintext.

Once enabled, you can create **Data Layer Variables** for the specific fields you need:

* `personaEvent.metadata.value` — on `form-update` events, the current value of the field identified by `personaEvent.metadata.fieldName`.
* `personaEvent.metadata.analyticsFields.<field_key>` — on the `complete` event, the final submitted value for a given allowlisted field key.

## Template code

Copy the contents below into a file named `persona-gtm-template.tpl` to import into GTM.

**`persona-gtm-template.tpl`**

```text persona-gtm-template.tpl
___TERMS_OF_SERVICE___

By creating or modifying this file you agree to Google Tag Manager's Community
Template Gallery Developer Terms of Service available at
https://developers.google.com/tag-manager/gallery-tos (or such other URL as
Google may provide), as modified from time to time.


___INFO___

{
  "type": "TAG",
  "id": "cvt_temp_public_id",
  "version": 1,
  "securityGroups": [],
  "displayName": "Persona Inquiry Events",
  "categories": [
    "ANALYTICS"
  ],
  "brand": {
    "id": "brand_persona",
    "displayName": "Persona",
    "thumbnail": ""
  },
  "description": "Forwards Persona Inquiry SDK events to the GTM data layer. Use with the Persona Embedded Flow SDK to track inquiry lifecycle events like completion, cancellation, and verification steps.",
  "containerContexts": [
    "WEB"
  ]
}


___TEMPLATE_PARAMETERS___

[
  {
    "type": "SELECT",
    "name": "eventFilter",
    "displayName": "Events to forward",
    "macrosInSelect": false,
    "selectItems": [
      {
        "value": "all",
        "displayValue": "All events"
      },
      {
        "value": "lifecycle",
        "displayValue": "Lifecycle events only (ready, complete, cancel, error)"
      },
      {
        "value": "custom",
        "displayValue": "Custom selection"
      }
    ],
    "simpleValueType": true,
    "defaultValue": "all",
    "help": "Choose which Persona SDK events are forwarded to the data layer."
  },
  {
    "type": "GROUP",
    "name": "customEvents",
    "displayName": "Select events",
    "groupStyle": "NO_ZIPPY",
    "enablingConditions": [
      {
        "paramName": "eventFilter",
        "paramValue": "custom",
        "type": "EQUALS"
      }
    ],
    "subParams": [
      {
        "type": "CHECKBOX",
        "name": "evt_ready",
        "checkboxText": "ready",
        "simpleValueType": true,
        "defaultValue": true
      },
      {
        "type": "CHECKBOX",
        "name": "evt_start",
        "checkboxText": "start",
        "simpleValueType": true,
        "defaultValue": true
      },
      {
        "type": "CHECKBOX",
        "name": "evt_complete",
        "checkboxText": "complete",
        "simpleValueType": true,
        "defaultValue": true
      },
      {
        "type": "CHECKBOX",
        "name": "evt_cancel",
        "checkboxText": "cancel",
        "simpleValueType": true,
        "defaultValue": true
      },
      {
        "type": "CHECKBOX",
        "name": "evt_error",
        "checkboxText": "error",
        "simpleValueType": true,
        "defaultValue": true
      },
      {
        "type": "CHECKBOX",
        "name": "evt_document_camera_select",
        "checkboxText": "document-camera-select",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_document_camera_capture",
        "checkboxText": "document-camera-capture",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_document_upload",
        "checkboxText": "document-upload",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_documents_save_successful",
        "checkboxText": "documents-save-successful",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_documents_save_fail",
        "checkboxText": "documents-save-fail",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_selfie_camera_select",
        "checkboxText": "selfie-camera-select",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_selfie_camera_capture",
        "checkboxText": "selfie-camera-capture",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_selfie_record_upload",
        "checkboxText": "selfie-record-upload",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_load_camera_failed",
        "checkboxText": "load-camera-failed",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_one_time_link_sent",
        "checkboxText": "one-time-link-sent",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_one_time_link_start",
        "checkboxText": "one-time-link-start",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_one_time_link_exit",
        "checkboxText": "one-time-link-exit",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_page_change",
        "checkboxText": "page-change",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_step_transitioning",
        "checkboxText": "step-transitioning",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_click",
        "checkboxText": "click",
        "simpleValueType": true,
        "defaultValue": false
      },
      {
        "type": "CHECKBOX",
        "name": "evt_form_update",
        "checkboxText": "form-update",
        "simpleValueType": true,
        "defaultValue": false
      }
    ]
  },
  {
    "type": "GROUP",
    "name": "advancedSettings",
    "displayName": "Advanced Settings",
    "groupStyle": "ZIPPY_CLOSED",
    "subParams": [
      {
        "type": "TEXT",
        "name": "eventPrefix",
        "displayName": "Event name prefix",
        "simpleValueType": true,
        "defaultValue": "persona",
        "help": "Prefix for data layer event names. Events will appear as prefix_eventName (e.g. persona_complete).",
        "valueValidators": [
          {
            "type": "NON_EMPTY"
          }
        ]
      }
    ]
  }
]


___SANDBOXED_JS_FOR_WEB_TEMPLATE___

const log = require('logToConsole');
const setInWindow = require('setInWindow');
const createQueue = require('createQueue');

const dataLayerPush = createQueue('dataLayer');
const eventPrefix = data.eventPrefix || 'persona';

const normalize = function (str) {
  var result = '';
  for (var i = 0; i < str.length; i++) {
    result += str.charAt(i) === '-' ? '_' : str.charAt(i);
  }
  return result;
};

const lifecycleEvents = ['ready', 'complete', 'cancel', 'error', 'start'];

const customEventMap = {
  'ready': data.evt_ready,
  'start': data.evt_start,
  'complete': data.evt_complete,
  'cancel': data.evt_cancel,
  'error': data.evt_error,
  'document-camera-select': data.evt_document_camera_select,
  'document-camera-capture': data.evt_document_camera_capture,
  'document-upload': data.evt_document_upload,
  'documents-save-successful': data.evt_documents_save_successful,
  'documents-save-fail': data.evt_documents_save_fail,
  'selfie-camera-select': data.evt_selfie_camera_select,
  'selfie-camera-capture': data.evt_selfie_camera_capture,
  'selfie-record-upload': data.evt_selfie_record_upload,
  'load-camera-failed': data.evt_load_camera_failed,
  'one-time-link-sent': data.evt_one_time_link_sent,
  'one-time-link-start': data.evt_one_time_link_start,
  'one-time-link-exit': data.evt_one_time_link_exit,
  'page-change': data.evt_page_change,
  'step-transitioning': data.evt_step_transitioning,
  'click': data.evt_click,
  'form-update': data.evt_form_update,
};

const shouldForward = function (eventName) {
  if (data.eventFilter === 'all') {
    return true;
  }
  if (data.eventFilter === 'lifecycle') {
    return lifecycleEvents.indexOf(eventName) > -1;
  }
  return !!customEventMap[eventName];
};

// Strip sensitive fields from metadata before pushing to the data layer.
// 'fields' contains PII (name, DOB, address, etc.) and 'sessionToken' is a
// credential — neither should be sent to third-party analytics.
const piiKeys = ['fields', 'sessionToken'];

const getType = require('getType');

const stripPii = function (obj) {
  if (!obj || getType(obj) !== 'object') {
    return obj;
  }
  const clean = {};
  for (var key in obj) {
    if (piiKeys.indexOf(key) === -1) {
      clean[key] = obj[key];
    }
  }
  return clean;
};

const push = function (eventName, metadata, attributes) {
  if (!shouldForward(eventName)) {
    return;
  }

  log('Persona GTM: forwarding event', eventName);

  const safeMetadata = stripPii(metadata);

  const dlEvent = {
    event: eventPrefix + '_' + normalize(eventName),
    personaEvent: {
      name: eventName,
      metadata: safeMetadata,
    },
  };

  if (attributes && getType(attributes) === 'object') {
    for (var key in attributes) {
      dlEvent[key] = attributes[key];
    }
  }

  dataLayerPush(dlEvent);
};

setInWindow('PersonaGTM', { push: push }, true);

log('Persona GTM: ready');
data.gtmOnSuccess();


___WEB_PERMISSIONS___

[
  {
    "instance": {
      "key": {
        "publicId": "logging",
        "versionId": "1"
      },
      "param": [
        {
          "key": "environments",
          "value": {
            "type": 1,
            "string": "debug"
          }
        }
      ]
    },
    "clientAnnotations": {
      "isEditedByUser": true
    },
    "isRequired": true
  },
  {
    "instance": {
      "key": {
        "publicId": "access_globals",
        "versionId": "1"
      },
      "param": [
        {
          "key": "keys",
          "value": {
            "type": 2,
            "listItem": [
              {
                "type": 3,
                "mapKey": [
                  {
                    "type": 1,
                    "string": "key"
                  },
                  {
                    "type": 1,
                    "string": "read"
                  },
                  {
                    "type": 1,
                    "string": "write"
                  },
                  {
                    "type": 1,
                    "string": "execute"
                  }
                ],
                "mapValue": [
                  {
                    "type": 1,
                    "string": "PersonaGTM"
                  },
                  {
                    "type": 8,
                    "boolean": true
                  },
                  {
                    "type": 8,
                    "boolean": true
                  },
                  {
                    "type": 8,
                    "boolean": false
                  }
                ]
              },
              {
                "type": 3,
                "mapKey": [
                  {
                    "type": 1,
                    "string": "key"
                  },
                  {
                    "type": 1,
                    "string": "read"
                  },
                  {
                    "type": 1,
                    "string": "write"
                  },
                  {
                    "type": 1,
                    "string": "execute"
                  }
                ],
                "mapValue": [
                  {
                    "type": 1,
                    "string": "PersonaGTM.push"
                  },
                  {
                    "type": 8,
                    "boolean": true
                  },
                  {
                    "type": 8,
                    "boolean": true
                  },
                  {
                    "type": 8,
                    "boolean": false
                  }
                ]
              },
              {
                "type": 3,
                "mapKey": [
                  {
                    "type": 1,
                    "string": "key"
                  },
                  {
                    "type": 1,
                    "string": "read"
                  },
                  {
                    "type": 1,
                    "string": "write"
                  },
                  {
                    "type": 1,
                    "string": "execute"
                  }
                ],
                "mapValue": [
                  {
                    "type": 1,
                    "string": "dataLayer"
                  },
                  {
                    "type": 8,
                    "boolean": true
                  },
                  {
                    "type": 8,
                    "boolean": true
                  },
                  {
                    "type": 8,
                    "boolean": false
                  }
                ]
              }
            ]
          }
        }
      ]
    },
    "clientAnnotations": {
      "isEditedByUser": true
    },
    "isRequired": true
  }
]


___TESTS___

scenarios:
- name: sets up global and succeeds
  code: |-
    const mockData = {
      eventFilter: 'all',
      eventPrefix: 'persona',
    };

    runCode(mockData);

    assertApi('setInWindow').wasCalled();
    assertApi('gtmOnSuccess').wasCalled();
- name: uses custom event prefix
  code: |-
    const mockData = {
      eventFilter: 'all',
      eventPrefix: 'MyPrefix',
    };

    runCode(mockData);

    assertApi('setInWindow').wasCalled();
    assertApi('gtmOnSuccess').wasCalled();


___NOTES___

Created on 2026-03-31
```