Hello There!

Lorem ipsum dolor sit amet, consectetur adipiscing elit,

GOOGLE ANALYTICS 4 (GA4)

Data Manager API: How to Send Server-Side Events to GA4

How to Send Server-Side Events to GA4 with the Data Manager API

Not every conversion happens in a browser. in the back office. A sales rep closes a deal in the CRM, a customer confirms an order over the phone, and the back-office team processes a refund. If those moments never reach GA4, your reports undercount revenue and your marketing data tells only part of the story.

The Google Data Manager API is a way to send those events to GA4 directly from your own systems. It checks every request, tells you when something is wrong and lets you confirm what happened afterwards. This guide explains in plain terms what it does, what you need to set up and how to send and verify your first events. It covers GA4 events only, for web and app streams. Refer to our GA4 guide to marketing performance for a step-by-step view of how GA4 data turns into better marketing decisions.

The Data Manager API carries events from your own systems into GA4.

Quick answer: The Data Manager API lets your server or CRM send events to GA4 and get clear feedback on each one. Set up a Google Cloud project, give a service account Editor or Administrator access to your GA4 property, then send events to /v1/events:ingest. Every time, 1) test with validateOnly: true first, 2) save the requestId of every real send, 3) check the status about 30 minutes later.

What Is the Data Manager API?

The Data Manager API (DMA) is Google’s single entry point for sending first-party data, meaning data you collect yourself, to Google’s advertising and measurement products. For GA4, it lets you send events such as purchases, leads and refunds from your server, CRM or offline systems straight to a GA4 property.

More importantly, the API provides feedback about each request. Instead of sending data and hoping it arrived, you get an error message when a request is wrong, a request ID you can trace and a status report once Google has processed the data.

In practice you can use it to:

  • Send server-side and offline events (purchase, lead, refund) to a GA4 web or app stream.
  • Connect offline events to the same person who browsed online, using the web clientId or the app appInstanceId.
  • Send the same events to up to 10 destinations in one request.
  • Test every request before sending it and check what happened afterwards.

The essentials at a glance

Item What it is
Where events go POST https://datamanager.googleapis.com/v1/events:ingest
Where you check results requestStatus.retrieve (one requestId per check)
Permission scope https://www.googleapis.com/auth/datamanager
How you sign in A service account (recommended for servers) or OAuth user credentials. API keys are not supported
Where in GA4 Your GA4 property ID, plus the Measurement ID (web stream) or Firebase App ID (app stream)

Why teams use it

  • Clear errors: A bad request fails with HTTP 400 and tells you which field is wrong. A status report follows once the data is processed.
  • Large batches: Up to 2,000 events per request. A nightly file of 50,000 events needs only 25 requests.
  • Several destinations at once: One request can reach up to 10 destinations, such as a web stream and an app stream.
  • Online and offline together: Offline events such as refunds and CRM leads attach to the same user through the web clientId or the app appInstanceId.
  • Hashed customer data: Identifiers such as email and phone can be sent hashed, with encryption of user data supported.

One caution applies. The Data Manager API does not guarantee that every event is counted. Event age, duplicate handling, identifiers and property permissions still decide what lands in your reports.

How It Works, Step by Step

In general, every setup follows the same path. You prepare access, build a request, test it, send it for real and confirm the result.

  1. Turn on the Data Manager API in a Google Cloud project.
  2. Give the account that will send the data (a service account or a person) Editor or Administrator access to your GA4 property.
  3. Build the request. It names where the data goes (your GA4 property plus the Measurement ID or Firebase App ID) and lists the events.
  4. Send it first with validateOnly: true. Nothing is saved, but you get the same checks as a real send.
  5. Send it for real and save the requestId in the response.
  6. Check the request status about 30 minutes later, then look for the events in GA4 reports.

The usual path: test first, send for real, save the requestId and confirm in GA4.

Before You Start: What You Need

Gather these first and the rest of the setup goes smoothly.

  • A Google Cloud project where the API is turned on. It also provides the credentials your system uses to sign in.
  • Editor or Administrator access on the GA4 property for the account that will send events (GA Admin > Property access management).
  • Your GA4 property ID. This is the numeric ID of the property, not the Measurement ID.
  • The destination ID. For a web stream this is the Measurement ID (G-XXXXXXXXXX). For an app stream it is the Firebase App ID.
  • An identifier for each event. Web events need a clientId (from gtag.js or the GTM Client ID variable). App events need an appInstanceId from the Firebase SDK.

One-Time Setup

Regardless of your chosen authentication method, enable the Data Manager API in your Cloud project and grant the sending account Editor or Administrator access to the GA4 property. Then choose one of the following approaches.

Option A: Service account (recommended for servers and scheduled jobs)

A service account is a robot identity for your system. It needs no person to log in and no OAuth app verification, which makes it the simplest choice for automated feeds.

  1. Create a service account in your Cloud project and note its email address, for example dm********@****************************nt.com.
  2. Give it the Service Usage Consumer role (roles/serviceusage.serviceUsageConsumer) on the project.
  3. In GA4, add the service account’s email as a user on the property with Editor or Administrator access.
  4. Set up its credentials. If your system runs on Google Cloud, such as Cloud Run, Compute Engine, or GKE, attach the service account to your workload to complete the authentication setup. Outside Google Cloud, prefer Workload Identity Federation. Use a downloaded key file only if neither is possible, and keep it in a secret manager.
  5. For local testing, give yourself the Service Account Token Creator role on it and impersonate the service account instead of using a key file. Your developer can use the commands below.

gcloud auth application-default login \
  -impersonate-service-account=SERVICE_ACCOUNT_EMAIL \
  -scopes=”https://www.googleapis.com/auth/datamanager,                  https://www.googleapis.com/auth/cloud-platform”

gcloud auth application-default print-access-token \
  -scopes=”https://www.googleapis.com/auth/datamanager”

Option B: OAuth user credentials

Choose this when you act on behalf of people who own the accounts. Create an OAuth client (Desktop for testing) with the datamanager scope and generate a refresh token. Apps used by external users need Google OAuth app verification.

Once access is in place, make a first call with validateOnly: true.

Sending Your First Event

Every request has three parts: an access token that proves who you are, a destination that says which GA4 property and stream receive the data, and the events themselves. The sample below tests a web purchase without saving anything, which is the safest way to start.

curl -X POST “https://datamanager.googleapis.com/v1/events:ingest” \
  -H “Authorization: Bearer $ACCESS_TOKEN” \
  -H “Content-Type: application/json” \
  -d ‘{
    “destinations”: [{
      “operatingAccount”: {
        “accountType”: “GOOGLE_ANALYTICS_PROPERTY”,
        “accountId”: “123456789”
      },
      “productDestinationId”: “G-XXXXXXXXXX”
    }],
    “events”: [{
      “eventName”: “purchase”,
      “eventTimestamp”: “2026-10-12T10:15:00+05:30”,
      “eventSource”: “WEB”,
      “clientId”: “1234567890.1761581763”,
      “transactionId”: “T_12345”,
      “conversionValue”: 30.03,
      “currency”: “USD”,
      “cartData”: {“items”: [
        {“itemId”: “SKU_1”, “quantity”: 3, “unitPrice”: 10.01}
      ]},
      “userProperties”: {
        “customerType”: “NEW”,
        “customerValueBucket”: “HIGH”,
        “additionalUserProperties”: [
          {“propertyName”: “plan_tier”, “value”: “pro”}
        ]
      }
    }],
    “validateOnly”: true
  }’

What to change in the sample

In the sample Replace it with
$ACCESS_TOKEN An access token with the datamanager scope, from your service account or OAuth credentials
123456789 Your GA4 property ID (not the Measurement ID)
G-XXXXXXXXXX The Measurement ID of a web stream in that property
eventTimestamp The real time of the event, within the last 72 hours
validateOnly Keep it true while testing. Set it to false to save the data for real

Sending events from an app

App streams use the same address and sign-in. Three details change: the destination ID is the Firebase App ID, eventSource is APP and appInstanceId replaces clientId. For example, the destination becomes “1:1234567890:android:abcd1234ef567890” and the event carries “appInstanceId”: “5A1B2C3D4E5F60718293A4B5C6D7E8F9”.

Reading the response

What you get back What it means
HTTP 200 with a requestId The request was accepted. Save the requestId so you can check the status later
HTTP 200 with fieldWarnings The request was accepted, but an optional field was dropped. Review the warnings
HTTP 400 with fieldViolations Nothing was processed. The field path in the message tells you what to fix

A successful response provides a brief confirmation, while a warning identifies any optional fields the API dropped. By contrast, an error identifies the field that failed. A shortened error looks like this:

{
  “error”: {
    “code”: 400,
    “status”: “INVALID_ARGUMENT”,
    “details”: [{
      “@type”: “type.googleapis.com/google.rpc.BadRequest”,
      “fieldViolations”: [{
        “field”: “destinations[0].product_destination_id”,
        “description”: “NOT_FOUND Resource not found”
      }]
    }]
  }
}

Which fields each event needs

Field Required What to put in it
eventName Yes A GA4 event name, under 40 characters. Reserved names fail with INVALID_EVENT_NAME
eventTimestamp Yes The event time in RFC 3339 format. It must be within the last 72 hours (48 hours to join client-side events)
clientId / appInstanceId Yes clientId for web (from gtag.js or the GTM Client ID variable). appInstanceId for app (from the Firebase SDK)
transactionId For purchases The order ID. GA4 uses it to avoid double counting and keeps the first one it receives
cartData.items For ecommerce itemId, quantity and unitPrice for each item
conversionValue, currency Recommended The order value, always together with a currency

Sending to More Than One Destination

One request can feed up to 10 destinations. When you use more than one GA4 destination, give each a short reference name and tag every event with the reference it belongs to. This keeps each event going only where you intend. For example, name your destinations ga_web and ga_app, then tag a web purchase with [“ga_web”].

If you set a loginAccount, it must be identical to the operatingAccount, or you can leave it out. Otherwise the request fails with OPERATING_ACCOUNT_LOGIN_ACCOUNT_MISMATCH.

Web stream vs app stream

Aspect Web stream App stream
Destination ID Measurement ID (G-XXXXXXXXXX) Firebase App ID (iOS or Android)
eventSource WEB APP
Required identifier clientId appInstanceId
App-only events None ad_impression, screen_view, in_app_purchase
Google Ads bidding Multi-source events held out for 14 days No hold

Adding Parameters, User Properties and Customer Data

In addition to the required fields, an event can carry extra details. These optional additions make your GA4 data richer.

  • Event parameters: Extra details on the event, sent as name and value pairs in additionalEventParameters. Use GA4 parameter names and text values. Example: shipping = “4.99”.
  • Item parameters: Extra details on a product, such as item_name, item_brand and item_category, in additionalItemParameters. GA4 allows up to 27 custom parameters per item. Example: item_brand = “Acme”.
  • User properties: Details about the customer, such as customerType (NEW, RETURNING or REENGAGED), customerValueBucket (LOW, MEDIUM or HIGH) and additionalUserProperties. They travel only inside an event. Example: customerType = NEW.
  • Customer identifiers: userId and userData are optional. To protect the data, trim and lowercase emails, write phone numbers in E.164 format, hash the value with SHA-256, then encode it as hex or Base64 and set the request-level encoding to match.

Checking That It Worked

You get three layers of feedback, from instant to final.

Layer What it tells you
validateOnly: true Runs the same checks as a real send but saves nothing. One bad event fails the whole request. A pass does not guarantee processing will succeed
fieldWarnings in the response An optional field was dropped. The request itself was accepted
requestStatus.retrieve The final result for each destination. Check about 30 minutes after sending, then back off (multiply the wait by 1.3, up to 60 minutes, and stop after 24 hours). For example, check at about 30, 39 and 51 minutes, then hourly

Keep checking with longer waits until the status is final, and still read warnings on SUCCESS.

The status is one of four values: PROCESSING (not final, check again), SUCCESS (read warning_info anyway), PARTIAL_SUCCESS and FAILED (read error_info). Test-only requests sent with validateOnly have no status to check.

Confirming in GA4: When the status shows SUCCESS with no unexpected warnings, look for the events in GA4 reporting. A corrected resend of the same transactionId will not replace the original, so test before the first real send. Keep the full error, including the requestId, for every failure, because Google support asks for it.

Common errors and how to fix them

Error What to do
NOT_FOUND on product_destination_id The ID does not match your account. Check that the Measurement ID belongs to a web stream of that property (not another property’s G- ID), that the account or service account email has Editor or Administrator, and, for transaction-ID events, that the property is allowlisted
INVALID_EVENT_NAME You used a reserved GA4 event name. Rename it
INVALID_CURRENCY_CODE Use an ISO 4217 code: USD, not US$
INVALID_HEX_ENCODING / INVALID_SHA256_FORMAT Customer data is plain text (first error) or encoded but not hashed (second). Hash it, then encode it
OPERATING_ACCOUNT_LOGIN_ACCOUNT_MISMATCH Leave out loginAccount or make it equal to operatingAccount
PERMISSION_DENIED (service account) The service account is missing the Service Usage Consumer role on the project, or has not been added to the GA4 property. Grant the role and add its email as Editor
PERMISSION_DENIED + SERVICE_DISABLED The API is not turned on in the Cloud project. Enable it (the error includes an activation link) and wait a few minutes
Old events not showing in GA4 The timestamp is older than 72 hours. For example, an event from 4 days ago will not appear. Send fresh events
429 RESOURCE_EXHAUSTED You hit a usage limit. Put more events in each request and keep at most 10 requests running at once. For example, send 1 request of 2,000 events instead of 2,000 requests
UNAVAILABLE, INTERNAL, DEADLINE_EXCEEDED A temporary problem on Google’s side. Retry with increasing waits and some randomness. Do not retry other client errors until you fix them

Usage Limits in Plain Terms

Importantly, limits apply to your Google Cloud project rather than to an individual GA4 property. For most analytics feeds they leave plenty of room.

Limit Value What it means in practice
Events per request 2,000 5,000 events take 3 requests (2,000 + 2,000 + 1,000)
User identifiers per event 10 One event with 2 emails and 1 phone number uses 3 of the 10
Destinations per request 10 The same events sent to 2 GA4 properties use 2 destinations
Sending (ingestion) 300 requests per minute and 100,000 per day, per Cloud project 1 million events a day at 2,000 per request is 500 requests, far under the cap
Everything else 300 requests per minute and 50,000 per day Account and resource management calls count here, not against sending
If you go over HTTP 429 RESOURCE_EXHAUSTED The error names which quota you hit, for example the daily or per-minute write limit
Analytics 360 Quotas are per Cloud project. No higher limits for 360 are documented A 360 and a standard property sent from one Cloud project share the same limits
Not published Payload size and an exact requests-per-second value Pace your sending. Google’s best practice is up to 10 requests at a time

Google separates limits by service and by reading versus writing, which gives four separate pools: Ingestion write, Ingestion read, Other write and Other read. Each has its own per-minute and per-day count. Google does not say which calls count as read or write, so treat sending events as writing and status checks as likely reading.

Rules to Remember Before Sending Events

To reduce failed sends, check the following rules before going live. Check these before you go live.

Area The rule Example
Event age The timestamp must be within the last 72 hours. To join or attribute with gtag.js or Firebase events, send within 48 hours of the browser or app event Sent now (Oct 12, 10:00), an event from Oct 9, 09:00 is rejected. One from Oct 10, 09:00 (49 hours old) is accepted but may not join its gtag.js event
Access The sending account (a person or a service account) needs Editor or Administrator on the GA4 property A service account that is not added in GA4, or is only a Viewer, cannot send events
Identifiers Web events need a clientId and app events need an appInstanceId. Transaction-ID events need a transactionId, and on web at least one of clientId, gclid, userId or userData A web purchase with only a transactionId is invalid. Add a clientId
Duplicates For the same transactionId and property, GA4 keeps the first one it receives gtag.js sends T_1001, then the API sends T_1001: the API copy is ignored
Allowlist Multi-source events with a transaction ID work only on allowlisted GA4 properties Not allowlisted? Request access through the form on the Events overview page
Ads bidding hold Multi-source events sent to web streams stay out of Google Ads conversion bidding for 14 days. App streams are not held A web purchase sent through the API today can feed bidding after 14 days
Event names Reserved GA4 names are rejected, and the 40-character limit applies first_visit is rejected. Use crm_signup instead
User property names Reserved: first_open_time, first_visit_time, last_deep_link_referrer, user_id, first_open_after_install. No google_, ga_ or firebase_ prefix ga_tier is invalid. plan_tier is fine
Dedicated fields Never repeat transaction_id, currency or value in additionalEventParameters, or quantity, price or item_id in additionalItemParameters Send the order value in conversionValue, not as a parameter named value
Item prices unitPrice excludes tax, shipping and event-level discounts. For an item-level discount, use the discounted price A $20 item with a 10% item discount: unitPrice 18
Value formats currency is a 3-letter ISO 4217 code. Region is a 2-letter ISO 3166-1 code. eventSource, if set, must match the stream “USD” not “$”. “IN” not “India”. WEB for a G- stream
Hashing Email, phone, name and street: normalize, hash with SHA-256, then encode as hex or Base64. City, state, postal code and country: normalize but do not hash. Send userId together with userData ” John.Doe+*@***il.com ” becomes jo*****@***il.com, then SHA-256
Not supported Linked data partners as GA4 destinations, and standalone uploads of user data or user properties A request with only userProperties and no event is not possible. Attach them to an event
All or nothing One invalid event fails the whole request 2,000 events with 1 bad name: none are processed. Fix it and resend all 2,000

Putting It Together: A CRM Purchase

Picture a hypothetical online store that confirms some orders by phone and records them in its CRM. It wants those purchases in GA4, tied to the visitor who first browsed the website. Here is how it would use the API.

  1. The team creates a service account, gives it Editor access to the GA4 property and turns on the API in its Cloud project.
  2. When a customer confirms an order, the CRM exports the order ID, value, currency, items, and the visitor’s clientId captured on the website.
  3. A small script builds the request with those details and sends it with validateOnly: true.
  4. Once the test passes, the script sends the request for real and stores the requestId.
  5. After that, the script checks the status about 30 minutes later and logs any warnings or errors.
  6. An analyst confirms in GA4 reports that the purchase shows the right value, currency and transaction ID.

However, this example illustrates the mechanics; it does not mean the API will resolve every tracking discrepancy.

Not sure your server-side setup is ready for this? Tatvic’s Google Tag Manager services team can review your GA4 setup, event sources and current infrastructure, then recommend the right architecture.

Consent and Privacy

Sending data from your server does not make tracking compliant on its own. To protect user privacy, apply consent signals and data-sharing rules at every step of the flow, and hash customer data before it leaves your systems. Our guide to implementing Google Consent Mode v2 covers the basics.

Key Takeaways: How to Get Started with the Data Manager API

  • The Data Manager API sends server-side and offline events to GA4 web and app streams. As a result, you can also identify issues through the feedback it provides.
  • For automated feeds, use a service account with Service Usage Consumer on the project and Editor or Administrator on the GA4 property.
  • Every request needs a destination, events and an access token with the datamanager scope.
  • Keep events within 72 hours, send the right identifier for the stream and include a transactionId on purchases.
  • Batch up to 2,000 events per request and pace your sending to stay within 300 requests per minute.
  • For every real request, test first, save the requestId and then check the status about 30 minutes later.

Need help with your server-side GA4 pipeline?

Tatvic’s experts can review your GA4 measurement, event quality and server-side architecture. Get a GA4 Measurement Audit or explore Tatvic’s Google Analytics 4 Services to find the gaps and choose the right approach for your business.

Automate your marketing with Tatvic

Frequently Asked

FAQs

It sends server-side and offline events, such as purchases, leads and refunds, from your server or CRM to a GA4 web or app stream. Each request is checked, returns a requestId and can be followed up with a status check.

The setup steps in this guide are manageable for a technical analyst. Sending events on a regular schedule usually means a small script or integration, so many teams involve an engineer for that part.

You need credentials from a Google Cloud project, either a service account (recommended for servers) or OAuth user credentials. API keys are not supported. The account that sends the events also needs Editor or Administrator access on the GA4 property.

Set validateOnly: true. It runs the same checks as a real send and saves nothing. A pass does not guarantee that processing will succeed, so also check the status about 30 minutes after a real send.

Up to 2,000 events per request, with up to 10 user identifiers per event and up to 10 destinations per request. Sending is limited to 300 requests per minute and 100,000 requests per day per Cloud project.

The timestamp must be within the last 72 hours. To join or attribute with gtag.js or Firebase events, send within 48 hours of the browser or app event.

GA4 keeps the first copy it receives for that transactionId and property. A corrected resend of the same transactionId will not replace the original, so test before the first real send.

The API fails fast. One invalid event fails the whole request, so a batch of 2,000 events with one bad event name processes none of them. Fix the event and resend all 2,000.

It makes problems visible. You get tests, field-level errors and status reports. It does not fix faulty event setups, missing identifiers, consent restrictions or timestamps outside the allowed window.
Bot Icon
Bot Icon

Tatvic Bot

Explore About Tatvic and Services