Appearance
Embedded forms & the event relay
When you embed a Recruitly form on your own site in an iframe, analytics fired inside the iframe is unreliable: GA4 sets first-party cookies scoped to the iframe's origin, which makes them third-party from the browser's point of view — Safari and Firefox block them outright and Chrome partitions them.
Recruitly's fix: inside an embed, the form's gtag is wrapped so every command is also posted to the parent window. Your page listens and re-fires the command into its own first-party GA — so form interactions land in your GA4 property with full attribution, alongside the rest of your site traffic.
Recruitly iframe Your page
gtag('event', ...) ──postMessage──▶ listener ──▶ your gtag.js ──▶ your GA4The snippet
Add this to any page that embeds a Recruitly form, after your Google Analytics initialisation code (gtag.js):
html
<script>
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'recruitly_ga_event' && window.gtag) {
window.gtag.apply(null, event.data.args);
}
});
</script>The same snippet, with a Copy button, lives in Recruitly under Marketplace → Google Analytics → Configuration.
Requirements:
- Your own GA4 (
gtag.js) must be initialised before the listener fires — put the snippet after it. - The listener must be on the page that contains the iframe.
- If you filter messages by
event.origin, allow the origin of the Recruitly form you embed.
Message contract
Every message has this shape:
| Field | Value |
|---|---|
event.data.type | Always the string 'recruitly_ga_event' |
event.data.args | The raw gtag() argument list, e.g. ['event', 'generate_lead'] or ['config', 'G-XXXXXXXXXX'] |
Not using gtag? Route it yourself
Because the relay carries the raw argument list, you can feed it into Google Tag Manager or any other collector instead of window.gtag:
html
<script>
window.addEventListener('message', function (event) {
if (!event.data || event.data.type !== 'recruitly_ga_event') return;
var args = event.data.args; // e.g. ['event', 'generate_lead', {...}]
if (args[0] === 'event') {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'recruitly_' + args[1], // → GTM custom event trigger
recruitlyParams: args[2] || {},
});
}
});
</script>Create a GTM Custom Event trigger for recruitly_generate_lead (etc.) and forward it to GA4 or any tag you like.
Verifying the relay
On the page hosting the iframe, open the browser console and run:
jswindow.addEventListener('message', e => { if (e.data?.type === 'recruitly_ga_event') console.log('relay:', e.data.args); });Interact with the embedded form (submit a test lead) — you should see the relayed commands, including
['event', 'generate_lead']on success. This proves the iframe side is working.If commands appear in the console but nothing reaches GA4, your snippet/listener isn't installed after
gtag.js— fix the ordering.Confirm end-to-end in GA4 Admin → DebugView or Reports → Realtime.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Console shows relayed commands, GA4 shows nothing | Listener missing or registered before gtag.js initialised. |
| Nothing in the console at all | The form isn't actually in an iframe (the relay only activates in embeds), or the embed failed to load — check the iframe renders. |
| Events land but with no campaign/source attribution | Expected when only iframe-side GA runs. Attribution comes from your page's GA — which is why the relay exists. Install the snippet. |
Duplicate page_views | Don't re-fire relayed config/page_view calls into a page that already tracks its own page views — filter to args[0] === 'event' if you customise the listener (the standard snippet is fine for most setups). |