1. Overview
XOhan Ads connects four event sources to a single Google Ads manager account:
- Collector — HTTPS endpoint receiving first-party SDK events and partner server-to-server postbacks.
- Validator — schema checks, idempotency, consent and fraud filtering.
- Forwarder — batches verified events and uploads them through the API.
- Console — dashboards, retry queue and audience sync (this website).
2. Authentication
OAuth 2.0 authorisation-code flow with offline access. The refresh token is stored encrypted and
only the adwords scope is requested.
https://accounts.google.com/o/oauth2/v2/auth
?client_id={oauth_client_id}
&redirect_uri=https://xohanooc.com/oauth/callback
&response_type=code
&access_type=offline
&prompt=consent
&scope=https://www.googleapis.com/auth/adwords
Every request also carries our developer token and a login-customer-id header when the call is made through the manager account. Tokens rotate automatically; a revoked token halts the forwarder and raises an alert instead of retrying with the old credential.
3. Event schema
All sources are normalised into one internal schema before validation.
| Field | Required | Notes |
|---|---|---|
event_id | yes | Idempotency key, unique per app install |
app_id | yes | Store / package identifier |
event_type | yes | first_open, sign_up, add_to_cart, in_app_purchase, view_item |
event_time | yes | UTC, ISO-8601 |
value, currency | optional | Required for value-based bidding |
gclid / gbraid / wbraid | optional | Click identifiers from the partner postback |
consent_ad_user_data | yes | Gate: when false, identifiers are dropped and only the aggregate event is stored |
4. Conversion upload
Verified events are grouped per app and per day, then submitted in batches. Each successful operation returns a request id which is stored against the event so the conversion can be traced or retracted later.
// one batch, one app, one day POST /v{api_version}/customers/{customer_id}:uploadClickConversions Authorization: Bearer {access_token} developer-token: {developer_token} login-customer-id: {manager_account_id} { "conversions": [{ "gclid": "EAIaIQobChMI7...", "conversionAction": "customers/0000000000/conversionActions/111111111", "conversionDateTime": "2026-06-11 14:32:08+00:00", "conversionValue": 24.99, "currencyCode": "USD", "orderId": "xo-order-88213" }], "partialFailure": true }
5. Conversion adjustments
When a partner retracts or restates an event, the pipeline sends an adjustment instead of a second
conversion, keeping totals consistent. An adjustment is located either by orderId or by
a gclidDateTimePair, depending on which identifier the original conversion used.
POST /v{api_version}/customers/{customer_id}:uploadConversionAdjustments { "conversionAdjustments": [{ "conversionAction": "customers/0000000000/conversionActions/111111111", "adjustmentType": "RETRACTION", "orderId": "xo-order-88213" }], "partialFailure": true }
6. Remarketing audiences
Segment membership is computed from forwarded events (for example high-value purchaser in 180 days), then synced on a daily schedule with automatic membership expiry so no user stays in an audience indefinitely. Membership only ever includes users whose consent flag was true at ingestion.
7. Errors and retries
- Partial failures are parsed per operation; only failed operations enter the retry queue.
- Exponential backoff (30s → 8h, maximum 5 attempts) for transient and rate-limit errors.
- Validation errors raise an internal alert and are never retried blindly.
- Daily reconciliation compares forwarded counts with source counts per app.
8. Retention
Raw payloads are kept 180 days for reconciliation, then purged. Aggregated metrics are kept 24 months. Deletion requests are handled as described in the privacy policy.
9. Access and environments
| Environment | Data | Who |
|---|---|---|
| Production | Live app events, live Google Ads manager account | Named operations staff |
| Staging | Synthetic events, test conversion actions | Engineering |
| Public demo (this site) | Static sample data, no API calls | Anyone with the demo account |
Questions about this integration: krzemienrao669@gmail.com