Skip to main content
Ask your AI

Google Data Manager API

Server Source code Package

The Google Data Manager API destination package provides server-side integration for sending conversion events and audience data to Google's advertising ecosystem through a single unified API endpoint.

Where this fits

Google Data Manager API is a server destination in the walkerOS flow:

Sends conversion events server-side to Google Ads, Display & Video 360, and GA4 through a single unified API.

Installation​

npm install @walkeros/server-destination-datamanager
import { startFlow } from '@walkeros/collector';
import { destinationDataManager } from '@walkeros/server-destination-datamanager';

await startFlow({
  destinations: {
    datamanager: {
      code: destinationDataManager,
      config: {
        // Service account credentials
        credentials: {
          client_email: process.env.GOOGLE_CLIENT_EMAIL,
          private_key: process.env.GOOGLE_PRIVATE_KEY,
        },
        settings: {
          destinations: [
            {
              operatingAccount: {
                accountId: '123-456-7890',
                accountType: 'GOOGLE_ADS',
              },
              productDestinationId: 'AW-CONVERSION-123',
            },
          ],
        },
      },
    },
  },
});

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​

PropertyTypeDescriptionMore
credentialscredentialsService account credentials (client_email + private_key). Recommended for serverless environments. (deprecated: use config.credentials)
client_email*stringService account email
private_key*stringService account private key (PEM format)
keyFilenamestringPath to service account JSON file. For local development or environments with filesystem access.
scopesArray<string>OAuth scopes for Data Manager API. Defaults to datamanager scope.
destinations*Array<object>Array of destination accounts and conversion actions/user lists (max 10)
eventSourceEvent source for all events. Defaults to WEB. Values: WEB, APP, IN_STORE, PHONE, OTHER
batchSizeintegerMaximum number of events to batch before sending (max 2000, like 100)
batchIntervalintegerTime in milliseconds to wait before auto-flushing batch
validateOnlybooleanIf true, validate request without ingestion (testing mode)
urlstringOverride API endpoint for testing
consentconsentRequest-level consent for all events
adUserDataConsent for data collection and use
adPersonalizationConsent for ad personalization
testEventCodestringTest event code for debugging
logLevelLog level for debugging (debug shows all API calls)
userDataRecord<string, any>Guided helper: User data mapping for all events
userIdanyGuided helper: First-party user ID for all events
clientIdanyGuided helper: GA4 client ID for all events
appInstanceIdanyGuided helper: GA4 app instance ID (Firebase) for all events
sessionAttributesanyGuided helper: Privacy-safe attribution for all events
consentAdUserDatastring | booleanConsent mapping: Field name from event.consent (like 'marketing') or static boolean value
consentAdPersonalizationstring | booleanConsent mapping: Field name from event.consent (like 'targeting') or static boolean value
ipany | booleanClient IP, sent as eventDeviceInfo.ipAddress. Resolves against { ingest, event }. Default: ["ingest.ip", "event.user.ip"]. false switches it off. A mapped ipAddress wins.
userAgentany | booleanClient user agent, sent as eventDeviceInfo.userAgent. Resolves against { ingest, event }. Default: ["ingest.userAgent", "event.user.userAgent"]. false switches it off. A mapped userAgent wins.
* Required fields

Mapping​

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

Examples

Page view

A page view is sent to Data Manager as a page_view event tied to the walker user id.

Event
{
  "name": "page view",
  "data": {
    "title": "Pricing",
    "url": "https://example.com/pricing"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "pagegroup": "docs"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "visitor-55"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "ev-1700000902000",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000902000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "express",
    "platform": "server"
  }
}
Mapping
{
  "name": "page_view",
  "data": {
    "map": {
      "transactionId": "id",
      "eventName": {
        "value": "page_view"
      },
      "userId": "user.id"
    }
  }
}
Out
fetch("https://datamanager.googleapis.com/v1/events:ingest", {
  "method": "POST",
  "headers": {
    "Authorization": "Bearer ya29.c.test_token",
    "Content-Type": "application/json"
  },
  "body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:22.000Z\",\"transactionId\":\"ev-1700000902000\",\"userId\":\"visitor-55\",\"eventName\":\"page_view\",\"eventSource\":\"WEB\"}],\"destinations\":[{\"operatingAccount\":{\"accountId\":\"123-456-7890\",\"accountType\":\"GOOGLE_ADS\"},\"productDestinationId\":\"AW-CONVERSION-123\"}],\"encoding\":\"HEX\"}"
})

Lead

A demo request form submission is sent to Data Manager as a generate_lead conversion with a hashed email.

Event
{
  "name": "form submit",
  "data": {
    "type": "demo-request"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "email": "prospect@example.com"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "ev-1700000901000",
  "trigger": "test",
  "entity": "form",
  "action": "submit",
  "timestamp": 1700000901000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "express",
    "platform": "server"
  }
}
Mapping
{
  "name": "generate_lead",
  "data": {
    "map": {
      "transactionId": "id",
      "eventName": {
        "value": "generate_lead"
      },
      "conversionValue": {
        "value": 10
      },
      "currency": {
        "value": "USD"
      },
      "email": "user.email"
    }
  }
}
Out
fetch("https://datamanager.googleapis.com/v1/events:ingest", {
  "method": "POST",
  "headers": {
    "Authorization": "Bearer ya29.c.test_token",
    "Content-Type": "application/json"
  },
  "body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:21.000Z\",\"transactionId\":\"ev-1700000901000\",\"userData\":{\"userIdentifiers\":[{\"emailAddress\":\"395ec5f334be0ab5b28568a1e7f6ed5ea80e443fb1ce3d803340586a3df46642\"}]},\"conversionValue\":10,\"currency\":\"USD\",\"eventName\":\"generate_lead\",\"eventSource\":\"WEB\"}],\"destinations\":[{\"operatingAccount\":{\"accountId\":\"123-456-7890\",\"accountType\":\"GOOGLE_ADS\"},\"productDestinationId\":\"AW-CONVERSION-123\"}],\"encoding\":\"HEX\"}"
})

Purchase

A completed order is posted to Google Data Manager as a purchase conversion with hashed user identifiers.

Event
{
  "name": "order complete",
  "data": {
    "id": "ORD-600",
    "total": 149.99,
    "currency": "EUR"
  },
  "context": {
    "shopping": [
      "complete",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "user-abc",
    "email": "buyer@example.com"
  },
  "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": "ev-1700000900000",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000900000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "express",
    "platform": "server"
  }
}
Mapping
{
  "name": "purchase",
  "data": {
    "map": {
      "transactionId": "data.id",
      "conversionValue": "data.total",
      "currency": {
        "key": "data.currency",
        "value": "USD"
      },
      "eventName": {
        "value": "purchase"
      },
      "userId": "user.id",
      "email": "user.email"
    }
  }
}
Out
fetch("https://datamanager.googleapis.com/v1/events:ingest", {
  "method": "POST",
  "headers": {
    "Authorization": "Bearer ya29.c.test_token",
    "Content-Type": "application/json"
  },
  "body": "{\"events\":[{\"eventTimestamp\":\"2023-11-14T22:28:20.000Z\",\"transactionId\":\"ORD-600\",\"userId\":\"user-abc\",\"userData\":{\"userIdentifiers\":[{\"emailAddress\":\"6a6c26195c3682faa816966af789717c3bfa834eee6c599d667d2b3429c27cfd\"}]},\"conversionValue\":149.99,\"currency\":\"EUR\",\"eventName\":\"purchase\",\"eventSource\":\"WEB\"}],\"destinations\":[{\"operatingAccount\":{\"accountId\":\"123-456-7890\",\"accountType\":\"GOOGLE_ADS\"},\"productDestinationId\":\"AW-CONVERSION-123\"}],\"encoding\":\"HEX\"}"
})

Guided mapping helpers​

Define common fields once in Settings instead of repeating them in every event mapping:

await startFlow({
  destinations: {
    datamanager: {
      code: destinationDataManager,
      config: {
        credentials: { /* ... */ },
        settings: {
          destinations: [...],

          // Guided helpers (apply to all events)
          userData: {
            email: 'user.mail',
            phone: 'data.phone',
          },
          userId: 'user.id',
          clientId: 'user.device',                  // GA4 web stream
          appInstanceId: 'user.appInstanceId',      // GA4 app stream (Firebase)
          sessionAttributes: 'context.sessionAttributes',

          // Consent mapping
          consentAdUserData: 'marketing',
          consentAdPersonalization: 'personalization',
        },
      },
    },
  },
});

Consent mapping: Map your consent field names (string) or use static values (boolean). Event mappings always override Settings helpers.

Client IP and user agent​

The destination sends the client IP and user agent as eventDeviceInfo.ipAddress and eventDeviceInfo.userAgent by default. The ip and userAgent settings resolve against { ingest, event } and default to ["ingest.ip", "event.user.ip"] and ["ingest.userAgent", "event.user.userAgent"], so they fill in whenever the source provides them (see the config.ingest recipe on the Express source). A value your event mapping sets for ipAddress or userAgent wins; a value that resolves to nothing is not sent. Set either to false to turn it off:

settings: {
  destinations: [...],
  ip: false,                          // never send the client IP
  userAgent: 'ingest.userAgent',      // request only, ignore event.user
}

Simulate​

walkeros push --simulate destination.<id> runs offline: the package's mock env supplies the auth client and fetch, so no token is requested and nothing reaches Google. The output shows the fetch call with the request body the destination would send.

Store sales (IN_STORE)​

For physical store conversions, set eventSource: 'IN_STORE' on the destination and map a storeId in the event mapping. The destination wraps it into the API's required eventLocation.storeId shape.

mapping: {
  order: {
    complete: {
      name: 'purchase',
      data: {
        map: {
          transactionId: 'data.id',
          conversionValue: 'data.total',
          currency: 'data.currency',
          storeId: 'data.storeId',
          email: 'user.email', // hashed PII still applies
        },
      },
    },
  },
}

Hash encoding​

Every request is sent with encoding: 'HEX'. Google requires the field whenever the payload contains hashed userData. The value is pinned because all hashing in this destination produces lowercase hex SHA-256 digests; exposing encoding as a setting would risk silently mismatched identifiers.

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)