How the integration works
One page, read once. Everything below is true on every platform; the install guide and the conversion API build on it rather than repeat it.
The moving parts
Section titled “The moving parts”| Part | What it is | Who owns it |
|---|---|---|
The tag (w.js) | One async script on your storefront | You paste it; we ship it |
| Config | Which pages are baskets, where the block renders, how to read the cart, branding, caps, which offers are live | Held on your GWP account, fetched at runtime |
| Cart adapter | The one read the tag makes of your basket total | You choose the type; we configure it |
| Selection | The gift the shopper picked, held in first-party storage for 30 days | The shopper's browser |
Click id (clid) | The opaque token that ties selection → claim → conversion | Minted server-side by us |
| Claim moment | Where the shopper actually gets the gift link | Confirmation page, your order email, an email we send from your domain, or your own systems |
| Postback | The advertiser telling us the gift converted | The advertiser's server |
1. The tag
Section titled “1. The tag”<script async src="https://cdn.gwpingenuity.com/w.js" data-site="YOUR_SITE_KEY"></script>- Self-contained. A single IIFE bundle with no external dependencies and no dynamic imports. It does not fetch a framework, a stylesheet or a font.
- Async and non-blocking. It boots on
DOMContentLoaded(or immediately, if the document is already past that), and every entry point is wrapped so a failure can never surface on your page. - Boots once. A global flag guards against a double-inclusion (via a tag manager and the theme, say) booting two widgets.
data-siteis required. Without it (or without an API base) the tag logs nothing and stops. The site key is public.- The API base is derived from the script's own
srcorigin. The tag calls/v1/*and loads images from the same host it was served from, so one host entry (https://cdn.gwpingenuity.com) coversscript-src,connect-srcandimg-src. - It publishes exactly one public API on
window.GWP. That isselection(), a read accessor documented in §5, plus a boolean boot flag,window.__gwp_loaded__. A CI gate boots the built bundle and fails ifwindow.GWPever grows another key, and the widget's render tree lives in a closed shadow root, so page scripts cannot reach in and cannot be reached by our CSS.
Debugging: set window.GWP_DEBUG = true before the tag boots and it logs each decision it makes: site key, config, page type, slot, cart read, eligibility. It is silent otherwise.
2. How the tag knows a page is a basket page
Section titled “2. How the tag knows a page is a basket page”The tag does not guess. On every page it makes one call:
GET /v1/config?site=YOUR_SITE_KEY&url=<origin+pathname>and the server decides what that page is, by matching the pathname against the URL patterns held on your account:
- A pattern containing
*is compiled to a regex anchored to the whole path:*/cart,*/cart/*,*/basket,*/cart.php,*/order-confirmation,*/checkout/success,*/thank-you. So*/cartmatches/cart,/en-gb/cartand/checkout/cart, but not/collections/cart-accessories. - A pattern with no
*at all is treated as a plain substring of the path./carton its own would match/collections/cart-accessoriestoo. Every pattern should therefore carry a leading*/. (This is why the shipped defaults are all anchored globs.) ?is not a glob metacharacter and is not escaped either, so a pattern must never contain one. Query strings are stripped from the path before matching, so there is never a reason to write one.- A pattern starting with
!is an exclusion and always wins. The defaults exclude*/collections/*,*/products/*,*/blogs/*and*/pages/*so a product handle called "bag" is never mistaken for a basket. - The response carries
pageType: "basket" | "confirmation", or nothing at all. No page type, no render: the tag mounts nothing and goes quiet.
Two consequences worth internalising:
- Query strings and fragments are stripped before matching, and are never sent. The tag reports
origin + pathnameand nothing else, on every call. Storefront URLs carry?email=,?discount=and order tokens, and none of that leaves the browser. - Patterns are configuration, not code. If your basket lives somewhere unusual, the fix is a config change, not a redeploy.
The cart drawer
Section titled “The cart drawer”A slide-out mini-cart opens with no URL change, so it cannot be derived from the URL at all. It is declared with two CSS selectors instead:
rootis the drawer element (document.querySelector(root)),openWhenis evaluated asroot.matches(openWhen)to tell open from closed (e.g.[open],.active).
Both are optional and off by default. If a URL already resolves to a basket or confirmation page, that mount always wins. There is never more than one gift block on screen.
Staying awake
Section titled “Staying awake”Storefronts are not static documents, so the tag re-evaluates (debounced, ~150 ms). Two sets of signals wake it:
- Always on, every store: DOM mutations,
pushState/replaceState/popstate/hashchange,window.load, adataLayer.push, CMP consent decision events, and agwp:cartchangeevent you can dispatch yourself. - Drawer-enabled stores only: when a cart drawer is configured and the current page is neither a basket nor a confirmation page (i.e. the drawer is the only surface that could open), the tag additionally watches a set of theme cart events (
shopify:cart:lines-update,theme-drawer:open,openable-element:open,cart:refresh/cart:updated/cart:update, and Dawn'scart-updatePubSub topic) plus a MutationObserver scoped to the drawer element. On a real basket page those listeners are removed again.
If you are on a custom platform, gwp:cartchange is the one signal that works everywhere. Do not rely on dispatching a theme cart event. Re-evaluation is idempotent: it brings the page to the right state, it does not stack widgets.
Config responses are not cached, so a config change reaches shoppers on their next config fetch: the next page load or route change.
3. The cart adapter
Section titled “3. The cart adapter”The tag needs exactly one number (the current basket total) plus, ideally, the line items. It never writes to the cart to get it. You pick one of three adapters:
| Type | What it reads | Configure |
|---|---|---|
datalayer | window.dataLayer, walked newest-first: ecommerce.value, cartValue, cart_total, plus a currency from currency / currencyCode | Nothing. Shape detection is built in |
window | window.gwp.cart = { value, currency, items: [{ id, qty }] } | You publish the global |
dom | document.querySelector(totalSelector).textContent, parsed to a number | totalSelector, and a currency |
Rules that hold for all three:
- Read-only, and fail-soft. Every adapter is wrapped: a missing global, a selector that matches nothing, an unparseable price string all return
null. They never throw onto your page. - A failed read means "unknown", and the widget fails open. The store-level minimum-cart gate and each offer's own minimum are skipped when the total cannot be read, so the gift block can render even though the basket value is unknown. This is deliberate: the alternative (treating an unreadable cart as £0) would silently suppress every offer on a store whose selector broke. The practical consequence is the one to plan for: a broken cart read does not look broken. It looks like gifts being offered below your threshold. The minimum is only ever enforced when the adapter returns a number, so verify the read (install guide § verify) rather than assuming silence means it works.
- Where the number is used: the store-level minimum cart value, each offer's own minimum, and a cart snapshot recorded with the selection. It never affects what the shopper is charged.
On a confirmation page the tag also tries to read your order id from window.gwp.orderId, a configured orderIdSelector, a dataLayer transactionId / orderId / ecommerce.transaction_id, or an ?order= / ?orderId= query parameter. That id is used to join our records to yours for reporting. It is not part of attribution: if it can't be read, the gift is still claimable and still attributable.
4. Gift selection
Section titled “4. Gift selection”When the page qualifies (right page type, offers available, cart above the minimum, frequency caps not spent, consent satisfied), the tag renders the gift block into the configured slot (an explicit CSS selector if you gave one, otherwise a container it injects at a configured fallback position).
The shopper picks a gift. That does one thing on the wire:
POST /v1/select → { "ok": true, "clid": "clk_…" }and one thing in the browser: the selection is persisted for 30 days in first-party storage. That is localStorage, mirrored to a first-party cookie on the registrable domain (SameSite=Lax) so it survives the hop from www. to a checkout subdomain. Keys used: gwp_sel (the selection), gwp_ses (a session reference), gwp_shows (frequency-cap counters), gwp_attrs (a marker for the Shopify email path), gwp_gsync (§5(d) stores only: a short hash of the site and the signed-in customer's number, kept while we hold their gift list, so the tag knows it owes us the empty list), gwp_em (separate-email stores only, localStorage only, for at most 24 hours: the shopper's sign-in email address, sealed so that only we can read it; see §5(e)), and a transient __gwp_probe cookie used once to find the registrable domain and immediately deleted.
Details that matter when you are reasoning about behaviour:
- Without consent, shoppers still get gifts, and we keep only what the gift needs. The config fetch happens first (it carries only your site key and
origin + pathname). If the shopper has refused cookies, or has not answered your banner, the widget runs in essential mode: the gifts show, nothing is stored until the shopper picks one, and then only that choice is kept, for at most 7 days, to link the gift to their order, with the markers tied to it where your store uses them (gwp_attrs,gwp_gsync). If your server asks us for an order's gifts (§5(d)), that link also needs the signed-in shopper's customer number: while their device holds a gift, the widget sends the number to us with those gifts, and once more with an empty list when the last one goes (until then the device keepsgwp_gsync). We keep only a keyed hash of it, with their gift list while it holds a gift (at most 30 days after the last sync) and with each order number your server asks about (60 days), and use it only to put those gifts in that order's email. A signed-in shopper who never picks a gift sends nothing. No session identifier, frequency caps, A/B tests, views or impressions are recorded for them, and the sign-in email capture of §5(e) never runs. That relies on the strictly-necessary exception for keeping a record of the shopper's own selections. Underauto, an explicit refusal or an unanswered banner means essential mode;strictalso treats a page with no consent platform that way. You choose which consent category unlocks full mode (for example a OneTrust category), and you can switch essential mode off so the widget shows nothing without consent. If consent is later withdrawn, the identifier, the counters and any sealed sign-in email are erased, and the shopper's chosen gift is kept on the 7-day essential lifetime. With essential mode switched off, the chosen gift is erased too, and on a §5(d) store the widget sends the empty list once so that our copy goes with it. - Multiple gifts are supported (1–5 per order, 1 by default).
- Frequency caps are counted per surface, so the cart drawer and the cart page keep independent counters.
- The server re-checks everything. A crafted
/v1/selectfor an offer your store isn't assigned, a paused offer, or a cart below the offer's minimum is refused. - Browser POSTs are origin-gated.
/v1/select,/v1/events,/v1/clickout,/v1/associate,/v1/gift-syncand/v1/redeemall check theOriginheader against your registered domains and return403otherwise.
5. The claim moment
Section titled “5. The claim moment”The claim moment is where the shopper is handed the link that redeems the gift. It is the single most important thing to get right in an install, because it is where the shopper actually receives what they were promised — and it is the one part that differs by platform. There are five concrete paths, and a store can use more than one.
(a) The confirmation page
Section titled “(a) The confirmation page”If your confirmation page URL is registered as a confirmation page type, the tag renders a redemption card there for the persisted selection. Pressing Claim does exactly two things:
POST /v1/clickout(recorded, fail-soft),window.location.href = <claim URL>.
The tag also posts /v1/associate to link your order id to each click id. This is for reporting; it does not affect whether the conversion is attributed.
(b) Your order-confirmation email (Shopify)
Section titled “(b) Your order-confirmation email (Shopify)”GWP does not send this email. You do. On Shopify stores with the order-email channel enabled, the tag stages the selection on the cart as attributes, and your notification template renders the claim block:
| Attribute | Value |
|---|---|
gwp_gift_count | "0"–"5", as a string |
gwp_gift_{i}_title | Gift title as shown on the card (i is 1-based) |
gwp_gift_{i}_advertiser | Advertiser name |
gwp_gift_{i}_claim_url | Absolute claim URL carrying that gift's click id, byte-identical to the on-page link. May be "" |
gwp_gift_{i}_image | Absolute image URL, or "" |
The write is a POST /cart/update.js on your own origin, with the shopper's cart cookie, carrying an attributes object and nothing else. Slots 1..5 are always written (unused ones blanked in the same request), so a shrunk selection can never leave a stale claim link behind, and a revoked selection triggers a clearing write (gwp_gift_count: "0"). The whole path is fail-soft: a failed write never surfaces on your page and never blocks the selection. The Liquid snippet that reads these attributes is reproduced in full in the Shopify platform guide.
(c) The generic hook: every platform
Section titled “(c) The generic hook: every platform”Not on Shopify? Use the platform-neutral hook. It fires on every platform, regardless of any Shopify-specific setting:
document.addEventListener("gwp:selection", (e) => { // e.detail.gifts = [{ title, advertiser, claimUrl, imageUrl }] // claimUrl / imageUrl may be "" — guard before rendering a button.});
// Or read the current state at any time:window.GWP.selection(); // → { gifts: [ … ] }The event fires on every selection change made in the widget (add, remove, replace), plus the initial restore of an existing selection, and the two revocations the widget performs on its own: the cart falling below the store minimum, and consent being withdrawn. Those two arrive as { gifts: [] } once the widget has unmounted and cleared its own stored state, so the accessor, the event and (on Shopify) the cleared cart attributes all say the same thing. Both the event and the accessor are scoped to pages where the widget actually mounted: on any other page the accessor returns { gifts: [] } and no event fires, even though a selection is persisted. So read the payload on the basket page and carry it forward in your own state (an order property, a dataLayer push, your own API).
The payload fields map one-to-one onto the gwp_gift_{i}_* cart attributes in §5(b), so an email template built from either source renders the same thing.
(d) Your server asks GWP
Section titled “(d) Your server asks GWP”For stores whose server sends the confirmation email while it creates the order, and would rather ask us for the gifts than carry them through checkout. You tell us which dataLayer key carries the signed-in customer's number. While a signed-in shopper's device holds a gift, the tag keeps our copy of their gift list in step with it: it sends the list with that number to POST /v1/gift-sync on each page load and after each change, and an empty list once when the last gift goes, so that we delete our copy. A signed-in shopper who holds no gift sends nothing. When your server creates the order, it calls POST /v1/api/order-gifts with the order number and the customer number, and puts the gift block it gets back into the email. A gift belongs to one order: once an order takes it, it leaves the list, and the tag drops it from the device. We keep a keyed hash of the customer number, never the number: with the gift list while it holds a gift, and with each order number your server asks about for 60 days. The request and its rules are in the claim moment guide.
(e) We email the gift link
Section titled “(e) We email the gift link”For stores that would rather not change their own emails. After the order, we email the shopper one claim link per gift, from your store's own email domain once it is verified with us. The tag needs two things for that, the shopper's address and the order number:
- On your sign-in page, and only in full mode (never in essential mode), the tag reads the one email field you name when the sign-in form is submitted, holds the address in memory, and keeps it only once the sign-in has evidently worked within 20 seconds: the account hash you publish in the
dataLayermatches, or the shopper leaves the sign-in page. Otherwise it is dropped. It never reads a password or any other field. It seals the address on the device to our key, so nothing on the device or your page can read it back, and keeps the sealed copy ingwp_emfor at most 24 hours. - On the confirmation page, it reads the order number from the element or pattern you configure and sends it, the click ids and the sealed address to
POST /v1/redeem. Without a sealed address it asks the shopper where to send the gift, and sends what they type.
We keep the address sealed, with a keyed hash of it for a per-address daily cap, and delete it 90 days after the request.
6. The click id
Section titled “6. The click id”clk_ + 32 lower-case hex characterse.g. clk_4f8c1d2b3a90f1e7c6d5b4a39281706f (36 characters total)- Minted server-side, one per gift selection, at
POST /v1/select. The browser never invents one. - Opaque. It encodes nothing about the shopper, the cart or the price.
- Carried on the claim URL. By default it is appended as
gwp_clid:
https://advertiser.example/offer?gwp_clid=clk_4f8c1d2b3a90f1e7c6d5b4a39281706f- When an offer is served through an affiliate network, it rides that network's own sub-id parameter instead (
clickref,sid,u1,subId1and the like), appended raw so an already-encoded deep link is not re-serialised. - One composition, everywhere. The on-page claim button, the Shopify cart attribute and any email you build from the generic hook all carry the identical URL for a given gift.
- HTTPS only. A redemption URL that is not
https:, that doesn't parse, or that points somewhere the live config doesn't vouch for produces no link at all rather than a link somewhere unexpected.
7. How commission is attributed
Section titled “7. How commission is attributed”selection claim conversionPOST /v1/select → claim URL carries → advertiser POSTs the clid backmints the clid ?gwp_clid=clk_… POST /v1/conversionThe advertiser's server posts the click id back with their own order id:
POST /v1/conversion
{ "advertiser_key": "…", "secret": "…", "gwp_clid": "clk_4f8c1d2b3a90f1e7c6d5b4a39281706f", "advertiser_order_id": "ORDER-10231", "order_value": 42.00, "currency": "GBP"}What happens to it, in order:
- Authentication. The secret (body field or
X-GWP-Secretheader) is checked against the advertiser's stored hash. Server-side only, never in browser code. Bad secret →401. - Known click id. The clid must exist and belong to that advertiser → otherwise
rejected_unknown_clid. - Idempotency.
(clid, advertiser_order_id)is the dedupe key. A repeat isrejected_replayand returnsok: true. A retry is a safe no-op, not a double credit. - Attribution window. Measured from the moment the shopper selected the gift, not from the click, against the window agreed for that advertiser. Past it →
rejected_outside_window. - Validation. An order id is required; a percent-model offer needs a positive
order_value;currency, if sent, must be an allow-listed currency and match the retailer's settlement currency (there is no FX conversion). - Credit. The commission is computed and split between the retailer and GWP. A conversion may be held before it becomes payable: briefly, for a settlement hold, or longer if it is set aside for review; a reversal inside that period nets it out before anyone is paid, which is why there is no clawback on the retailer side. Reversals go to
/v1/reversaland are themselves idempotent.
Two things attribution does not depend on: your order id (that is a reporting join), and any A/B variant the shopper happened to see (an analytics dimension only — the split is identical either way).
Full parameter and error tables are in the conversion API reference.
8. What the tag deliberately does not do
Section titled “8. What the tag deliberately does not do”This is the list to hand your security or platform reviewer.
It never alters items or prices. The only thing it adds to your page is its own container element and the shadow host inside it, both removed on unmount. The only write it makes to your cart is on Shopify stores with the order-email channel switched on, and that write carries a gwp_* attributes object and nothing else: no line items, no quantities, no discounts, no shipping. It never touches checkout.
It patches two host-page functions, and discloses both. The tag wraps history.pushState / history.replaceState to notice SPA navigation, and dataLayer.push to notice late cart hydration. Both wrappers call the original first and return its value, and neither inspects the arguments. That is the whole of it: no other host global, prototype or built-in is modified.
It is fail-silent. Every boot path, evaluation, DOM query, storage access, cart read and network call is wrapped. A merchant-authored CSS selector that is invalid throws a DOMException inside our try, never on your page. A rate-limited or failed config fetch leaves the current state alone and retries later rather than tearing a live widget down. Errors are reported to our own /v1/error endpoint (message, stack, version and origin + pathname only) and never to your console unless you turn on GWP_DEBUG.
It loads nothing from third parties. The bundle has no runtime dependencies and pulls in no CDN script, stylesheet, webfont, iframe or tracking pixel. Its entire network surface is:
| Request | Where it goes |
|---|---|
GET /v1/config, POST /v1/events, /v1/select, /v1/clickout, /v1/associate, /v1/gift-sync, /v1/redeem, /v1/error, /v1/retention-optin | The origin the tag itself was served from (cdn.gwpingenuity.com) |
| Gift and brand images | The same origin. Image paths from config must be single-slash origin-relative; data:, http(s):// and protocol-relative values are refused and the tile falls back to a monogram |
GET /cart.js, POST /cart/update.js | Your own origin, Shopify order-email path only |
Fonts come from a font-family name in your branding config. No font file is ever fetched.
It sends no third-party cookies, and no page PII beyond two values, each only if you switch on the feature that needs it. If your server asks us for an order's gifts (§5(d)), the tag sends the signed-in customer's number from the dataLayer key you named, only while the shopper's device holds a gift (or once, to remove them), and we keep only a keyed hash of it. If we email the gift link for you (§5(e)), the tag reads the email address the shopper signs in with (the one field you name, never the password, and only in full mode), seals it on the device so that only we can read it, and sends it with the order number to /v1/redeem; we keep it sealed for 90 days. Storage is first-party only (localStorage plus a first-party cookie on your registrable domain). Reported URLs are origin + pathname; query strings and fragments are dropped in the browser before anything is sent, and again at ingest.
It renders nothing where it shouldn't. Every one of these is "render nothing": no page type, no offers, cart below the minimum, frequency cap spent, consent denied or unanswered, no safe anchor to mount into. The ones that occur before a mount leave the DOM exactly as they found it.
Switching it off, and who holds which lever
Section titled “Switching it off, and who holds which lever”Reviewers ask this. There are two levers and they are not both yours. Do not take a central kill switch to a change board as though it were a control you operate.
| Lever | Whose | How fast | What it does |
|---|---|---|---|
Remove the tag: delete the <script> from your template, or pause the tag-manager tag | Yours. No involvement from us at all | As fast as your own deploy or tag-manager publish | Total and final: nothing of ours executes. This is your real emergency control, and it is the one to write into a change record |
| Narrow the config: page-detection patterns, eligibility rules, the cart-drawer toggle, consent mode | Yours, from your dashboard (or ask us) | Next config fetch | Changes where and when the block may render. Not an off-switch, but it is how you take it off one surface without a deploy |
| The kill switch: per store, and network-wide | Ours. GWP staff only. It is not on the retailer dashboard, and there is no self-serve equivalent | Next config fetch, with no redeploy on your side | The config endpoint starts returning null, so the tag mounts nothing and disarms its document-wide observer. Every flip is audit-logged |
To ask us to use ours: email support@gwpingenuity.com with [INTEGRATION] in the subject and your site key. Be aware of what that is and is not: a monitored mailbox we aim to answer within two working days, not a 24/7 incident line, and we do not publish an emergency-response SLA today. If your risk assessment needs a same-hour off-switch, removing the tag is the one that meets it. Make sure whoever owns your storefront deploys knows that, and treat any faster action from us as a courtesy rather than a commitment.
What is true in all three cases: a shopper mid-page keeps whatever is already on screen. None of these levers reach into a page that has already rendered; they take effect on the next config fetch, which happens on the next page or route change.
Versioning, pinning and SRI
Section titled “Versioning, pinning and SRI”The short version: there is no version-pinned URL and no Subresource Integrity hash today. Here is exactly what that means and what we offer instead.
/w.jsis a single, unversioned route. A new release replaces the bytes at the same URL. There is no/w/1.4.2.jsto pin to and we do not offer one.- Updates reach shoppers within minutes. Our servers pick up a new release within five minutes, and
/w.jsis served withCache-Control: public, max-age=300, so a browser that already holds a copy fetches the new one within five more. Plan for up to about ten minutes for every new page load to be on a new build. A tab that is already open keeps the bundle it loaded until the shopper navigates. There is still no update prompt and nothing to redeploy on your side. - You can add an
integrityattribute, and you should know what happens if you do. It will be valid until our next release and will then stop the bundle loading. That failure is safe for your storefront (a blocked script is exactly the fail-silent case the tag is built around, your page is unaffected and nothing throws), but it is silent: you simply lose the gift block, with no error anyone is watching for, until someone updates the hash. We do not publish release notifications, so nothing would tell you. If your policy requires SRI, tell us before you install rather than pinning quietly.
What we do offer: a self-identifying bundle. Every GET /w.js response carries an X-GWP-Widget-Version header whose value is the first 12 lower-case hex characters of the SHA-256 of the exact bytes in that response. You can verify it yourself, from any machine, with no cooperation from us:
# 1. what we say the bytes arecurl -sI https://cdn.gwpingenuity.com/w.js | grep -i x-gwp-widget-version# x-gwp-widget-version: <12 lower-case hex characters>
# 2. what the bytes actually arecurl -s https://cdn.gwpingenuity.com/w.js | shasum -a 256 | cut -c1-12# the same 12 characters — by construction, on every releaseThat gives a reviewer three things without a pinning mechanism: a stable identity for the build they actually reviewed, a one-line check that tells them whether it has changed since, and something concrete to quote in a change record. Our own deploy and uptime checks assert against the same header, so it is load-bearing for us too rather than decoration.
The build behind that hash is deterministic. The published bundle is produced by one command from one source commit; it embeds no build timestamp and no random identifiers, and nothing environment-specific is baked into it (it derives its API base at runtime from its own <script src>, as in §1). The same commit therefore reproduces byte-identical output, which is what makes the hash a genuine identity rather than a build counter. Our publish step also refuses to ship when the bundle it just built and the copy about to be uploaded disagree. One limit: our source is not public, so this is a property of our release process, not something you can re-run. The part you can re-run yourself is the hash check above.
No changelog is published today. If you need to know what changed between two builds (for a change record, or to decide whether a re-review is warranted), ask us and quote the two X-GWP-Widget-Version values. That is a person answering an email, not a feed you can subscribe to; we would rather say so than imply otherwise.
Where next
Section titled “Where next”- Retailer's developer: Install the tag covers snippet placement, page registration, adapter examples, the claim moment, browser verification steps, troubleshooting and CSP. Then the platform guides for Shopify, custom/headless and everything else.
- Advertiser: Conversion API has the endpoint, auth, parameters, response and error shapes, idempotency, attribution window, reversals, worked examples.
- Anyone with a question these pages do not answer: how to reach a person — email
support@gwpingenuity.comwith[INTEGRATION]in the subject, and what to put in it.