Upload conversions

This guide provides detailed instructions for uploading offline conversions using the Campaign Manager 360 API Conversions service. Before continuing, it is recommended that you review the Overview for an introduction to offline conversions and to familiarize yourself with concepts discussed in this guide.

Configure conversion resources

The first step in the conversion upload process is creating one or more Conversion resource objects. Each of these objects represent a single conversion event and contain a few required fields:

Field Description
floodlightActivityId The Floodlight activity with which this conversion will be associated.
floodlightConfigurationId The Floodlight configuration used by the specified activity.
ordinal A value used to control how conversions from the same device or user on the same day are deduplicated. See the FAQ for more information.
timestampMicros The timestamp of the conversion, in microseconds since the Unix epoch.

Additionally, each object must contain one of the following fields:

Field Description
encryptedUserId A single encrypted user ID obtained from the %m match macro or Data Transfer.
encryptedUserIdCandidates[] A list of encrypted user IDs obtained from the %m match macro or Data Transfer. The first of these IDs with a recorded Floodlight exposure prior to the specified timestampMicros will be used. If none of the IDs match an existing exposure, an error will be thrown.
dclid A Display Click Identifier generated by Campaign Manager 360 or Display & Video 360.
gclid A Google Click Identifier generated by Google Ads or Search Ads 360.
matchId A unique advertiser created identifier passed to Campaign Manager 360 via a Floodlight tag.
mobileDeviceId An unencrypted mobile ID in the IDFA or AdID format or a Connected TV Identifier for Advertising (IFA) from a supported CTV device platform (Roku, Fire TV, Android TV, Apple TV, Xbox, Samsung, Vizio). Note that Google does not support YouTube Connected TV IFAs.

Optional fields are documented in the reference documentation. Note that the quantity field, though optional, must be at least 1 for the conversion to count towards certain metrics (such as Total Conversions) when running reports.

The example below illustrates the creation of a simple conversion resource object:

C#

// Generate a timestamp in milliseconds since Unix epoch.
TimeSpan timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1);
long currentTimeInMilliseconds = (long) timeSpan.TotalMilliseconds;

// Find the Floodlight configuration ID based on the provided activity ID.
FloodlightActivity floodlightActivity =
    service.FloodlightActivities.Get(profileId, floodlightActivityId).Execute();
long floodlightConfigurationId = (long) floodlightActivity.FloodlightConfigurationId;

// Create the conversion.
Conversion conversion = new Conversion();
conversion.EncryptedUserId = conversionUserId;
conversion.FloodlightActivityId = floodlightActivityId;
conversion.FloodlightConfigurationId = floodlightConfigurationId;
conversion.Ordinal = currentTimeInMilliseconds.ToString();
conversion.TimestampMicros = currentTimeInMilliseconds * 1000;

Java

long currentTimeInMilliseconds = System.currentTimeMillis();

// Find Floodlight configuration ID based on the provided activity ID.
FloodlightActivity floodlightActivity = reporting.floodlightActivities()
    .get(profileId, floodlightActivityId).execute();
long floodlightConfigurationId = floodlightActivity.getFloodlightConfigurationId();

Conversion conversion = new Conversion();
conversion.setEncryptedUserId(encryptedUserId);
conversion.setFloodlightActivityId(floodlightActivityId);
conversion.setFloodlightConfigurationId(floodlightConfigurationId);
conversion.setOrdinal(String.valueOf(currentTimeInMilliseconds));
conversion.setTimestampMicros(currentTimeInMilliseconds * 1000);

PHP

$currentTimeInMicros = time() * 1000 * 1000;

// Find Floodlight configuration ID based on provided activity ID.
$activity = $this->service->floodlightActivities->get(
    $values['user_profile_id'],
    $values['floodlight_activity_id']
);
$floodlightConfigId = $activity->getFloodlightConfigurationId();

$conversion = new Google_Service_Dfareporting_Conversion();
$conversion->setEncryptedUserId($values['encrypted_user_id']);
$conversion->setFloodlightActivityId($values['floodlight_activity_id']);
$conversion->setFloodlightConfigurationId($floodlightConfigId);
$conversion->setOrdinal($currentTimeInMicros);
$conversion->setTimestampMicros($currentTimeInMicros);

Python

# Look up the Floodlight configuration ID based on activity ID.
floodlight_activity = service.floodlightActivities().get(
    profileId=profile_id, id=floodlight_activity_id).execute()
floodlight_config_id = floodlight_activity['floodlightConfigurationId']

current_time_in_micros = int(time.time() * 1000000)

# Construct the conversion.
conversion = {
    'encryptedUserId': encrypted_user_id,
    'floodlightActivityId': floodlight_activity_id,
    'floodlightConfigurationId': floodlight_config_id,
    'ordinal': current_time_in_micros,
    'timestampMicros': current_time_in_micros
}

Ruby

# Look up the Floodlight configuration ID based on activity ID.
floodlight_activity = service.get_floodlight_activity(profile_id,
  floodlight_activity_id)
floodlight_config_id = floodlight_activity.floodlight_configuration_id

current_time_in_micros = DateTime.now.strftime('%Q').to_i * 1000

# Construct the conversion.
conversion = DfareportingUtils::API_NAMESPACE::Conversion.new(
  encrypted_user_id: encrypted_user_id,
  floodlight_activity_id: floodlight_activity_id,
  floodlight_configuration_id: floodlight_config_id,
  ordinal: current_time_in_micros,
  timestamp_micros: current_time_in_micros
)

Specify encryption info

If you plan to attribute offline conversions to encrypted user IDs, as in the previous example, you'll need to provide some details about how they're encrypted as part of your insert request. In particular, you'll need to know:

  1. The encryption source, which describes where a batch of encrypted IDs came from. Acceptable values are AD_SERVING for IDs sourced from the %m match macro, or DATA_TRANSFER for IDs sourced from Data Transfer files.

  2. The encryption entity, which is a unique set of values used to encrypt user IDs. These values normally relate to a Campaign Manager 360 account when the source is Data Transfer, or a Campaign Manager 360 advertiser when the source is the %m macro, but this is not always the case. If you're not sure, contact your Campaign Manager 360 account representative or Campaign Manager 360 support for more information.

When necessary, creating an EncryptionInfo object that specifies these values is the second step in the conversion upload process:

C#

// Create the encryption info.
EncryptionInfo encryptionInfo = new EncryptionInfo();
encryptionInfo.EncryptionEntityId = encryptionEntityId;
encryptionInfo.EncryptionEntityType = encryptionEntityType;
encryptionInfo.EncryptionSource = encryptionSource;

Java

// Create the encryption info.
EncryptionInfo encryptionInfo = new EncryptionInfo();
encryptionInfo.setEncryptionEntityId(encryptionEntityId);
encryptionInfo.setEncryptionEntityType(encryptionEntityType);
encryptionInfo.setEncryptionSource(encryptionSource);

PHP

$encryptionInfo = new Google_Service_Dfareporting_EncryptionInfo();
$encryptionInfo->setEncryptionEntityId($values['encryption_entity_id']);
$encryptionInfo->setEncryptionEntityType($values['encryption_entity_type']);
$encryptionInfo->setEncryptionSource($values['encryption_source']);

Python

# Construct the encryption info.
encryption_info = {
    'encryptionEntityId': encryption_entity_id,
    'encryptionEntityType': encryption_entity_type,
    'encryptionSource': encryption_source
}

Ruby

# Construct the encryption info.
encryption_info = DfareportingUtils::API_NAMESPACE::EncryptionInfo.new(
  encryption_entity_id: encryption[:entity_id],
  encryption_entity_type: encryption[:entity_type],
  encryption_source: encryption[:source]
)

Be aware that each insert request may contain only one EncryptionInfo object. This means that all of the conversions included in a given request must originate from the same source and use the same encryption entity.

Generate an insert request

The final step in this process is to upload your conversions with a call to batchinsert. This method accepts a ConversionsBatchInsertRequest object, which combines the set of conversions to be uploaded with their associated encryption info (when necessary):

C#

// Insert the conversion.
ConversionsBatchInsertRequest request = new ConversionsBatchInsertRequest();
request.Conversions = new List<Conversion>() { conversion };
request.EncryptionInfo = encryptionInfo;

ConversionsBatchInsertResponse response =
    service.Conversions.Batchinsert(request, profileId).Execute();

Java

ConversionsBatchInsertRequest request = new ConversionsBatchInsertRequest();
request.setConversions(ImmutableList.of(conversion));
request.setEncryptionInfo(encryptionInfo);

ConversionsBatchInsertResponse response = reporting.conversions()
    .batchinsert(profileId, request).execute();

PHP

$batch = new Google_Service_Dfareporting_ConversionsBatchInsertRequest();
$batch->setConversions([$conversion]);
$batch->setEncryptionInfo($encryptionInfo);

$result = $this->service->conversions->batchinsert(
    $values['user_profile_id'],
    $batch
);

Python

# Insert the conversion.
request_body = {
    'conversions': [conversion],
    'encryptionInfo': encryption_info
}
request = service.conversions().batchinsert(profileId=profile_id,
                                            body=request_body)
response = request.execute()

Ruby

# Construct the batch insert request.
batch_insert_request =
  DfareportingUtils::API_NAMESPACE::ConversionsBatchInsertRequest.new(
    conversions: [conversion],
    encryption_info: encryption_info
  )

# Insert the conversion.
result = service.batchinsert_conversion(profile_id, batch_insert_request)

Be aware that Campaign Manager 360 attempts to insert each conversion in your request on a best-effort basis, rather than inserting the entire batch as an all-or-nothing transaction. If some conversions in a batch fail to insert, others might still be inserted successfully. Therefore, it's recommended that you inspect the returned ConversionsBatchInsertResponse, to determine the status of each conversion:

C#

// Handle the batchinsert response.
if (!response.HasFailures.Value) {
  Console.WriteLine("Successfully inserted conversion for encrypted user ID {0}.",
      conversionUserId);
} else {
  Console.WriteLine("Error(s) inserting conversion for encrypted user ID {0}:",
      conversionUserId);

  ConversionStatus status = response.Status[0];
  foreach(ConversionError error in status.Errors) {
    Console.WriteLine("\t[{0}]: {1}", error.Code, error.Message);
  }
}

Java

if (!response.getHasFailures()) {
  System.out.printf("Successfully inserted conversion for encrypted user ID %s.%n",
      encryptedUserId);
} else {
  System.out.printf("Error(s) inserting conversion for encrypted user ID %s:%n",
      encryptedUserId);

  // Retrieve the conversion status and report any errors found. If multiple conversions
  // were included in the original request, the response would contain a status for each.
  ConversionStatus status = response.getStatus().get(0);
  for (ConversionError error : status.getErrors()) {
    System.out.printf("\t[%s]: %s.%n", error.getCode(), error.getMessage());
  }
}

PHP

if (!$result->getHasFailures()) {
    printf(
        'Successfully inserted conversion for encrypted user ID %s.',
        $values['encrypted_user_id']
    );
} else {
    printf(
        'Error(s) inserting conversion for encrypted user ID %s:<br><br>',
        $values['encrypted_user_id']
    );

    $status = $result->getStatus()[0];
    foreach ($status->getErrors() as $error) {
        printf('[%s] %s<br>', $error->getCode(), $error->getMessage());
    }
}

Python

if not response['hasFailures']:
  print ('Successfully inserted conversion for encrypted user ID %s.'
         % encrypted_user_id)
else:
  print ('Error(s) inserting conversion for encrypted user ID %s.'
         % encrypted_user_id)

  status = response['status'][0]
  for error in status['errors']:
    print '\t[%s]: %s' % (error['code'], error['message'])

Ruby

if result.has_failures
  puts format('Error(s) inserting conversion for encrypted user ID %s.',
    encrypted_user_id)

  status = result.status[0]
  status.errors.each do |error|
    puts format("\t[%s]: %s", error.code, error.message)
  end
else
  puts format('Successfully inserted conversion for encrypted user ID %s.',
    encrypted_user_id)
end

The status field of the response, as seen above, will contain a ConversionStatus object for every conversion included in the original request. If you're only interested in conversions that failed to insert, the hasFailures field can be used to quickly determine if any conversion in the provided batch failed.

Verify conversions were processed

Uploaded conversions will generally be processed and available to report on within 24 hours. To verify whether or not conversions you've uploaded were processed, it's recommended to run a Floodlight Impressions report. Unlike other attribution reports, these will return both attributed (associated with an ad) and unattributed conversions by default. This makes it ideal for quickly checking to see whether the conversions you've sent have made it to Campaign Manager 360.