Upload offline conversions

This guide provides detailed instructions for uploading offline conversions using the DCM/DFA Reporting and Trafficking API's 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 represents a single conversion event, and must contain the following information:

Field Description
encryptedUserId or mobileDeviceId Either an encrypted user ID or mobile device ID. See Obtain device and user IDs for more information.
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 de-duplicated.
timestampMicros The timestamp of the conversion, in microseconds since the Unix epoch.

Optional conversion fields are explained in the reference documentation. 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 DCM account when the source is Data Transfer, or a DCM advertiser when the source is the %m macro, but this is not always the case. If you're not sure, contact your DCM account representative or DCM 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 DCM 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

unless result.has_failures
  puts 'Successfully inserted conversion for encrypted user ID %s.' %
      encrypted_user_id
else
  puts 'Error(s) inserting conversion for encrypted user ID %s.' %
      encrypted_user_id

  status = result.status[0]
  status.errors.each do |error|
    puts "\t[%s]: %s" % [error.code, error.message]
  end
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 DCM.

Frequently asked questions

Are there any restrictions on what I can upload?

There are a few restrictions to be aware of:

  1. batchinsert requests are limited to 1,000 conversions.
  2. The timestamp of a conversion can be at most 25 days old from the time of submission.
  3. You can only submit conversions to a Floodlight configuration to which your DCM user profile has access.

Can I use gclids?

The DCM/DFA Reporting and Trafficking API does not currently support inserting conversions tied to a gclid (Google Click ID).

What if I insert the same conversion to both DCM and DS?

If you insert the same conversion to both DCM and DS (DoubleClick Search), the entries will not be de-duplicated. Please use separate Floodlight activities in this case.

Send feedback about...

DCM/DFA Reporting and Trafficking API
DCM/DFA Reporting and Trafficking API