Egenverk Conversion Relay

Ändringslogg för Egenverk Conversion Relay

Vad som ändrats i varje tillägg, senaste först.

Ändringsloggens text är på engelska, som i tillägget.

Atom-flöde

Version 1.4.0

Ändrat

  • The server reads marketing consent and the ad click from first-party cookies on requests WooCommerce makes anyway, instead of waiting for the browser to send them to ?wc-ajax=egenverk_conversion_relay: every page view and wc-ajax request of a visitor with a WooCommerce session or a login (init priority 20, before Kustom's confirmation completes a payment at 999), a cart addition, the order review, the checkout and the moment before a payment completes. Cookiebot's CookieConsent is parsed by the plugin itself: -1 (a visitor outside a consent region, which Cookiebot's script also reports as a grant) is a grant, 0 a refusal, and otherwise only the marketing flag of the object Cookiebot writes is read, up to 4 kB; anything else is unknown. WP Consent API's wp_consent_marketing cookie is read directly, because wp_has_consent() on the server grants when no consent type is set or, in an opt-out region, when there is no cookie: deny is a refusal, allow a grant only when wp_has_consent() agrees, anything else unknown. The click comes from the pixel SDK's __oppref, which the SDK replaces on every ad landing, then from the plugin's own cookie egenverk_conversion_relay_click, and the browser reference from __obref; each is checked like the endpoint checks them, and must be valid UTF-8.
  • The consent record is written through the same path as the endpoint, so a refusal still withdraws queued purchases at once, but only when consent or the click changed, or when the record has less than two days left; a copy in the WooCommerce session answers every other request without reading the record (a logged-in customer without a session cookie gets no copy, so no session is stored for it). No session is ever opened for this, unknown consent never changes a record, a refusal waits up to five seconds for a record another request holds, and a click the granted record holds is kept when the cookies lack it, as the browser keeps it until a refusal.
  • A cart addition queues its items_added event when the consent cookie in that request grants, instead of requiring a record an earlier request had stored.
  • The response to a granted cart addition hands the session's additions of the last two minutes to the browser in a two-minute cookie, egenverk_conversion_relay_added (host-only, not HttpOnly, SameSite=Lax, Secure on https, path /), each with the event id the server generated, the product and variation, quantity, amount and currency. Carrying every recent addition, rather than the ones the request brought back, keeps an addition whose answer the browser had not read when the next one was posted, and never sets again what the browser sent. The value is at most 1 kB encoded: product names are dropped first, then an addition too large on its own, then the oldest; when nothing fits the cookie is removed. The script reads it on added_to_cart, wc-blocks_added_to_cart and when a page loads, which covers an addition that reloads the page, measures items_added with each event id it has not measured in the last five minutes, and removes the cookie. With refused consent, the pixel off or the event not selected it removes it unmeasured; while consent is unknown, or another script owns the pixel SDK, it leaves it. A refusal the server sees removes it. A refusal also forgets the ids measured, and empties the session's additions, so a later grant does not hand earlier ones over.
  • An order is stamped with the consent in the checkout request's own cookie; an order placed without a readable consent cookie is unknown, whatever an older record says. The session's unpaid order is re-stamped on every order review and consent change, never above what the request's cookie says. Every capture, through the payment or a paid status, now needs that stamp to be a grant as well as the record, and none happens in a request whose consent cookie refuses. A grant that reaches the server only after the payment completed no longer makes the purchase count, as it could through the payment until now: consent is taken as it was when the order was placed and paid.
  • The ad click from the landing URL is kept in the first-party cookie egenverk_conversion_relay_click as <expiry>.<click> (host-only, path /, SameSite=Lax, Secure on https, not HttpOnly), 30 days from the click, instead of in localStorage, which the server cannot read. The script writes it only with a grant and never writes the same click again, so a reload does not extend it; a click 1.3 kept in localStorage moves to the cookie with its expiry. With a grant and a click the server sets the same cookie, since Safari keeps a cookie a script writes for seven days only. The expiry it carries is kept in a new session or for a new account, a click only the pixel SDK's __oppref carries keeps the expiry the session saw before, and a cookie the browser already holds is not sent again. A refusal removes it on both sides.
  • While the server keeps a grant for the browser it sets two marker cookies until the record expires, host-only, SameSite=Lax, Secure on https: egenverk_conversion_relay_record, HttpOnly, which names the record with a signature, and egenverk_conversion_relay_marker, which the script reads and which holds only the expiry, so no script sees a stable identifier. A refusal made on a page, which the next request WooCommerce makes would carry anyway, is sent at once as one small request (client: 4, action: refuse, with keepalive, so it survives the tab being closed) when the server may hold a grant: with a cart, a login or that marker. It is sent once per change to a refusal on that page, and on start-up for a refusal made before the script ran while the marker is there; never for a refusal the page request itself carried, and never in answer to another tab. The HttpOnly marker's signed reference to the record lets the refusal reach it after the WooCommerce session has expired, as it does two days after a guest's last visit; it is signed with a key derived from the auth salt, so rotating the salts invalidates every marker, which is then ignored and removed, and such a late refusal is dropped while the record expires by itself. The endpoint answers the refusal with an empty result, never throttles it, removes both markers, and ignores it when the request's consent cookie grants again; the script then sends a refusal made before it started at most once per marker value in a tab session. The order received, pay-for-order and account pages, where nothing is measured, carry the script in a refusal-only mode: no pixel, no page events and no cart additions, only the refusal and its cleanup.
  • The script no longer sends grants, clicks, cart and login changes, the preparation before the first cart addition, the daily and 30-minute repeats or the forced sync before checkout, and it no longer listens to other tabs. The synchronization state older versions kept in localStorage (egenverk_conversion_relay_synced, egenverk_conversion_relay_granted and their first names) is removed on the first page view. The pixel, the page events, the configuration in the page and the diagnostics are unchanged. A page cached with HTML from before 1.2.1 measures nothing until the page cache is purged.
  • The endpoint still answers scripts from 1.2.x and 1.3.x (client 2 and 3) as before for one version, since pages and browser caches keep them for a while: grants, clicks, refusals, the cart preparation and the throttling. That code goes in 1.5.0.
  • A refusal the record lock kept from being stored is logged as a warning (consent_lock_busy).
  • A guest who logs in or creates an account at checkout takes the consent record and click to the account (WooCommerce 8.8 and later), unless the account holds a newer record.

Åtgärdat

  • The changelog of 1.2.1 and 1.3.2 promised one synchronization before an order is placed, so that a refusal made on the checkout page reaches the server first. The script hooked WooCommerce's checkout_place_order event on the checkout form, but Kustom Checkout posts the order straight to ?wc-ajax=checkout from its own script, so that event never fires and the synchronization never ran for an order placed through Kustom: a refusal made on its checkout page after an earlier grant could leave the order stamped with the grant. The checkout request itself now carries the consent cookie, and the order and the record are both written from it in that request, whichever script posts the order; the hook is gone. A refusal made after the order was placed is read on Kustom's confirmation page before that request captures the payment; if Kustom's server push captured it first, the refusal withdraws the queued purchase if it reaches the server before the purchase is sent, a few seconds after payment.
  • Since 1.0.0 a purchase captured through woocommerce_payment_complete or Kustom's payment callback was decided by the customer's consent record alone, never by the consent recorded on the order when it was placed, so an order placed with consent unknown or refused was still exported when an older grant was on record. Only the paid-status path (1.2.0) checked the order.

Version 1.3.3

Åtgärdat

  • With the pixel on, the request budget of 1.3.2 (1.2.1 on the old slug) still cost about 0.3 requests per page view on a live store instead of the few its tests measured: a visitor with consent sent one or two on almost every page, a few seconds after it loaded. The script queued a refusal for the pixel SDK before starting it, and the SDK deletes its browser reference (the __obref cookie) on a refusal and creates a new one when consent follows. Every page view therefore got a new reference, which the script counts as something the server has to hear. The reference sent to OpenAI also changed on every page, so a visitor's page views and purchase could not be tied together through it. The same refusal deleted the SDK's stored click (__oppref) on every page, so pixel events after the landing page lost it, and stored the refusal where the SDK in the visitor's other open tabs reads it, switching their pixel off. The SDK is now started with the grant it is only ever created with, and the reference lasts; a new test replays the SDK's cookie handling over ten page views.

Version 1.3.2

Åtgärdat

  • A store running 1.1.2 received 248,000–286,000 requests a day to ?wc-ajax=convert_relay, 25–47 % of all PHP requests, and 1.2.0 still about 1.1 per page view, about 3 per page for an active visitor with consent, each answered with 5.5 kB. Every one is an uncached POST that boots WordPress and WooCommerce, and with a small PHP-FPM pool the site and wp-admin slowed down. The cause: the script sent a bootstrap and a sync on every page view of a visitor with consent, a cart, a login or a stored grant, remembered what it had sent only for the page it was on, fetched the configuration, which is the same for every visitor, from the server each time, and received all of it in every answer.
  • What the server last received is now kept in the browser (localStorage key egenverk_conversion_relay_synced; the state 1.2.1 kept under convert_relay_synced moves there, so updating from 1.2.1 costs no request): the consent state, the click, whether there was a cart and a login, and an opaque marker of the session it was stored under. A request goes out only when one of those changed: a grant or a refusal (unknown consent is never sent on its own), a new or changed click, a cart or login appearing or going, a cart addition and the forced sync before a classic checkout. An unchanged state is repeated once a day, before the server forgets the record after 30 days, and every 30 minutes for a logged-in customer, whose record every device of the account shares. A visitor without a server session sends nothing further until a cart or a login opens one. Measured by the new tests: 0 requests for 10 page views with unknown or refused consent, 1 for 10 page views after a grant, at most 2 for the first cart addition, 1 before placing an order.
  • The configuration every visitor shares (active, pixel, Pixel ID, adapter, events, log level) is printed into the page with a version; the page stays identical for every guest, so it can be cached. An answer carries the configuration only when the page was rendered with other settings, otherwise just the session marker and the cart events.
  • The bootstrap and the sync are one request. The session token it needed was handed out to any request that passed the origin and header checks, so it protected nothing further; writes are bound to the WooCommerce session cookie as before, and the diagnostics endpoint no longer asks for a nonce either. The server answers a state it already holds without taking the lock or writing, and touches the session only when there are cart events to collect.
  • A browser whose ad blocker stops the pixel SDK reported pixel_load_failed on every page view, each report another request that boots WordPress; it now reports each error once a day.
  • A tab that sees another tab remove the stored click no longer answers that tab's refusal with its own older grant.
  • Pages cached with an older script keep working for one version: 1.2.0 and 1.3.0 scripts still get a token, a log nonce and the answers they read, and 1.2.1 scripts calling the endpoint by the first name are answered as current ones. A page cached with 1.3.0's HTML asks for its configuration once per view until the page cache is purged.

Version 1.3.1

Ändrat

  • The Egenverk symbol is redrawn with the three bars centred in their square, in the admin header signature, the brand files of the UI kit (assets/egenverk-ui/brand/), the product icon and the wordpress.org banner and icons.

Version 1.3.0

Ändrat

  • Slug, folder, main file (egenverk-conversion-relay.php), text domain and translation files are egenverk-conversion-relay; namespace Egenverk\ConversionRelay; constants EGENVERK_CONVERSION_RELAY_*; options, transients, nonces, Action Scheduler hooks and group, script handles and JavaScript globals use the egenverk_conversion_relay / egenverk-conversion-relay prefix. The WooCommerce log source is egenverk-conversion-relay. Author Egenverk, https://egenverk.se.
  • Activation deactivates Convert Relay (any folder whose main file is convert-relay.php), which would otherwise measure every event a second time; while both are loaded, this plugin stays idle and says so. Settings, the encrypted API key, the validation result and history and the snapshot warning move to the new option names once per site (activation, and first load on sites a network activation did not reach); values already under the new names win. Jobs queued under the old Action Scheduler hooks are removed; the maintenance job reschedules pending deliveries.
  • The tables {prefix}convert_relay_*, the order meta _convert_relay_* and the encryption context keep their names, so queued purchases and orders need no migration.
  • Kept for one version: the wc-ajax endpoints convert_relay and convert_relay_log and the X-Convert-Relay header for pages cached with the old script; the filters convert_relay_* (through apply_filters_deprecated); the CONVERT_RELAY_API_KEY constant and environment variable. The browser moves the stored click and grant marker from the old localStorage keys.
  • The admin page moved to WooCommerce → Settings → Conversion Relay, with the sections Settings, Validation & log and Delivery status, and uses the Egenverk UI kit (assets/egenverk-ui/, 1.1.0) with the product icon and the "by egenverk" signature. Settings are saved through WooCommerce's settings form; validation and retry post to their own actions with their own nonce. Old admin URLs redirect to the tab.
  • readme.txt describes the plugin as the connection between WooCommerce and ChatGPT Ads; new tags; screenshots, banner and icon in .wordpress-org/.
  • Uninstall with erase also removes the options of the old name.

Version 1.2.0

Ändrat

  • Purchases are captured on woocommerce_payment_complete and on the order statuses processing and completed, whichever comes first, so cash on delivery, bank transfer and orders marked paid by hand are measured. A capture through a status also requires the consent recorded on the order while it was unpaid to be a grant. WooCommerce Subscriptions renewals are skipped; the filter convert_relay_capture_order can exclude further orders.
  • Diagnostics are written to the WooCommerce logger (source convert-relay) instead of the plugin's own .php files in uploads, which security scanners flag. The Validation & log tab links to WooCommerce → Status → Logs. The files, directories and options of the file logger (convert_relay_log_suffix, convert_relay_log_error, convert_relay_log_truncated) are deleted on upgrade; the size budgets, their filters and constants are gone.
  • A page contacts the server only when the visitor grants consent, has a cart, is logged in, or the browser holds a grant the server stored (localStorage marker convert_relay_granted, removed by a refusal or when the server has no session). Before, every page view sent an uncached bootstrap request, even for visitors with unknown or denied consent. Consent given later on the page bootstraps and sends the page events at once.
  • The admin notice about a missing consent integration appears only when measurement is enabled, and only on the Plugins screen and the plugin's page.
  • convert_relay_settings and convert_relay_version are autoloaded, and the maintenance schedule is checked once an hour instead of on every request.
  • With a persistent object cache the synchronization throttle counts there instead of in transients.
  • Admin input is unslashed and sanitized before validation; the delivery states, the header label and a Settings link on the Plugins screen are translatable.
  • phpcs.xml uses WordPress-Extra and PHPCompatibilityWP for PHP 7.4+. Header WC requires at least: 8.2.

Åtgärdat

  • An invalid Pixel ID or API key, or unavailable encryption, stopped the save with an error page and discarded the rest of the form. The rest is saved now and a notice names the rejected field. Saving shows a confirmation.
  • A key saved before the site's security keys changed silently read as empty; a notice now asks for it again.
  • Tables were only created on activation, so a network activation left subsites without tables and a future schema change would not reach sites that update automatically. The upgrade routine runs dbDelta on every version change, per site.
  • A database without GET_LOCK skipped every purchase without a trace. It is logged as an error and shown under Configuration status as Database locks.
  • The snapshot failure warning stayed forever; it clears when the next purchase is queued.
  • The consent endpoint accepted only the home_url origin, so a site whose site_url differs got a silent 403. Both are accepted, the filter convert_relay_allowed_origins adds more, and a rejected origin is logged at most once an hour.

Version 1.1.2

Åtgärdat

  • A refusal of marketing consent could fail to reach the server. When a session had exceeded the synchronization limit, every further request from the page was answered 429, and a page whose very first request was refused never became ready: it could not even read the consent banner, because the adapter came from the server's answer. A visitor who then withdrew consent on the checkout page placed the order with the earlier grant still stored, so the purchase could be exported, and the pixel was not switched off either. A refusal from a current script is no longer held to the synchronization limit, since it can only stop measurement and is sent only when the state changed; its own limit of 300 a minute per session (convert_relay_refusal_limit) only stops a script that repeats refusals to write to the database. A refusal that fails after a 429 is retried, and it removes the stored click even when the server configuration never loaded. A throttled page still becomes ready with measurement off, switches the pixel off on a refusal and sends that refusal, but no grants. The page configuration names the adapter, so consent can be read before the server answers.
  • Log files could be downloaded on servers that do not read .htaccess, such as nginx, where .php in uploads is served as text: the directory name was fixed, so the daily file names were predictable. The directory is now convert-relay-logs- followed by 24 random hexadecimal characters kept in the option convert_relay_log_suffix. Files in the directories used before, wp-content/convert-relay-logs/ and wp-content/uploads/convert-relay-logs/, are deleted on upgrade together with their guards, and so are directories under another random name. The name is created by the upgrade routine; uninstall removes every directory of this form.
  • On a multisite network, uninstalling with erase selected removed only the main site's tables, settings and logs, although every site that activated the plugin has its own. Each site is now handled on its own erase setting.

Version 1.1.1

Ändrat

  • The browser sends a synchronization request only when consent or attribution changed since the last successful one, and at most ten per page view. Cart additions and checkout submission still always synchronize, so their events are collected. After a 429 response the page stops synchronizing.
  • A page without a WooCommerce session asks the server once and not again until a cart action, since the answer cannot change before then.
  • The endpoint limits synchronization requests to 60 a minute per WooCommerce session and, for scripts older than 1.1.1 without a session, 120 a minute per address, adjustable with the convert_relay_sync_limit and convert_relay_sync_address_limit filters; the address is only kept as a keyed hash. The first refusal in a window is logged as a warning. The current script identifies itself and receives a 429. Tabs still running an earlier script, which retries on every storage event whatever the status, receive a successful answer with measurement switched off instead: that script then removes the stored click in the granted tab too, which ends the loop without the tab having to be reloaded. Current scripts without a session are not counted per address, because visitors behind one proxy or carrier NAT share it. The counter is a transient, so without a persistent object cache each counted request writes to wp_options.
  • No script is loaded while measurement is inactive, which is the state after installation.
  • The script no longer depends on jQuery. Its cart and checkout hooks attach when jQuery is present, also when a script optimizer runs this script or delivers jQuery after the page has loaded, so block themes do not load jQuery for this plugin.

Åtgärdat

  • The consent endpoint ?wc-ajax=convert_relay received a request loop in production: over half of all requests on the store, about 300 a minute from single visitors roughly once a second, each one booting WordPress and WooCommerce. Two open tabs with different consent states triggered each other through localStorage. A tab whose consent was still unknown, which is every tab opened before the visitor answered the banner since the banner does not update tabs already open, removed the stored ad click; the granted tab received the storage event, wrote the click back with a fresh expiry, and that write fired the event in the first tab again. Every round ran a synchronization request. The stored click is now removed only on a refusal, never on unknown consent. A tab reacts to another tab only when the click actually changed or was removed, and after a removal it no longer re-imports the click from the pixel cookie, so a refusal in one tab is not undone by a tab that still shows the old grant.
  • Reloading a landing URL that carried oppref restarted the click's 30-day lifetime, although the documentation said a refresh does not extend it. The stored click is only replaced by a different click.

Version 1.1.0

Tillagt

  • WP Consent API as a second consent integration, next to Cookiebot, so the plugin works with the consent banners that support that API. It reads the marketing category with wp_has_consent() on the server and in the browser, and listens for wp_listen_for_consent_change and wp_consent_type_defined so page events are sent the moment consent is given, as with Cookiebot. When the banner signals that it sets the consent type in the browser (waitfor_consent_hook), consent stays unknown until it has, so a server-side opt-out default cannot grant consent to a visitor in an opt-in region. wp_has_consent() reports consent for every category while no banner has declared a consent type, so that case counts as unknown and nothing is measured; once a banner declares opt-in or opt-out, its rule applies. A server-side grant also requires the browser to report one, so either side can only hold measurement back. The plugin registers itself with the API. The option is disabled in the settings while the WP Consent API plugin is inactive, unless it is already the saved choice, and an admin notice says why measurement stops if that plugin is deactivated later.
  • Consent::adapter() returns the configured integration. The consent endpoint, the browser report endpoint and Settings::active() now go through it instead of constructing the Cookiebot adapter directly.

Ändrat

  • Event log files moved from wp-content/convert-relay-logs/ to convert-relay-logs/ in the uploads directory, where WordPress.org expects plugins to write. The directory now carries an index.php and an .htaccess deny rule in addition to the exit header in every file. Log files in the old directory are deleted on the first request after the upgrade, using the same file name pattern as uninstall, and the old directory is removed if nothing else is left in it. Uninstall with erase selected cleans both locations, and the admin error for a failed write names the directory in use.
  • The admin notice asks for a consent integration instead of naming Cookiebot, and the settings text describes any consent tool.
  • The Before production card describes payment confirmation for every gateway instead of Kustom's, and the Kustom version appears under Configuration status only when Kustom is installed. Kustom's accepted-authorization handling is unchanged.
  • tools/package.py refuses to build an installer in which any file matches a local deny list of terms, and no longer packages README.md, docs/, .DS_Store or __pycache__; readme.txt is the user documentation.
  • readme.txt rewritten for WordPress.org: it stands on its own, lists every external endpoint with what is sent and when, links to OpenAI's Ad Tools Terms, Conversion Terms and privacy policy, and adds a FAQ. The plugin header gains Tested up to and License URI.

Version 1.0.12

Åtgärdat

  • The page events waited for the next page load when the visitor accepted marketing cookies on the page they arrived on. page_viewed, contents_viewed and checkout_started were only measured while the script started up, so a visitor who was still looking at the consent banner at that moment was measured from the following page view at the earliest, and an ad click identifier that lived only in the landing URL was gone by then. They are now sent the moment consent is given, in the same visit and with the attribution the landing page carries. Each event still has its own identifier, so the repeated calls that follow a consent change send nothing twice.

Version 1.0.11

Tillagt

  • Optional server matching signals, each behind its own setting and off until an administrator enables it: the customer's IP address with the user agent, and the billing country, city, region and postal code. They raise how often a purchase can be matched to an ad click when no attribution identifier survived the journey. The advertising API documents these as raw values rather than hashed, which the settings screen states, and a value that does not match the documented shape is left out instead of sent, including a two-letter country code that is not part of ISO 3166-1 alpha-2. Both are read from the order when the event is queued and stored encrypted with the rest of the payload, so enabling them adds nothing to what the site already keeps.

Version 1.0.10

Åtgärdat

  • The installer name was hardcoded in tools/package.py, so building a release produced a ZIP named after version 1.0.9 whatever the plugin header said. The name is derived from the header now, and the build refuses to run when readme.txt disagrees with it.
  • Browser events did not match the payloads the measurement pixel documents, and the advertising account reported rejected pixel events with invalid properties. page_viewed sent no contents at all, where the documentation sends the page itself as a content item; it now carries the page slug, the document title and content_type: page. The slug is never a search term or any other visitor input, so a page view cannot carry what somebody typed. items_added sent no amount or currency, neither on the event nor on the item, although both are documented for it; a cart addition is now priced like the checkout and purchase events, from the cart line that was added rather than the catalogue entry, so add-ons, name your price and dynamic pricing report what the customer actually added; an unreadable price drops the amount rather than sending a partial one. A search page reports a fixed id and a fixed name, because the generated document title embeds the query.

Version 1.0.9

Åtgärdat

  • Log retention was enforced by age only, so on a busy store with Info or Debug selected a single half-day file could grow until the disk did: browser reports alone are allowed 1,200 a minute. A file now stops accepting entries at 8 MB and surfaces a notice in the viewer, and maintenance deletes the oldest files once the directory passes 64 MB. Both budgets are filterable, a constant overrides the filter, neither can be set below 64 KB, and a per-file cap above the directory budget is clamped to it, so a limit can neither silently disable logging nor let one file fill the directory.
  • The Event files card named 8 MB and 64 MB even when a filter or constant had changed them, so the text was wrong exactly when an administrator read it to make sense of the truncation notice. It now names the limits in force.
  • The compiled Swedish catalogue was maintained by hand, so a translated string whose source text changed fell back to English. It is built from the source catalogue now, and the check script fails when either is out of date.

Version 1.0.8

Tillagt

  • Contents items now carry the product name, which the measurement pixel documents as a required field of a contents item and which neither the browser nor the server events populated.
  • Purchase items carry their unit price including tax and the currency. amount is the unit price because quantity is sent alongside it, so the two multiply to the line total. A line total that does not divide evenly by its quantity sends no unit price at all, rather than a truncated one whose multiple misses the line.
  • Variation purchases carry a variant_dict of at most ten attribute pairs, each name and value bounded to 200 characters. The values are read from the order item, so they are the ones the customer bought: a later catalogue edit, a deleted variation or an "Any …" wildcard cannot change or empty them. Only meta matching an attribute of the parent product is used, so unrelated line item meta such as a gift message is never sent.
  • checkout_started carries the cart contents and the cart total instead of an empty payload; the first fifty lines are sent.

Åtgärdat

  • Optional item enrichment is dropped instead of failing the event when a price, attribute or name cannot be read, so a single unreadable line cannot stop a purchase from being measured.

Version 1.0.7

Tillagt

  • Verified the acknowledgement contract against the live API. A response counts as an acknowledgement only when it parses, reports no rejection and carries an integral accepted_events of at least one, so a fractional or exponent-notation count is treated as schema drift; the captured response for an accepted event is {"accepted_events":1} under HTTP 200. Validation and purchase deliveries therefore report accepted instead of marking every HTTP 2xx as unverified, while anything unparseable or rejected still fails closed.
  • tools/ack-probe.php, a development-only probe that records one API response body so the contract can be re-checked if the API changes. It is not part of the installer.

Version 1.0.6

Åtgärdat

  • The release section of the README claimed that the current version was a local build not yet published to GitHub. That sentence went stale the moment a version was released, so it no longer names a single version and points at the Releases page instead. No runtime changes.

Version 1.0.5

Tillagt

  • Protected daily FM/EM event files in the store timezone, with errors always enabled and selectable warning/info/debug levels.
  • Structured purchase state, API response, consent and consent-gated browser SDK diagnostics without credentials or event payloads.
  • Admin file selection, bounded viewing, 14-day retention, concurrent-write locking and browser report rate limits. Each rate window keeps its original expiry, so steady traffic below the documented per-minute limits never accumulates into a lasting 429 period.

Version 1.0.4

Ändrat

  • Split the admin page into Settings, Validation & log, and Delivery status tabs, retaining the relevant tab after actions.
  • Explain HTTP success without verified acknowledgement in a distinct warning result.
  • Keep the latest 100 validation results without credentials or payloads. On the first validation after upgrading, the result stored by an earlier version seeds the new log instead of being overwritten. History is removed on opted-in uninstall.

Version 1.0.3

Ändrat

  • Show a masked placeholder for configured API keys and an accessible show/hide button inside the field.
  • Retrieve the key only on explicit request through a nonce-protected, uncached endpoint restricted to administrators, and only for a key saved through this plugin. A key supplied by wp-config.php or the environment is never returned, so WooCommerce managers cannot read server configuration. Viewing a key does not change stored credentials.

Version 1.0.2

Ändrat

  • Redesigned settings with responsive cards, aligned fields, configuration status, section navigation and a delivery empty state.
  • Added readable browser event labels and Swedish translations; grouped destructive options under advanced settings.
  • Validation controls explain missing credentials before a test can be sent.

Version 1.0.1

Åtgärdat

  • Lower the WordPress requirement from 6.6 to 6.5: the previous metadata blocked installation on compatible 6.5 sites despite no runtime dependency on 6.6.

Version 1.0.0

Tillagt

  • Consent-gated browser measurement and queued purchase snapshots for WooCommerce.
  • Cookiebot adapter, Kustom authorization integration, HPOS order access, encrypted secrets and payloads.
  • Disabled-by-default setup, synthetic validation, delivery diagnostics and bounded retries.
  • GitHub pre-release distribution with the verified WordPress installer attached as a release asset.