Piwik PRO
Server-side event delivery to the Piwik PRO Tracking API. The destination uses the mapping of the Piwik PRO web destination: rule.name is a Piwik PRO JavaScript method such as trackEvent or ecommerceOrder, and data resolves to its arguments. A web mapping that uses the 13 portable methods produces the same hits on the server. Browser-only commands, such as state setters like setUserId, are skipped. Each push, or each flushed batch, becomes one bulk request to your account. There is no API token to manage: the destination needs only your account URL and site id.
Piwik PRO is a server destination in the walkerOS flow:
Builds Tracking API hits from your mapping and sends them in one bulk POST request, with the visitor's IP address, user agent and language taken from the incoming request.
Installation
npm install @walkeros/server-destination-piwikpro- Integrated
- Bundled
import { startFlow } from '@walkeros/collector';
import { destinationPiwikPro } from '@walkeros/server-destination-piwikpro';
await startFlow({
destinations: {
piwikpro: {
code: destinationPiwikPro,
config: {
settings: {
url: 'https://your_account_name.piwik.pro/',
appId: 'XXX-XXX-XXX-XXX-XXX',
},
batch: { size: 100, wait: 1000 },
},
},
},
});Add to your flow.json destinations:
"destinations": {
"piwikpro": {
"package": "@walkeros/server-destination-piwikpro",
"import": "destinationPiwikPro",
"config": {
"settings": {
"url": "https://your_account_name.piwik.pro/",
"appId": "XXX-XXX-XXX-XXX-XXX"
},
"batch": { "size": 100, "wait": 1000 }
}
}
}Before the first event, fill the request context in your server source, as shown in the ingest prerequisite.
Configuration
This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.
Settings
| Property | Type | Description | More |
|---|---|---|---|
url | string | Piwik PRO account URL, normalized to one trailing slash. Hits go to <url>ppms.php | |
appId | string | Piwik PRO site or app id, sent as idsite | |
timeout | integer | Request timeout in milliseconds. Default: 5000 | |
identified | boolean | object | Identified or anonymous tracking. true (default) identifies every hit, false makes every hit anonymous (uia=1, dda=1, no _id, no uid). A consent object like { marketing: true } identifies a hit when any listed state is granted. Do not repeat these states in config.consent, or events are queued and never reach the destination. | |
customDimensions | Record<string, Mapping.Value> | Custom dimensions sent as dimension{id} on every hit, keyed by bare dimension id. Values are mapping values resolved against the event (like { "1": "data.size" }). Rule-level customDimensions win per key. | |
ip | any | boolean | Visitor IP, sent as cip. Resolves against { ingest, event }. Default: ["ingest.ip", "event.user.ip"]. false switches it off. Kept on anonymous hits for country-level geolocation. | |
userAgent | any | boolean | User agent, sent as ua. Resolves against { ingest, event }. Default: ["ingest.userAgent", "event.user.userAgent"]. false switches it off. | |
language | any | boolean | Accept-Language value, sent as lang. Resolves against { ingest, event }. Default: ["ingest.language", "event.user.language"]. false switches it off. | |
pageUrl | any | boolean | Page URL, sent as url (required by the Tracking API, a hit without it is skipped). Resolves against { ingest, event }. Default: "event.source.url". false switches it off. | |
referrer | any | boolean | Referrer URL, sent as urlref. Resolves against { ingest, event }. Default: "event.source.referrer". false switches it off. | |
visitorId | any | boolean | Visitor id, sent as _id on identified hits. A 16-character hex value passes through, anything else is hashed to 16 hex (sha256). Resolves against { ingest, event }. Default: "event.user.device". false switches it off. | |
userId | any | boolean | User id, sent as uid on identified hits. Resolves against { ingest, event }. Default: "event.user.id". false switches it off. | |
pageViewId | any | boolean | Page view id, sent as pv_id (first 6 hex characters, else a 6-character hash). Resolves against { ingest, event }. Default: "event.source.trace", only for web events whose trace differs from the server collector trace. false switches it off. | |
timestamp | any | boolean | Event time in milliseconds, sent as cdt in UNIX seconds. Resolves against { ingest, event }. Default: "event.timestamp". false lets Piwik PRO use the receive time. |
Mapping
Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
| Property | Type | Description | More |
|---|---|---|---|
goalId | string | number | Piwik PRO goal id (UUID or legacy integer). Adds a second hit, trackGoal, to the same request. | |
goalValue | Mapping.Value | ||
customDimensions | Record<string, Mapping.Value> | Custom dimensions for this rule, keyed by bare dimension id. Values are mapping values resolved against the event (like { "1": "data.size" }). Wins per key over the destination customDimensions. |
Examples
Custom event
A promotion visible event maps to trackEvent with category, action and name as positional arguments.
{
"name": "promotion visible",
"data": {
"name": "Setting up tracking easily",
"position": "hero"
},
"context": {
"ab_test": [
"engagement",
0
]
},
"globals": {
"pagegroup": "homepage"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "d7b52232325ec740",
"trigger": "visible",
"entity": "promotion",
"action": "visible",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "trackEvent",
"data": {
"set": [
{
"value": "promotion"
},
{
"value": "visible"
},
"data.name"
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_c=promotion&e_a=visible&e_n=Setting+up+tracking+easily&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Add to cart
A product add maps to ecommerceAddToCart with the added product.
{
"name": "product add",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"intent",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "e145323e448c0a2d",
"trigger": "click",
"entity": "product",
"action": "add",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "ecommerceAddToCart",
"data": {
"set": [
{
"set": [
{
"map": {
"sku": "data.id",
"name": "data.name",
"price": "data.price",
"quantity": {
"value": 1
},
"variant": {
"key": "data.color"
},
"customDimensions": {
"map": {
"1": "data.size"
}
}
}
}
]
}
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_t=add-to-cart&ec_products=%5B%5B%22ers%22%2C%22Everyday+Ruck+Snack%22%2Cnull%2C420%2C1%2Cnull%2C%22black%22%2C%7B%221%22%3A%22l%22%7D%5D%5D&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Cart update
A cart view maps to ecommerceCartUpdate with the cart products and the cart total as revenue.
{
"name": "cart view",
"data": {
"currency": "EUR",
"value": 840
},
"context": {
"shopping": [
"cart",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420,
"quantity": 2
},
"context": {
"shopping": [
"cart",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "565b357e6b3a10dd",
"trigger": "load",
"entity": "cart",
"action": "view",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "ecommerceCartUpdate",
"data": {
"set": [
{
"loop": [
"nested",
{
"condition": {
"$code": "e=>y(e)&&\"product\"===e.entity"
},
"map": {
"sku": "data.id",
"name": "data.name",
"price": "data.price",
"quantity": {
"value": 1
},
"variant": {
"key": "data.color"
},
"customDimensions": {
"map": {
"1": "data.size"
}
}
}
}
]
},
"data.value"
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_t=cart-update&ec_products=%5B%5B%22ers%22%2C%22Everyday+Ruck+Snack%22%2Cnull%2C420%2C1%2Cnull%2C%22black%22%2C%7B%221%22%3A%22l%22%7D%5D%5D&revenue=840&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Ecommerce order
A completed order maps to ecommerceOrder with its products, order id, total, tax and shipping.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "a8e6f6e6c9161050",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "ecommerceOrder",
"data": {
"set": [
{
"loop": [
"nested",
{
"condition": {
"$code": "e=>y(e)&&\"product\"===e.entity"
},
"map": {
"sku": "data.id",
"name": "data.name",
"price": "data.price",
"quantity": {
"value": 1
},
"variant": {
"key": "data.color"
},
"customDimensions": {
"map": {
"1": "data.size"
}
}
}
}
]
},
{
"map": {
"orderId": "data.id",
"grandTotal": "data.total",
"tax": "data.taxes",
"shipping": "data.shipping"
}
}
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_t=order&ec_id=0rd3r1d&revenue=555&ec_tx=73.76&ec_sh=5.22&ec_products=%5B%5B%22ers%22%2C%22Everyday+Ruck+Snack%22%2Cnull%2C420%2C1%2Cnull%2C%22black%22%2C%7B%221%22%3A%22l%22%7D%5D%2C%5B%22cc%22%2C%22Cool+Cap%22%2Cnull%2C42%2C1%2Cnull%2Cnull%2C%7B%221%22%3A%22one+size%22%7D%5D%5D&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Product detail view
A product view maps to ecommerceProductDetailView with the product as a single-item array.
{
"name": "product view",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [],
"consent": {
"functional": true
},
"id": "fdce8153957a6ee4",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "ecommerceProductDetailView",
"data": {
"set": [
{
"set": [
{
"map": {
"sku": "data.id",
"name": "data.name",
"price": "data.price",
"quantity": {
"value": 1
},
"variant": {
"key": "data.color"
},
"customDimensions": {
"map": {
"1": "data.size"
}
}
}
}
]
}
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_t=product-detail-view&ec_products=%5B%5B%22ers%22%2C%22Everyday+Ruck+Snack%22%2Cnull%2C420%2C1%2Cnull%2C%22black%22%2C%7B%221%22%3A%22l%22%7D%5D%5D&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Remove from cart
A product remove maps to ecommerceRemoveFromCart with the removed product.
{
"name": "product remove",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "fa6c5bd0240f6589",
"trigger": "test",
"entity": "product",
"action": "remove",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "ecommerceRemoveFromCart",
"data": {
"set": [
{
"set": [
{
"map": {
"sku": "data.id",
"name": "data.name",
"price": "data.price",
"quantity": {
"value": 1
},
"variant": {
"key": "data.color"
},
"customDimensions": {
"map": {
"1": "data.size"
}
}
}
}
]
}
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_t=remove-from-cart&ec_products=%5B%5B%22ers%22%2C%22Everyday+Ruck+Snack%22%2Cnull%2C420%2C1%2Cnull%2C%22black%22%2C%7B%221%22%3A%22l%22%7D%5D%5D&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Event with goal value
An order event tracks a custom event and a goal conversion whose revenue resolves from the order total.
{
"name": "order complete",
"data": {
"id": "0rd3r1d",
"currency": "EUR",
"shipping": 5.22,
"taxes": 73.76,
"total": 555
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "f22a6ae7ab800806",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "trackEvent",
"settings": {
"goalId": "6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c",
"goalValue": "data.total"
},
"data": {
"set": [
{
"value": "order"
},
{
"value": "complete"
},
"data.id",
"data.total"
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&e_c=order&e_a=complete&e_n=0rd3r1d&e_v=555&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\",\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&idgoal=6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c&revenue=555&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Page view
A page view becomes a trackPageView hit with the page title as action_name and the page context.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "4d5175dfbfa0bfaf",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"data": "data.title"
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&action_name=walkerOS+documentation&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Page view with goal
A page view rule with a goalId still tracks the page view and adds a trackGoal hit to the same request.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "7d738346b3833a94",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"settings": {
"goalId": "6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c"
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&action_name=walkerOS+documentation&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\",\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&idgoal=6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Ping
A heartbeat event maps to ping, which extends the session without counting as a page view.
{
"name": "page ping",
"data": {
"string": "foo",
"number": 1,
"boolean": true,
"array": [
0,
"text",
false
]
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "14fd7fac4e4c11fc",
"trigger": "test",
"entity": "page",
"action": "ping",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "ping"
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&ping=6&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Site search
A search event maps to trackSiteSearch with keyword, category and result count.
{
"name": "search submit",
"data": {
"query": "rucksack",
"category": "bags",
"results": 12
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "46e19a9409b84cfc",
"trigger": "test",
"entity": "search",
"action": "submit",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "trackSiteSearch",
"data": {
"set": [
"data.query",
"data.category",
"data.results"
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&search=rucksack&search_cats=%5B%22bags%22%5D&search_count=12&url=https%3A%2F%2Fwww.example.com%2F&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Download link
A tagged download maps to trackLink, which sends the file address as download and as the hit url.
{
"name": "file download",
"data": {
"url": "https://www.example.com/whitepaper.pdf"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"id": "us3r",
"device": "c00k13",
"session": "s3ss10n"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "88794cf4c50df54b",
"trigger": "test",
"entity": "file",
"action": "download",
"timestamp": 1700000300000,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "browser",
"platform": "web",
"url": "https://www.example.com/",
"referrer": "https://www.walkeros.io/"
}
}{
"name": "trackLink",
"data": {
"set": [
"data.url",
{
"value": "download"
}
]
}
}sendServer("https://your_account_name.piwik.pro/ppms.php", "{\"requests\":[\"?idsite=e8f3a1c2-0b4d-4e5f-9a6b-7c8d9e0f1a2b&rec=1&send_image=0&ts_n=walkerOS&ts_v=4.7.2&download=https%3A%2F%2Fwww.example.com%2Fwhitepaper.pdf&url=https%3A%2F%2Fwww.example.com%2Fwhitepaper.pdf&urlref=https%3A%2F%2Fwww.walkeros.io%2F&_id=cc8e27118413234d&uid=us3r&pv_id=0a1b2c&cdt=1700000300\"]}", {
"timeout": 5000
})Ingest prerequisite
In the browser, the Piwik PRO tracker sends the visitor's IP address, user agent and language with every hit. On the server these values come from the incoming request. The ip, userAgent and language settings read ingest.ip, ingest.userAgent and ingest.language first, then fall back to event.user. Fill ingest with the map operator in the server source's config.ingest:
"sources": {
"express": {
"package": "@walkeros/server-source-express",
"config": {
"settings": { "port": 8080 },
"ingest": {
"map": {
"ip": { "key": "ip" },
"userAgent": { "key": "headers.user-agent" },
"language": { "key": "headers.accept-language" }
}
}
}
}
}Without it, Piwik PRO receives no client IP, user agent or language, and locates every visitor at your server. Behind a load balancer or a CDN, ip is the address of the last proxy; see client IP behind a proxy for the header to map instead. The other server sources take the same map, see the request scope.
What each setting sends
| Setting | Default | Sends |
|---|---|---|
url | required | Your account URL. Hits go to <url>ppms.php. The URL is normalized to one trailing slash. |
appId | required | The site or app id, as idsite. |
timeout | 5000 | Not sent. The request timeout in milliseconds. |
identified | true | Identified or anonymous hits, see identified and anonymous. |
customDimensions | none | dimension{id} on every hit, see custom dimensions. |
ip | ["ingest.ip", "event.user.ip"] | cip, the visitor's IP address. Kept on anonymous hits, for country-level location. |
userAgent | ["ingest.userAgent", "event.user.userAgent"] | ua |
language | ["ingest.language", "event.user.language"] | lang |
pageUrl | "event.source.url" | url. The Tracking API requires it, so an event without a page URL is skipped. |
referrer | "event.source.referrer" | urlref |
visitorId | "event.user.device" | _id on identified hits. A 16 character lowercase hex value passes through. Anything else is hashed to 16 hex characters, so walkerOS device ids are always hashed. |
userId | "event.user.id" | uid on identified hits. |
pageViewId | the page trace of web events | pv_id, as 6 hex characters. See server-side vs browser. |
timestamp | "event.timestamp" | cdt, the event time in UNIX seconds. |
The last nine settings are mapping values, resolved for every hit against { ingest, event }. A path therefore names its side, like ingest.ip or event.user.id. An array tries each value in turn and uses the first one that is defined. Set a setting to false to leave its parameter out. With timestamp: false, Piwik PRO uses the time it receives the hit.
For cookieless tracking, pair visitorId with the fingerprint transformer: let it write a hash of length 16 to user.hash, and set visitorId to "event.user.hash".
Copy your web mapping
rule.name selects a Piwik PRO JavaScript method, and data resolves to its arguments in order. Use set to pass several arguments; any other value is a single argument. A page view without rule.name becomes trackPageView, with rule.data as its arguments, or else data.title as the title.
The server builds hits for 13 methods. A required argument is marked with *. When it is missing, the event is invalid and skipped. Every hit also carries the common parameters, the custom dimensions and the request context from the settings above.
| Method (JavaScript signature) | What the server sends |
|---|---|
trackPageView([title]) | action_name |
trackEvent(category*, action*[, name[, value[, dimensions]]]) | e_c, e_a, e_n, e_v (a number) |
trackGoal(goalId*[, conversionValue[, dimensions]]) | idgoal, revenue (a number) |
trackSiteSearch(keyword*[, category[, resultCount[, dimensions]]]) | search, search_cats (a JSON array, a single category becomes ["..."]), search_count (an integer) |
trackLink(linkAddress*, linkType*[, dimensions]) | link or download, by linkType, which must be "link" or "download". url is set to the same address. |
trackContentImpression(contentName*[, contentPiece[, contentTarget]]) | c_n, c_p, c_t |
trackContentInteraction(contentInteraction*, contentName*[, contentPiece[, contentTarget]]) | c_i, c_n, c_p, c_t |
ecommerceProductDetailView(products*) | e_t=product-detail-view, ec_products |
ecommerceAddToCart(products*) | e_t=add-to-cart, ec_products |
ecommerceRemoveFromCart(products*) | e_t=remove-from-cart, ec_products |
ecommerceCartUpdate(products*, grandTotal*) | e_t=cart-update, ec_products, revenue |
ecommerceOrder(products*, { orderId*, grandTotal*, subTotal, tax, shipping, discount }) | e_t=order, ec_id, revenue, ec_st, ec_tx, ec_sh, ec_dt, ec_products |
ping() | ping=6 |
Numbers may also arrive as numeric strings. products is an array of 1 to 100 product objects with sku*, name, category (a string or up to 5 strings), price, quantity, brand, variant and customDimensions (keyed by bare dimension id). A product without sku, a category that is not a string or up to 5 strings, a price or quantity that is not a number, or more than 100 products make the event invalid.
State setters such as setUserId exist only in the browser. The server keeps no tracker state, so it skips any method outside this table with a warning. The user id comes from the userId setting instead.
A page view, a custom event with a goal and an ecommerce order:
"mapping": {
"page": {
"view": {}
},
"promotion": {
"click": {
"name": "trackEvent",
"data": {
"set": [{ "value": "promotion" }, { "value": "click" }, "data.name"]
},
"settings": { "goalId": "6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c" }
}
},
"order": {
"complete": {
"name": "ecommerceOrder",
"data": {
"set": [
{
"loop": [
"nested",
{
"condition": "$code:(entity) => entity.entity === 'product'",
"map": {
"sku": "data.id",
"name": "data.name",
"price": "data.price",
"quantity": { "value": 1 }
}
}
]
},
{
"map": {
"orderId": "data.id",
"grandTotal": "data.total",
"tax": "data.taxes",
"shipping": "data.shipping"
}
}
]
}
}
}
}Goals
A rule's settings.goalId adds a second hit, trackGoal, to the same request. settings.goalValue is a mapping value for its revenue, such as "data.total". A page view rule with goal settings still sends its page view. With silent: true, the rule sends only the goal hit.
A rule without name on an event that is not a page view sends only the goal when it has a goalId:
"order": {
"complete": {
"settings": {
"goalId": "6a0f4c1e-3b2d-4e8f-a7c9-1d2e3f4a5b6c",
"goalValue": "data.total"
}
}
}When the goal value resolves to something other than a number or a numeric string, the goal hit is invalid and the whole event is skipped. The method hit is not sent either. The same applies to any invalid hit: an event is sent completely or not at all.
Custom dimensions
Custom dimensions are keyed by bare dimension id, like { "1": "data.size" }. Each value is a mapping value, resolved for every event.
settings.customDimensionsapplies to every hit.- A rule's
settings.customDimensionsapplies to that rule's hits and wins per key. A rule dimension that resolves to nothing removes that dimension from the hit, even when the destination sets it. trackEvent,trackGoal,trackSiteSearchandtrackLinkalso take a dimensions object as an argument, like{ "dimension1": "l" }. When your mapping passes one, its values win over both settings. Keys other thandimension{id}are dropped.
The server sends each dimension as dimension{id}=value, ordered by id. The goal hit carries the destination and rule dimensions. Values from a dimensions argument are sent as they are. A value you percent-encoded for the browser therefore arrives encoded. For values that need encoding in the browser, prefer the customDimensions settings, which each destination encodes for its own platform.
Identified and anonymous
The identified setting decides whether a hit identifies the visitor.
identified | Hits |
|---|---|
absent or true | Identified, the Piwik PRO default. |
false | Always anonymous, for example on a cookieless site that never asks for consent. |
a consent object, like { "marketing": true } | Identified when any listed consent state is granted, anonymous otherwise. |
The consent check reads the collector's consent state and the event's own consent. Events relayed from the browser carry the visitor's choice. Use config.consent for the states that gate tracking at all, and identified for the states that gate identification:
"config": {
"consent": { "analytics": true },
"settings": {
"url": "https://your_account_name.piwik.pro/",
"appId": "XXX-XXX-XXX-XXX-XXX",
"identified": { "marketing": true }
}
}Do not repeat the identified states in config.consent. Events without that consent are then queued and never reach the destination, so no anonymous hit is sent.
An anonymous hit carries uia=1 and dda=1 and leaves out _id and uid. It keeps cip, so Piwik PRO can still locate the visitor by country. Each hit is decided on its own. Only the web destination can merge an earlier anonymous session into the identified visitor, with deanonymizeUser.
Batching
Set a destination-level batch so that each flushed batch becomes one bulk request:
"batch": { "size": 100, "wait": 1000 }Spell out both values. The default size cap is 1000 entries, and a bare number such as "batch": 500 sets only wait. Batch scheduling explains wait, size and age. Without batching, every event is a request of its own in the same format.
The body of a bulk request holds one request string per hit. For a page view and a custom event it looks like this, with ts_v set to the package version:
{
"requests": [
"?idsite=XXX-XXX-XXX-XXX-XXX&rec=1&send_image=0&ts_n=walkerOS&ts_v=<version>&action_name=walkerOS+documentation&url=https%3A%2F%2Fwww.example.com%2F&cip=203.0.113.7&ua=Mozilla%2F5.0&lang=de-DE&_id=cc8e27118413234d&pv_id=0a1b2c&cdt=1700000300",
"?idsite=XXX-XXX-XXX-XXX-XXX&rec=1&send_image=0&ts_n=walkerOS&ts_v=<version>&e_c=promotion&e_a=visible&e_n=Setting+up+tracking+easily&url=https%3A%2F%2Fwww.example.com%2F&cip=203.0.113.7&ua=Mozilla%2F5.0&lang=de-DE&_id=cc8e27118413234d&pv_id=0a1b2c&cdt=1700000301"
]
}A goal adds its hit to the same list. Skipped events are left out of the request and count as delivered. If every event of a batch is skipped, no request is sent.
A failed request (a 4xx or 5xx status, a timeout or a network error) throws, so the whole batch goes to the destination's dead-letter queue. A 429 status is logged as rate limiting first. The destination does not retry.
Server-side vs browser
| The browser tracker fills automatically | Server | How |
|---|---|---|
| Client IP, user agent, language | Yes | ip, userAgent and language, from the ingest map with event.user fallbacks |
| Page URL, referrer | Yes | pageUrl and referrer, from event.source |
Visitor id (_id) | Yes | visitorId, from event.user.device, hashed |
| User id | Yes | userId, from event.user.id |
Page view id (pv_id) | Yes, for walkerOS web sources | The page trace: one per page load, one per walker run in single-page apps |
| Request time | Yes | cdt, from event.timestamp |
| Heartbeat for time on page | Partly | The ping method (ping=6) from a pulse-triggered event. walkerOS has no unload or blur trigger. |
| Automatic outlink and download tracking | No | Browser only (enableLinkTracking). On the server, tag the link and map its event to trackLink. |
| Screen resolution, plugins, performance timings, JavaScript errors | No | Not sent |
The page view id links an event to the page view it happened on. By default it is the trace of an event from a walkerOS web source, and only when that trace differs from the server collector's own trace. Events that start on the server, and hits decoded by the GA4 transformer, get no pv_id unless you set pageViewId yourself.
Unmapped and invalid events
These events are skipped:
- An event that is not a page view and whose rule has neither
namenorgoalId. - An event whose rule names a method outside the table, such as
setUserId. - A
silentrule without a goal. - An invalid event: a missing required argument, no page URL, a value that must be a number but is not (such as
e_vorrevenue), or more than 100 products.
Skips never throw. The destination logs a warning once per event name and reason, and repeats at debug level. To keep unmapped events out of the destination entirely, ignore everything with a wildcard and map what you need:
"mapping": {
"*": { "*": { "ignore": true } },
"page": { "view": {} }
}