Skills › Marketing Skills › conversion-tracking › detect-calendly.json
detect-calendly.json
122 lines · skills/conversion-tracking/assets/gtm-recipes/detect/detect-calendly.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 - Calendly",
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 - Calendly Booking Listener",
27 "type": "html",
28 "parameter": [
29 {
30 "type": "TEMPLATE",
31 "key": "html",
32 "value": "<script>\n(function () {\n 'use strict';\n if (window.__ctDetectCalendly) return;\n window.__ctDetectCalendly = true;\n window.dataLayer = window.dataLayer || [];\n\n // The CSS selector matches only Calendly's documented exact embed host.\n // Do not read iframe.src or its `src` attribute: prefill parameters can\n // contain invitee PII. The selector engine performs the prefix match without\n // exposing the URL value to this listener.\n var CALENDLY_FRAME_SELECTOR = 'iframe[src^=\\\"https://calendly.com/\\\"]';\n var CALENDLY_ORIGIN = 'https://calendly.com';\n var API_HOSTS = { 'api.calendly.com': true, 'calendly.com': true };\n var trackedFrames = [];\n var seenPairs = [];\n\n function trackFrame(frame) {\n if (!frame || !frame.contentWindow) return;\n for (var i = 0; i < trackedFrames.length; i++) {\n if (trackedFrames[i].frame === frame) {\n trackedFrames[i].source = frame.contentWindow;\n trackedFrames[i].origin = CALENDLY_ORIGIN;\n return;\n }\n }\n trackedFrames.push({ frame: frame, source: frame.contentWindow, origin: CALENDLY_ORIGIN });\n }\n\n function scan(root) {\n if (!root || typeof root.querySelectorAll !== 'function') return;\n var frames;\n try { frames = root.querySelectorAll(CALENDLY_FRAME_SELECTOR); } catch (e) { return; }\n for (var i = 0; i < frames.length; i++) trackFrame(frames[i]);\n }\n\n function sourceIsTracked(event) {\n for (var i = 0; i < trackedFrames.length; i++) {\n var item = trackedFrames[i];\n try {\n if (item.frame.isConnected === true && item.frame.contentWindow === event.source && item.source === event.source && item.origin === event.origin) return true;\n } catch (e) { /* replaced or inaccessible frames fail closed */ }\n }\n return false;\n }\n\n function resourceIdentity(value, kind) {\n if (typeof value !== 'string' || /[?#]/.test(value)) return null;\n try {\n var url = new URL(value);\n if (url.protocol !== 'https:' || url.port || !API_HOSTS[url.hostname]) return null;\n var eventMatch = /(?:^|\\/)scheduled_events\\/([A-Za-z0-9_-]{1,128})\\/?$/.exec(url.pathname);\n var inviteeMatch = /(?:^|\\/)scheduled_events\\/([A-Za-z0-9_-]{1,128})\\/invitees\\/([A-Za-z0-9_-]{1,128})\\/?$/.exec(url.pathname);\n if (kind === 'event' && eventMatch) return { eventId: eventMatch[1].toLowerCase() };\n if (kind === 'invitee' && inviteeMatch) return { eventId: inviteeMatch[1].toLowerCase(), inviteeId: inviteeMatch[2].toLowerCase() };\n } catch (e) { return null; }\n return null;\n }\n\n function pairSeen(eventId, inviteeId) {\n var key = eventId + ':' + inviteeId;\n for (var i = 0; i < seenPairs.length; i++) if (seenPairs[i] === key) return true;\n seenPairs.push(key);\n return false;\n }\n\n function onMessage(message) {\n if (!message || !message.source || message.source === window || !sourceIsTracked(message)) return;\n var data = message.data;\n if (typeof data === 'string') {\n try { data = JSON.parse(data); } catch (e) { return; }\n }\n if (!data || typeof data !== 'object' || data.event !== 'calendly.event_scheduled') return;\n var payload = data.payload || {};\n var eventUri = payload.event && payload.event.uri;\n var inviteeUri = payload.invitee && payload.invitee.uri;\n var eventIdentity = resourceIdentity(eventUri, 'event');\n var inviteeIdentity = resourceIdentity(inviteeUri, 'invitee');\n if (!eventIdentity || !inviteeIdentity || eventIdentity.eventId !== inviteeIdentity.eventId) return;\n if (pairSeen(eventIdentity.eventId, inviteeIdentity.inviteeId)) return;\n window.dataLayer.push({ event: 'calendly_event_scheduled', event_uri: eventUri, invitee_uri: inviteeUri });\n }\n\n function start() {\n if (typeof document === 'undefined') return;\n scan(document);\n if (typeof MutationObserver === 'undefined' || !document.documentElement) return;\n try {\n var observer = new MutationObserver(function (mutations) {\n for (var i = 0; i < mutations.length; i++) {\n var added = mutations[i].addedNodes || [];\n for (var j = 0; j < added.length; j++) {\n var node = added[j];\n if (node && node.tagName === 'IFRAME') scan(document);\n else scan(node);\n }\n if (mutations[i].type === 'attributes' && mutations[i].attributeName === 'src') scan(document);\n }\n });\n observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['src'] });\n } catch (e) {}\n }\n\n if (typeof document !== 'undefined') {\n if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, false);\n else start();\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 - Calendly Event Scheduled",
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": "calendly_event_scheduled"
72 }
73 ]
74 }
75 ],
76 "filter": []
77 }
78 ],
79 "variable": [
80 {
81 "accountId": "0",
82 "containerId": "0",
83 "variableId": "1",
84 "name": "DLV - Calendly Event URI",
85 "type": "v",
86 "parameter": [
87 {
88 "type": "TEMPLATE",
89 "key": "name",
90 "value": "event_uri"
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 - Calendly Invitee URI",
104 "type": "v",
105 "parameter": [
106 {
107 "type": "TEMPLATE",
108 "key": "name",
109 "value": "invitee_uri"
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}