---
title: "Upload Google Ads conversions"
method: POST
path: "/api/apps/{app_id}/google-ads/conversions/upload"
---

# Upload Google Ads conversions

`POST /api/apps/{app_id}/google-ads/conversions/upload`

<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>

Reports conversions to Google Ads from your own server, for the conversions only your server knows about.

This is the counterpart to the browser-side `gtag('event', 'conversion', ...)` call. Use it for a payment that actually settled, an order confirmed after review, or anything an ad blocker would stop the browser reporting. Each conversion needs the goal it counts towards, when it happened, and the click it came from.

Read the outcome from the counts rather than from `status`. A partial batch still comes back as `success`, so compare `total_uploaded` with `total_submitted`. The difference is accounted for by `duplicates_removed`, `cross_request_duplicates`, and `skipped_no_click_id`, and anything left over is rows Google rejected one by one. Google does not tell Base44 which rows those were, so the response cannot name them.

Send `order_id` on every conversion you can. Base44 remembers each conversion it has reported and never reports the same one twice, keyed on the order or click ID together with the goal and the timestamp, and that record does not expire. Sending a corrected value for a conversion you already reported has no effect for that reason. Use [Upload enhanced conversions](/api-reference/upload-enhanced-google-ads-conversions) to add buyer details to a conversion you have already reported.

<Note>There is no cap on how many rows you can send and no rate limit on this endpoint. Base44 sends them to Google in batches of 2,000, one batch after another, while your request stays open, so a very large list means a very long request. Keep each call to a few thousand rows and send several calls instead.</Note>

<Note>A 409 means the request to Google timed out after it was sent, so those conversions may or may not have landed. Retrying is safe. Base44 has already recorded them as sent, so the retry reports them under `cross_request_duplicates` rather than counting them twice.</Note>

<Note>This endpoint accepts a personal API key. Workspace API keys are not authorized for it and are rejected with a 403.</Note>

## Path parameters

- `app_id` string, required — ID of the app whose Google Ads conversion tracking you want to manage.

## Request body

- object
  - `conversions` object[] — The conversions to report. An empty list is accepted and reports nothing.
    - `conversion_action_id` string, required — Google Ads' own ID for the goal this conversion counts towards, as returned in `google_action_id` by [List conversion actions](/api-reference/list-google-ads-conversion-actions).
    - `conversion_date_time` string, required — When the conversion happened, as `YYYY-MM-DD HH:MM:SS+HH:MM` including the offset. Google rejects any other format with a 400, and Base44 passes the value through unchanged.
    - `gclid` string — The Google click ID the conversion is attributed to, taken from the `gclid` query parameter on the landing URL.
    - `gbraid` string — The click ID for an iOS app-to-web journey, used instead of `gclid` when the landing URL carries `gbraid`.
    - `wbraid` string — The click ID for a web-to-app journey, used instead of `gclid` when the landing URL carries `wbraid`.
    - `conversion_value` number — What the conversion was worth, in the currency given by `currency_code`. It must be zero or more.
    - `currency_code` string — Currency of `conversion_value` as a three-letter ISO 4217 code. Base44 uppercases it before sending, so `eur` and `EUR` both work.
    - `order_id` string — Your own ID for the order behind the conversion. Send it whenever you have one, because it is what lets Google dedupe the conversion on its side as well.
    - `user_identifiers` object — Hashed details of the buyer, which improve how well Google matches the conversion to a click. Hash every value yourself, because Base44 sends them as given.
      - `hashed_email` string — The buyer's email address, lowercased, trimmed, and SHA-256 hashed to lowercase hex.
      - `hashed_phone_number` string — The buyer's phone number in E.164 form, SHA-256 hashed to lowercase hex.
      - `hashed_first_name` string — The buyer's first name, lowercased and SHA-256 hashed to lowercase hex. Base44 only sends it together with `hashed_last_name`, so one without the other is dropped and has no effect.
      - `hashed_last_name` string — The buyer's last name, lowercased and SHA-256 hashed to lowercase hex. Send it together with `hashed_first_name`, because one without the other is dropped.

## Response `200`

What happened to each conversion in the batch.

- ConversionUploadResult — What happened to a batch of uploaded conversions.
  - `status` string, required — Whether anything reached Google. The value is `success` when at least one conversion landed and `no_rows_uploaded` when none did, so it does not tell you the whole batch landed.
  - `total_submitted` integer, required — How many conversions you sent, counted before any of the checks below.
  - `duplicates_removed` integer, required — How many conversions were dropped as duplicates of another conversion in the same request, matching on order or click ID together with the goal and the timestamp.
  - `cross_request_duplicates` integer, required — How many conversions a previous request already reported. Base44 remembers every conversion it has sent, permanently, so a retried upload lands here instead of counting twice.
  - `skipped_no_click_id` integer, required — How many conversions were dropped before Google saw them because they carried none of `gclid`, `gbraid`, or `wbraid`. Google requires one of the three.
  - `total_uploaded` integer, required — How many conversions Google accepted.

## Other responses

- `400` — Google Ads rejected the whole request, for example a malformed `conversion_date_time` or a `conversion_action_id` that is not on the account. The response message carries Google's reason.
- `401` — Missing or invalid credentials.
- `403` — You don't have access to this app, the app does not exist, or you used a workspace API key. A missing app and an app you cannot reach are deliberately the same answer.
- `404` — The app has no connected Google Ads account.
- `409` — The request to Google Ads timed out after being sent, so the conversions may or may not have landed. Retrying is safe and reports them as cross-request duplicates.
- `422` — Validation Error
- `429` — Google Ads is rate limiting the account. Retry later.

---

[API](https://skmtc.dev/adexad/apis/base44-app-management-api.md) · [All operations](https://skmtc.dev/adexad/apis/base44-app-management-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/adexad/base44-app-management-api/revisions/7f5ce8287501/schema)
