Google Tag Manager

One Custom HTML tag loads pb.js on every page. Conversion events are more Custom HTML tags on your existing triggers.

Install pb.js

  1. GTM workspace → TagsNew Custom HTML.
  2. Name it AdsPing — pb.js and paste:
gtm-custom-html.html
<script async src="https://api.adsping.io/pb.js" data-pixel-id="YOUR_PIXEL_ID"></script>
  1. Trigger: Initialization — All Pages (loads before your other tags).
  2. Save → Submit → Publish.

Fire events from GTM

Conversion events are additional Custom HTML tags calling adsping('track', …), attached to the GTM triggers you already have. Example — a Purchase tag triggered on your order-confirmation page, pulling values from GTM variables:

html
<script>
  adsping("track", "Purchase", {
    value: {{Order Total}},
    currency: "USD",
    order_id: "{{Order ID}}",
    event_id: "{{Order ID}}-purchase",
  });
</script>

The event_id matters more here than anywhere else. A GTM tag fires off a trigger, and the same trigger often reaches us by a second road — read the next section before you publish.

Every event works this way — see the event pages for the full payload of each.

Avoid double-counting

AdsPing listens to window.dataLayer and turns the standard GA4 e-commerce pushes into events on its own. That is the trap in the setup above: your Purchase tag fires off the purchase push, and we already read that push. One sale, two events. A Meta or TikTok tag on the same trigger makes it three, because we mirror those calls too.

Every copy is a real conversion as far as the ad platform is concerned, so revenue and conversion count both inflate. Two ways to stop it, and the first is usually right.

Give every road the same event id

Put a stable id on the tag — the order number works, since one order is one sale — and pass the same value to any pixel tag firing beside it. We drop the second arrival for 24 hours, and Meta deduplicates the browser copy against the server one.

html
<script>
  var eid = "{{Order ID}}-purchase";
  adsping("track", "Purchase", {
    value: {{Order Total}}, currency: "USD",
    order_id: "{{Order ID}}", event_id: eid,
  });
  // if a Meta Pixel tag fires on this trigger too:
  fbq("track", "Purchase", { value: {{Order Total}}, currency: "USD" },
      { eventID: eid });
</script>
One id per sale, not per tag. Reusing an id across different events — a Purchase and an AddPaymentInfo sharing the order number — makes us drop the second one as a duplicate. Suffix them: {{Order ID}}-purchase, {{Order ID}}-payment.

Or switch the bridge off

If your container already reports everything you need, take our reader out of the picture entirely:

html
<script async src="https://api.adsping.io/pb.js"
        data-pixel-id="YOUR_PIXEL_ID"
        data-ignore-datalayer="true"></script>

Your dataLayer is untouched — GA4 and every other consumer keep working. AdsPing just stops reading it, so the only events we record are the ones your own adsping('track', …) tags send, plus the browser pixels we mirror with a shared event id.

Check what disappears first. Containers usually push more event types than they have AdsPing tags for — a add_payment_info or view_item push with no tag of its own is an event you were getting for free and will stop getting. Open Diagnostics, list the event types you receive today, and make sure each one has a Custom HTML tag before you publish this.

Identity from the dataLayer

When a push carries user_data, we read the email and phone out of it. But window.dataLayer is a shared array — your container, an agency's tags, a chat widget and an A/B tool all write to the same place, and a push carries no proof of who wrote it.

So by default we treat what we find there as a guess: it rides the event it came with, it fills a field nothing else supplied, and it never overwrites an identity you sent with adsping('identify', …). It is also not kept for the visitor's next visit.

If your own container is the only writer, say so and those pushes count as statements — kept for the visitor, applied to later events, exactly like an identify call:

html
<script async src="https://api.adsping.io/pb.js"
        data-pixel-id="YOUR_PIXEL_ID"
        data-trust-datalayer="true"></script>
Reach for this when the dataLayer is your only identity source — tag installed, no identify call written, and match quality flat between visits. Leave it off when another company's tags live in the same container. Either way a constant placeholder address is refused: a container that fills the field with [email protected] when it has no real address hands every visitor one identity, so we drop it and tell you in the response.

Verify

  • Use GTM Preview mode — the AdsPing tag shows under Tags Fired on page load.
  • The pixel detail page in AdsPing shows Last event: a few seconds ago.
Once pb.js is firing, head to Connect Meta to start forwarding events.

Was this page helpful?