Skills › Marketing Skills › conversion-tracking › detect-typeform.json
detect-typeform.json
122 lines · skills/conversion-tracking/assets/gtm-recipes/detect/detect-typeform.json at 88c79f6
1{
2 "exportFormatVersion": 2,
3 "exportTime": "2026-07-02 00:00:00",
4 "containerVersion": {
5 "path": "accounts/0/containers/0/versions/0",
6 "accountId": "0",
7 "containerId": "0",
8 "containerVersionId": "0",
9 "container": {
10 "path": "accounts/0/containers/0",
11 "accountId": "0",
12 "containerId": "0",
13 "name": "Conversion Detection - Typeform",
14 "publicId": "GTM-XXXXXXX",
15 "usageContext": [
16 "WEB"
17 ],
18 "fingerprint": "0",
19 "tagManagerUrl": "https://tagmanager.google.com/#/container/accounts/0/containers/0/workspaces/0"
20 },
21 "tag": [
22 {
23 "accountId": "0",
24 "containerId": "0",
25 "tagId": "1",
26 "name": "Conversion Detection - Typeform Submission Listener",
27 "type": "html",
28 "parameter": [
29 {
30 "type": "TEMPLATE",
31 "key": "html",
32 "value": "<script>\n(function () {\n 'use strict';\n\n // Shared recipe/template install guard. Keep this exact name so both\n // installation paths attach one message listener.\n if (window.__ctDetectTypeform) return;\n window.__ctDetectTypeform = true;\n window.dataLayer = window.dataLayer || [];\n\n var ORIGIN_PATTERN = /(^|\\.)typeform\\.com$/;\n var TYPEFORM_IFRAME_SELECTOR = 'iframe[src^=\"https://\"][src*=\".typeform.com/to/\"],iframe[src^=\"https://typeform.com/to/\"]';\n var accepted = Object.create(null);\n var trackedFrames = [];\n\n function isSafeId(value) {\n return typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value);\n }\n\n // Match a public form ID against the URL path using CSS selectors. Do not\n // fetch, parse, retain, or serialize the iframe src; its query can contain\n // prefilled respondent data.\n function hasFormPath(iframe, formId, host) {\n if (!isSafeId(formId) || !host || !iframe || typeof iframe.matches !== 'function') return false;\n var prefix = 'iframe[src^=\"https://' + host + '/to/' + formId;\n var selectors = [\n 'iframe[src=\"https://' + host + '/to/' + formId + '\"]',\n prefix + '?\"]',\n prefix + '#\"]',\n prefix + '/\"]'\n ];\n for (var i = 0; i < selectors.length; i++) {\n try {\n if (iframe.matches(selectors[i])) return true;\n } catch (e) {\n return false;\n }\n }\n return false;\n }\n\n function isTypeformFrame(iframe) {\n if (!iframe || typeof iframe.matches !== 'function') return false;\n try { return iframe.matches(TYPEFORM_IFRAME_SELECTOR); } catch (e) { return false; }\n }\n\n function track(iframe) {\n if (!isTypeformFrame(iframe)) return;\n for (var i = 0; i < trackedFrames.length; i++) {\n if (trackedFrames[i].iframe === iframe) return;\n }\n try {\n trackedFrames.push({ iframe: iframe, source: iframe.contentWindow });\n } catch (e) {\n // If a browser denies the WindowProxy reference, fail closed for this frame.\n }\n }\n\n function scan(root) {\n if (!root || typeof root.querySelectorAll !== 'function') return;\n var frames;\n try { frames = root.querySelectorAll(TYPEFORM_IFRAME_SELECTOR); } catch (e) { return; }\n for (var i = 0; i < frames.length; i++) track(frames[i]);\n }\n\n function trackedSourceForForm(source, formId, host) {\n scan(document);\n for (var i = 0; i < trackedFrames.length; i++) {\n var tracked = trackedFrames[i];\n try {\n if (tracked.source === source && hasFormPath(tracked.iframe, formId, host)) return true;\n } catch (e) {\n // A browser may deny access to a detached frame; ignore that frame.\n }\n }\n return false;\n }\n\n function ownDataValue(object, key) {\n if (!object || typeof object !== 'object') return undefined;\n var descriptor;\n try { descriptor = Object.getOwnPropertyDescriptor(object, key); } catch (e) { return undefined; }\n return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')\n ? descriptor.value\n : undefined;\n }\n\n function onMessage(event) {\n if (!event || !event.source || event.source === window) return;\n var origin = event.origin;\n if (typeof origin !== 'string') return;\n var match = /^https:\\/\\/([^/:?#]+)$/.exec(origin);\n if (!match || !ORIGIN_PATTERN.test(match[1])) return;\n\n var data = event.data;\n if (!data || typeof data !== 'object') return;\n\n // Only the historically captured canonical success message is accepted.\n // Public Typeform docs document onSubmit, not raw postMessage aliases.\n if (ownDataValue(data, 'type') !== 'form-submit') return;\n var formId = ownDataValue(data, 'formId');\n var responseId = ownDataValue(data, 'responseId');\n if (!isSafeId(formId) || !isSafeId(responseId)) return;\n if (!trackedSourceForForm(event.source, formId, match[1])) return;\n\n var key = formId + '\\u0000' + responseId;\n if (accepted[key]) return;\n accepted[key] = true;\n window.dataLayer.push({\n event: 'typeform_form_submitted',\n form_id: formId,\n response_id: responseId\n });\n }\n\n scan(document);\n if (typeof MutationObserver !== 'undefined' && document.documentElement) {\n var observer = new MutationObserver(function (mutations) {\n for (var i = 0; i < mutations.length; i++) {\n var mutation = mutations[i];\n if (mutation.type === 'attributes') {\n track(mutation.target);\n continue;\n }\n var nodes = mutation.addedNodes || [];\n for (var j = 0; j < nodes.length; j++) {\n var node = nodes[j];\n if (!node) continue;\n if (node.tagName === 'IFRAME') track(node);\n else scan(node);\n }\n }\n });\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['src']\n });\n }\n\n window.addEventListener('message', onMessage, false);\n})();\n</script>"
33 },
34 {
35 "type": "BOOLEAN",
36 "key": "supportDocumentWrite",
37 "value": "false"
38 }
39 ],
40 "firingTriggerId": [
41 "2147479553"
42 ],
43 "tagFiringOption": "ONCE_PER_EVENT",
44 "monitoringMetadata": {
45 "type": "MAP"
46 },
47 "consentSettings": {
48 "consentStatus": "NOT_SET"
49 }
50 }
51 ],
52 "trigger": [
53 {
54 "accountId": "0",
55 "containerId": "0",
56 "triggerId": "2",
57 "name": "CE - Typeform Submitted",
58 "type": "CUSTOM_EVENT",
59 "customEventFilter": [
60 {
61 "type": "EQUALS",
62 "parameter": [
63 {
64 "type": "TEMPLATE",
65 "key": "arg0",
66 "value": "{{_event}}"
67 },
68 {
69 "type": "TEMPLATE",
70 "key": "arg1",
71 "value": "typeform_form_submitted"
72 }
73 ]
74 }
75 ],
76 "filter": []
77 }
78 ],
79 "variable": [
80 {
81 "accountId": "0",
82 "containerId": "0",
83 "variableId": "1",
84 "name": "DLV - Typeform Form ID",
85 "type": "v",
86 "parameter": [
87 {
88 "type": "TEMPLATE",
89 "key": "name",
90 "value": "form_id"
91 },
92 {
93 "type": "INTEGER",
94 "key": "dataLayerVersion",
95 "value": "2"
96 }
97 ]
98 },
99 {
100 "accountId": "0",
101 "containerId": "0",
102 "variableId": "2",
103 "name": "DLV - Typeform Response ID",
104 "type": "v",
105 "parameter": [
106 {
107 "type": "TEMPLATE",
108 "key": "name",
109 "value": "response_id"
110 },
111 {
112 "type": "INTEGER",
113 "key": "dataLayerVersion",
114 "value": "2"
115 }
116 ]
117 }
118 ],
119 "fingerprint": "0",
120 "tagManagerUrl": "https://tagmanager.google.com/#/container/accounts/0/containers/0/workspaces/0"
121 }
122}