The AdWords API will sunset on April 27, 2022. Migrate to the Google Ads API to take advantage of the latest Google Ads features.

Remarketing Samples

The code samples below provide examples of common remarketing functions using the AdWords API. Client Library.

Create a remarketing user list (audience)

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;
using System.Collections.Generic;
using System.Linq;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example illustrates how to create a user list a.k.a. audience.
    /// </summary>
    public class AddAudience : ExampleBase
    {
        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            AddAudience codeExample = new AddAudience();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser());
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example illustrates how to create a user list a.k.a. audience.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        public void Run(AdWordsUser user)
        {
            using (AdwordsUserListService userListService =
                (AdwordsUserListService) user.GetService(AdWordsService.v201809
                    .AdwordsUserListService))
                using (ConversionTrackerService conversionTrackerService =
                    (ConversionTrackerService) user.GetService(AdWordsService.v201809
                        .ConversionTrackerService))
                {
                    BasicUserList userList = new BasicUserList
                    {
                        name = "Mars cruise customers #" + ExampleUtilities.GetRandomString(),
                        description = "A list of mars cruise customers in the last year.",
                        status = UserListMembershipStatus.OPEN,
                        membershipLifeSpan = 365
                    };

                    UserListConversionType conversionType = new UserListConversionType
                    {
                        name = userList.name
                    };
                    userList.conversionTypes = new UserListConversionType[]
                    {
                        conversionType
                    };

                    // Optional: Set the user list status.
                    userList.status = UserListMembershipStatus.OPEN;

                    // Create the operation.
                    UserListOperation operation = new UserListOperation
                    {
                        operand = userList,
                        @operator = Operator.ADD
                    };

                    try
                    {
                        // Add the user list.
                        UserListReturnValue retval = userListService.mutate(new UserListOperation[]
                        {
                            operation
                        });

                        UserList newUserList = retval.value[0];

                        Console.WriteLine("User list with name '{0}' and id '{1}' was added.",
                            newUserList.name, newUserList.id);

                        List<string> conversionIds = new List<string>();
                        Array.ForEach(userList.conversionTypes,
                            delegate(UserListConversionType item)
                            {
                                conversionIds.Add(item.id.ToString());
                            });

                        // Create the selector.
                        Selector selector = new Selector()
                        {
                            fields = new string[]
                            {
                                ConversionTracker.Fields.Id,
                                ConversionTracker.Fields.GoogleGlobalSiteTag,
                                ConversionTracker.Fields.GoogleEventSnippet
                            },
                            predicates = new Predicate[]
                            {
                                Predicate.In(ConversionTracker.Fields.Id, conversionIds.ToArray())
                            }
                        };

                        // Get all conversion trackers.
                        ConversionTrackerPage page = conversionTrackerService.get(selector);

                        if (page != null && page.entries != null)
                        {
                            foreach (ConversionTracker tracker in page.entries)
                            {
                                Console.WriteLine(
                                    "Google global site tag:\n{0}\nGoogle event snippet:\n{1}",
                                    tracker.googleGlobalSiteTag, tracker.googleGlobalSiteTag);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        throw new System.ApplicationException(
                            "Failed to add user lists (a.k.a. audiences).", e);
                    }
                }
        }
    }
}

Create an AdWords conversion tracker and add to it upload conversions

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;
using System.Collections.Generic;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example adds an AdWords conversion tracker and an upload conversion tracker.
    /// </summary>
    public class AddConversionTrackers : ExampleBase
    {
        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            AddConversionTrackers codeExample = new AddConversionTrackers();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser());
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example adds an AdWords conversion tracker and an upload " +
                    "conversion tracker.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        public void Run(AdWordsUser user)
        {
            using (ConversionTrackerService conversionTrackerService =
                (ConversionTrackerService) user.GetService(AdWordsService.v201809
                    .ConversionTrackerService))
            {
                List<ConversionTracker> conversionTrackers = new List<ConversionTracker>();

                // Create an Adwords conversion tracker.
                AdWordsConversionTracker adWordsConversionTracker = new AdWordsConversionTracker
                {
                    name =
                        "Earth to Mars Cruises Conversion #" + ExampleUtilities.GetRandomString(),
                    category = ConversionTrackerCategory.DEFAULT,

                    // Set optional fields.
                    status = ConversionTrackerStatus.ENABLED,
                    viewthroughLookbackWindow = 15,
                    defaultRevenueValue = 23.41,
                    alwaysUseDefaultRevenueValue = true
                };
                conversionTrackers.Add(adWordsConversionTracker);

                // Create an upload conversion for offline conversion imports.
                UploadConversion uploadConversion = new UploadConversion
                {
                    // Set an appropriate category. This field is optional, and will be set to
                    // DEFAULT if not mentioned.
                    category = ConversionTrackerCategory.LEAD,
                    name = "Upload Conversion #" + ExampleUtilities.GetRandomString(),
                    viewthroughLookbackWindow = 30,
                    ctcLookbackWindow = 90,

                    // Optional: Set the default currency code to use for conversions
                    // that do not specify a conversion currency. This must be an ISO 4217
                    // 3-character currency code such as "EUR" or "USD".
                    // If this field is not set on this UploadConversion, AdWords will use
                    // the account's currency.
                    defaultRevenueCurrencyCode = "EUR",

                    // Optional: Set the default revenue value to use for conversions
                    // that do not specify a conversion value. Note that this value
                    // should NOT be in micros.
                    defaultRevenueValue = 2.50
                };

                // Optional: To upload fractional conversion credits, mark the upload conversion
                // as externally attributed. See
                // https://developers.google.com/adwords/api/docs/guides/conversion-tracking#importing_externally_attributed_conversions
                // to learn more about importing externally attributed conversions.

                // uploadConversion.isExternallyAttributed = true;

                conversionTrackers.Add(uploadConversion);

                try
                {
                    // Create operations.
                    List<ConversionTrackerOperation> operations =
                        new List<ConversionTrackerOperation>();
                    foreach (ConversionTracker conversionTracker in conversionTrackers)
                    {
                        operations.Add(new ConversionTrackerOperation()
                        {
                            @operator = Operator.ADD,
                            operand = conversionTracker
                        });
                    }

                    // Add conversion tracker.
                    ConversionTrackerReturnValue retval =
                        conversionTrackerService.mutate(operations.ToArray());

                    // Display the results.
                    if (retval != null && retval.value != null)
                    {
                        foreach (ConversionTracker conversionTracker in retval.value)
                        {
                            Console.WriteLine(
                                "Conversion with ID {0}, name '{1}', status '{2}' and " +
                                "category '{3}' was added.", conversionTracker.id,
                                conversionTracker.name, conversionTracker.status,
                                conversionTracker.category);
                            if (conversionTracker is AdWordsConversionTracker)
                            {
                                AdWordsConversionTracker newAdWordsConversionTracker =
                                    (AdWordsConversionTracker) conversionTracker;
                                Console.WriteLine(
                                    "Google global site tag:\n{0}\nGoogle event snippet:\n{1}",
                                    newAdWordsConversionTracker.googleGlobalSiteTag,
                                    newAdWordsConversionTracker.googleEventSnippet);
                            }
                        }
                    }
                    else
                    {
                        Console.WriteLine("No conversion trackers were added.");
                    }
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException("Failed to add conversion trackers.", e);
                }
            }
        }
    }
}

Create and populate a user list

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example adds a user list (a.k.a. audience) and uploads hashed
    /// email addresses to populate the list.
    ///
    /// <p>
    /// <em>Note:</em> It may take up to several hours for the list to be
    /// populated with members. Email addresses must be associated with a Google
    /// account. For privacy purposes, the user list size will show as zero until
    /// the list has at least 1000 members. After that, the size will be rounded
    /// to the two most significant digits.
    /// </p>
    /// </summary>
    public class AddCrmBasedUserList : ExampleBase
    {
        private static readonly string[] EMAILS = new string[]
        {
            "customer1@example.com",
            "customer2@example.com",
            " Customer3@example.com "
        };

        private const string FIRST_NAME = "John";
        private const string LAST_NAME = "Doe";
        private const string COUNTRY_CODE = "US";
        private const string ZIP_CODE = "10001";

        private SHA256 digest = SHA256.Create();

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            AddCrmBasedUserList codeExample = new AddCrmBasedUserList();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser());
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example adds a user list (a.k.a. audience) and " +
                    "uploads hashed email addresses to populate the list.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        public void Run(AdWordsUser user)
        {
            using (AdwordsUserListService userListService =
                (AdwordsUserListService) user.GetService(AdWordsService.v201809
                    .AdwordsUserListService))
            {
                // Create a user list.
                CrmBasedUserList userList = new CrmBasedUserList()
                {
                    name = "Customer relationship management list #" +
                        ExampleUtilities.GetRandomString(),
                    description = "A list of customers that originated from email addresses",

                    // CRM - based user lists can use a membershipLifeSpan of 10000 to indicate
                    // unlimited; otherwise normal values apply.
                    membershipLifeSpan = 30L,
                    uploadKeyType = CustomerMatchUploadKeyType.CONTACT_INFO
                };

                // Create operation.
                UserListOperation operation = new UserListOperation()
                {
                    operand = userList,
                    @operator = Operator.ADD
                };

                try
                {
                    // Add user list.
                    UserListReturnValue result = userListService.mutate(new UserListOperation[]
                    {
                        operation
                    });

                    Console.WriteLine(
                        "Created new user list with name = '{0}' and id = " + "'{1}'.",
                        result.value[0].name, result.value[0].id);

                    // Get user list ID.
                    long userListId = result.value[0].id;

                    // Prepare the emails for upload.
                    List<Member> memberList = new List<Member>();

                    // Hash normalized email addresses based on SHA-256 hashing algorithm.
                    string[] emailHashes = new string[EMAILS.Length];
                    for (int i = 0; i < EMAILS.Length; i++)
                    {
                        Member member = new Member
                        {
                            hashedEmail = ToSha256String(digest, ToNormalizedEmail(EMAILS[i]))
                        };
                        memberList.Add(member);
                    }

                    ;

                    // Add a user by first and last name.
                    AddressInfo addressInfo = new AddressInfo
                    {
                        // First and last name must be normalized and hashed.
                        hashedFirstName = ToSha256String(digest, FIRST_NAME),
                        hashedLastName = ToSha256String(digest, LAST_NAME),
                        // Country code and zip code are sent in plaintext.
                        zipCode = ZIP_CODE,
                        countryCode = COUNTRY_CODE
                    };

                    Member memberByAddress = new Member
                    {
                        addressInfo = addressInfo
                    };
                    memberList.Add(memberByAddress);

                    // Create operation to add members to the user list based on email
                    // addresses.
                    MutateMembersOperation mutateMembersOperation = new MutateMembersOperation()
                    {
                        operand = new MutateMembersOperand()
                        {
                            userListId = userListId,
                            membersList = memberList.ToArray()
                        },
                        @operator = Operator.ADD
                    };

                    // Add members to the user list based on email addresses.
                    MutateMembersReturnValue mutateMembersResult = userListService.mutateMembers(
                        new MutateMembersOperation[]
                        {
                            mutateMembersOperation
                        });

                    // Display results.
                    // Reminder: it may take several hours for the list to be populated
                    // with members.
                    foreach (UserList userListResult in mutateMembersResult.userLists)
                    {
                        Console.WriteLine(
                            "Email addresses were added to user list with " +
                            "name '{0}' and id '{1}'.", userListResult.name, userListResult.id);
                    }
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException(
                        "Failed to add user lists " +
                        "(a.k.a. audiences) and upload email addresses.", e);
                }
            }
        }

        /// <summary>
        /// Hash email address using SHA-256 hashing algorithm.
        /// </summary>
        /// <param name="digest">Provides the algorithm for SHA-256.</param>
        /// <param name="email">The email address to hash.</param>
        /// <returns>Hash email address using SHA-256 hashing algorithm.</returns>
        private static string ToSha256String(SHA256 digest, string email)
        {
            byte[] digestBytes = digest.ComputeHash(Encoding.UTF8.GetBytes(email));
            // Convert the byte array into an unhyphenated hexadecimal string.
            return BitConverter.ToString(digestBytes).Replace("-", string.Empty);
        }

        /// <summary>
        /// Removes leading and trailing whitespace and converts all characters to
        /// lower case.
        /// </summary>
        /// <param name="email">The email address to normalize.</param>
        /// <returns>A normalized copy of the string.</returns>
        private static string ToNormalizedEmail(string email)
        {
            return email.Trim().ToLower();
        }
    }
}

Create rule-based user lists

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;
using System.Collections.Generic;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example adds two rule-based remarketing user lists: one with no
    /// site visit date restrictions, and another that will only include users
    /// who visit your site in the next six months. See
    /// https://developers.google.com/adwords/api/docs/guides/rule-based-remarketing
    /// to learn more about rule based remarketing.
    /// </summary>
    public class AddRuleBasedRemarketingList : ExampleBase
    {
        private const string DATE_FORMAT_STRING = "yyyyMMdd";

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            AddRuleBasedRemarketingList codeExample = new AddRuleBasedRemarketingList();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser());
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example adds two rule-based remarketing user lists: one with " +
                    "no site visit date restrictions, and another that will only include users " +
                    "who visit your site in the next six months. See " +
                    "https://developers.google.com/adwords/api/docs/guides/rule-based-remarketing" +
                    " to learn more about rule based remarketing.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        public void Run(AdWordsUser user)
        {
            using (AdwordsUserListService userListService =
                (AdwordsUserListService) user.GetService(AdWordsService.v201809
                    .AdwordsUserListService))
            {
                // First rule item group - users who visited the checkout page and had
                // more than one item in their shopping cart.
                StringRuleItem checkoutStringRuleItem = new StringRuleItem
                {
                    key = new StringKey
                    {
                        name = "ecomm_pagetype"
                    },
                    op = StringRuleItemStringOperator.EQUALS,
                    value = "checkout"
                };

                RuleItem checkoutRuleItem = new RuleItem
                {
                    Item = checkoutStringRuleItem
                };

                NumberRuleItem cartSizeNumberRuleItem = new NumberRuleItem
                {
                    key = new NumberKey
                    {
                        name = "cartsize"
                    },
                    op = NumberRuleItemNumberOperator.GREATER_THAN,
                    value = 1
                };

                RuleItem cartSizeRuleItem = new RuleItem
                {
                    Item = cartSizeNumberRuleItem
                };

                // Combine the two rule items into a RuleItemGroup so AdWords will AND
                // their rules together.
                RuleItemGroup checkoutMultipleItemGroup = new RuleItemGroup
                {
                    items = new RuleItem[]
                    {
                        checkoutRuleItem,
                        cartSizeRuleItem
                    }
                };

                // Second rule item group - users who check out within the next 3 months.
                DateRuleItem startDateDateRuleItem = new DateRuleItem
                {
                    key = new DateKey
                    {
                        name = "checkoutdate"
                    },
                    op = DateRuleItemDateOperator.AFTER,
                    value = DateTime.Now.ToString(DATE_FORMAT_STRING)
                };
                RuleItem startDateRuleItem = new RuleItem
                {
                    Item = startDateDateRuleItem
                };

                DateRuleItem endDateDateRuleItem = new DateRuleItem
                {
                    key = new DateKey
                    {
                        name = "checkoutdate"
                    },
                    op = DateRuleItemDateOperator.BEFORE,
                    value = DateTime.Now.AddMonths(3).ToString(DATE_FORMAT_STRING)
                };
                RuleItem endDateRuleItem = new RuleItem
                {
                    Item = endDateDateRuleItem
                };

                // Combine the date rule items into a RuleItemGroup.
                RuleItemGroup checkedOutNextThreeMonthsItemGroup = new RuleItemGroup
                {
                    items = new RuleItem[]
                    {
                        startDateRuleItem,
                        endDateRuleItem
                    }
                };

                // Combine the rule item groups into a Rule so AdWords knows how to apply the rules.
                Rule rule = new Rule
                {
                    groups = new RuleItemGroup[]
                    {
                        checkoutMultipleItemGroup,
                        checkedOutNextThreeMonthsItemGroup
                    },

                    // ExpressionRuleUserLists can use either CNF Or DNF For matching. CNF means
                    // 'at least one item in each rule item group must match', and DNF means 'at
                    // least one entire rule item group must match'.
                    // DateSpecificRuleUserList only supports DNF. You can also omit the rule
                    // type altogether To Default To DNF.
                    ruleType = UserListRuleTypeEnumsEnum.DNF
                };

                // Third and fourth rule item groups.
                // Visitors of a page who visited another page. See
                // https://developers.google.com/adwords/api/docs/reference/latest/AdwordsUserListService.StringKey
                // for more details.
                StringKey urlStringKey = new StringKey()
                {
                    name = "url__"
                };

                StringRuleItem site1StringRuleItem = new StringRuleItem
                {
                    key = urlStringKey,
                    op = StringRuleItemStringOperator.EQUALS,
                    value = "example.com/example1"
                };
                RuleItem site1RuleItem = new RuleItem
                {
                    Item = site1StringRuleItem
                };

                StringRuleItem site2StringRuleItem = new StringRuleItem
                {
                    key = (urlStringKey),
                    op = (StringRuleItemStringOperator.EQUALS),
                    value = ("example.com/example2")
                };
                RuleItem site2RuleItem = new RuleItem
                {
                    Item = (site2StringRuleItem)
                };

                // Create two RuleItemGroups to show that a visitor browsed two sites.
                RuleItemGroup site1RuleItemGroup = new RuleItemGroup
                {
                    items = new RuleItem[]
                    {
                        site1RuleItem
                    }
                };
                RuleItemGroup site2RuleItemGroup = new RuleItemGroup
                {
                    items = new RuleItem[]
                    {
                        site2RuleItem
                    }
                };

                // Create two rules to show that a visitor browsed two sites.
                Rule userVisitedSite1Rule = new Rule
                {
                    groups = new RuleItemGroup[]
                    {
                        site1RuleItemGroup
                    }
                };

                Rule userVisitedSite2Rule = new Rule
                {
                    groups = new RuleItemGroup[]
                    {
                        site2RuleItemGroup
                    }
                };

                // Create the user list with no restrictions on site visit date.
                ExpressionRuleUserList expressionUserList = new ExpressionRuleUserList();
                string creationTimeString = DateTime.Now.ToString("yyyyMMdd_HHmmss");
                expressionUserList.name =
                    "Expression based user list created at " + creationTimeString;
                expressionUserList.description = "Users who checked out in three month window OR " +
                    "visited the checkout page with more than one item in their cart.";
                expressionUserList.rule = rule;

                // Optional: Set the prepopulationStatus to REQUESTED to include past users
                // in the user list.
                expressionUserList.prepopulationStatus =
                    RuleBasedUserListPrepopulationStatus.REQUESTED;

                // Create the user list restricted to users who visit your site within
                // the next six months.
                DateTime startDate = DateTime.Now;
                DateTime endDate = startDate.AddMonths(6);

                DateSpecificRuleUserList dateUserList = new DateSpecificRuleUserList
                {
                    name = "Date rule user list created at " + creationTimeString,
                    description = string.Format(
                        "Users who visited the site between {0} and " +
                        "{1} and checked out in three month window OR visited the checkout page " +
                        "with more than one item in their cart.",
                        startDate.ToString(DATE_FORMAT_STRING),
                        endDate.ToString(DATE_FORMAT_STRING)),
                    rule = rule,

                    // Set the start and end dates of the user list.
                    startDate = startDate.ToString(DATE_FORMAT_STRING),
                    endDate = endDate.ToString(DATE_FORMAT_STRING)
                };

                // Create the user list where "Visitors of a page who did visit another page".
                // To create a user list where "Visitors of a page who did not visit another
                // page", change the ruleOperator from AND to AND_NOT.
                CombinedRuleUserList combinedRuleUserList = new CombinedRuleUserList
                {
                    name = "Combined rule user list created at " + creationTimeString,
                    description = "Users who visited two sites.",
                    leftOperand = userVisitedSite1Rule,
                    rightOperand = userVisitedSite2Rule,
                    ruleOperator = CombinedRuleUserListRuleOperator.AND
                };

                // Create operations to add the user lists.
                List<UserListOperation> operations = new List<UserListOperation>();
                foreach (UserList userList in new UserList[]
                {
                    expressionUserList,
                    dateUserList,
                    combinedRuleUserList
                })
                {
                    UserListOperation operation = new UserListOperation
                    {
                        operand = userList,
                        @operator = Operator.ADD
                    };
                    operations.Add(operation);
                }

                try
                {
                    // Submit the operations.
                    UserListReturnValue result = userListService.mutate(operations.ToArray());

                    // Display the results.
                    foreach (UserList userListResult in result.value)
                    {
                        Console.WriteLine(
                            "User list added with ID {0}, name '{1}', status '{2}', " +
                            "list type '{3}', accountUserListStatus '{4}', description '{5}'.",
                            userListResult.id, userListResult.name, userListResult.status,
                            userListResult.listType, userListResult.accountUserListStatus,
                            userListResult.description);
                    }
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException("Failed to add rule based user lists.",
                        e);
                }
            }
        }
    }
}

Import conversion adjustments for existing conversions

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example demonstrates adjusting one conversion, but you can add more than one
    /// operation in a single mutate request.
    /// </summary>
    public class UploadOfflineConversionAdjustments : ExampleBase
    {
        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            UploadOfflineConversionAdjustments codeExample =
                new UploadOfflineConversionAdjustments();
            Console.WriteLine(codeExample.Description);
            try
            {
                string conversionName = "INSERT_CONVERSION_NAME_HERE";
                string gclid = "INSERT_GOOGLE_CLICK_ID_HERE";
                string conversionTime = "INSERT_CONVERSION_TIME_HERE";
                OfflineConversionAdjustmentType adjustmentType =
                    (OfflineConversionAdjustmentType) Enum.Parse(
                        typeof(OfflineConversionAdjustmentType), "INSERT_ADJUSTMENT_TYPE_HERE");
                string adjustmentTime = "INSERT_ADJUSTMENT_TIME_HERE";
                double adjustedValue = double.Parse("INSERT_ADJUSTED_VALUE_HERE");

                codeExample.Run(new AdWordsUser(), conversionName, gclid, conversionTime,
                    adjustmentType, adjustmentTime, adjustedValue);
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example demonstrates adjusting one conversion, but you can add " +
                    "more than one operation in a single mutate request.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        /// <param name="conversionName">Name of the conversion to make adjustments.</param>
        /// <param name="gclid">The google click ID for the adjustment.</param>
        /// <param name="conversionTime">The conversion time.</param>
        /// <param name="adjustmentType">The type of conversion adjustment.</param>
        /// <param name="adjustmentTime">The conversion adjustment time.</param>
        /// <param name="adjustedValue">The conversion adjustment value.</param>
        public void Run(AdWordsUser user, string conversionName, string gclid,
            string conversionTime, OfflineConversionAdjustmentType adjustmentType,
            string adjustmentTime, double adjustedValue)
        {
            using (OfflineConversionAdjustmentFeedService service =
                (OfflineConversionAdjustmentFeedService) user.GetService(AdWordsService.v201809
                    .OfflineConversionAdjustmentFeedService))
            {
                // Associate conversion adjustments with the existing named conversion
                // tracker. The GCLID should have been uploaded before with a
                // conversion.
                GclidOfflineConversionAdjustmentFeed feed =
                    new GclidOfflineConversionAdjustmentFeed()
                    {
                        conversionName = conversionName,
                        googleClickId = gclid,
                        conversionTime = conversionTime,
                        adjustmentType = adjustmentType,
                        adjustmentTime = adjustmentTime,
                        adjustedValue = adjustedValue
                    };

                // Create the operation.
                var operation = new OfflineConversionAdjustmentFeedOperation()
                {
                    @operator = Operator.ADD,
                    operand = feed
                };

                try
                {
                    // Issue a request to the servers for adjustments of the conversion.
                    OfflineConversionAdjustmentFeedReturnValue retval = service.mutate(
                        new OfflineConversionAdjustmentFeedOperation[]
                        {
                            operation
                        });
                    GclidOfflineConversionAdjustmentFeed updatedFeed =
                        (GclidOfflineConversionAdjustmentFeed) retval.value[0];
                    Console.WriteLine(
                        "Uploaded conversion adjustment value of '{0}' for Google " +
                        "Click ID '{1}'.", updatedFeed.conversionName, updatedFeed.googleClickId);
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException("Failed to update conversion adjustment.",
                        e);
                }
            }
        }
    }
}

Import offline call conversions

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example imports offline call conversion values for calls related to the
    /// ads in your account.
    /// </summary>
    public class UploadOfflineCallConversions : ExampleBase
    {
        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            string conversionName = "INSERT_CONVERSION_NAME_HERE";

            // For times use the format yyyyMMdd HHmmss tz. For more details on formats, see:
            // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
            // For time zones, see:
            // https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids

            //  The conversion time should be after the call start time.
            string conversionTime = "INSERT_CONVERSION_TIME_HERE";
            string callStartTime = "INSERT_CALL_START_TIME_HERE";

            string callerId = "INSERT_CALLER_ID_HERE";
            double conversionValue = double.Parse("INSERT_CONVERSION_VALUE_HERE");

            UploadOfflineCallConversions codeExample = new UploadOfflineCallConversions();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser(), conversionName, callStartTime, callerId,
                    conversionTime, conversionValue);
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return
                    "This code example imports offline call conversion values for calls related " +
                    "to the ads in your account.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        /// <param name="conversionName">The name of the call conversion to be updated.</param>
        /// <param name="callStartTime">The call start time.</param>
        /// <param name="conversionValue">The conversion value to be uploaded.</param>
        /// <param name="callerId">The caller ID to be uploaded.</param>
        /// <param name="conversionTime">The conversion time, in yyyymmdd hhmmss
        /// format.</param>
        public void Run(AdWordsUser user, string conversionName, string callStartTime,
            string callerId, string conversionTime, double conversionValue)
        {
            using (OfflineCallConversionFeedService offlineCallConversionFeedService =
                (OfflineCallConversionFeedService) user.GetService(AdWordsService.v201809
                    .OfflineCallConversionFeedService))
            {
                // Associate offline call conversions with the existing named conversion tracker.
                // If this tracker was newly created, it may be a few hours before it can accept
                // conversions.
                OfflineCallConversionFeed feed = new OfflineCallConversionFeed
                {
                    callerId = callerId,
                    callStartTime = callStartTime,
                    conversionName = conversionName,
                    conversionTime = conversionTime,
                    conversionValue = conversionValue
                };

                OfflineCallConversionFeedOperation offlineCallConversionOperation =
                    new OfflineCallConversionFeedOperation
                    {
                        @operator = Operator.ADD,
                        operand = feed
                    };

                try
                {
                    // This example uploads only one call conversion, but you can upload
                    // multiple call conversions by passing additional operations.
                    OfflineCallConversionFeedReturnValue offlineCallConversionReturnValue =
                        offlineCallConversionFeedService.mutate(
                            new OfflineCallConversionFeedOperation[]
                            {
                                offlineCallConversionOperation
                            });

                    // Display results.
                    foreach (OfflineCallConversionFeed feedResult in
                        offlineCallConversionReturnValue.value)
                    {
                        Console.WriteLine(
                            "Uploaded offline call conversion value of {0} for caller ID '{1}'.",
                            feedResult.conversionValue, feedResult.callerId);
                    }
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException(
                        "Failed to upload offline call conversions.", e);
                }
            }
        }

    }
}

Import offline click conversions

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example imports offline conversion values for specific clicks to
    /// your account. To get Google Click ID for a click, run
    /// CLICK_PERFORMANCE_REPORT. To set up a conversion tracker, run the
    /// AddConversionTrackers.cs example.
    /// </summary>
    public class UploadOfflineConversions : ExampleBase
    {
        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            string conversionName = "INSERT_CONVERSION_NAME_HERE";
            // GCLID needs to be newer than 30 days.
            string gClId = "INSERT_GOOGLE_CLICK_ID_HERE";
            //  The conversion time should be higher than the click time.
            string conversionTime = "INSERT_CONVERSION_TIME_HERE";
            double conversionValue = double.Parse("INSERT_CONVERSION_VALUE_HERE");

            UploadOfflineConversions codeExample = new UploadOfflineConversions();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser(), conversionName, gClId, conversionTime,
                    conversionValue);
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example imports offline conversion values for specific clicks " +
                    "to your account. To get Google Click ID for a click, run " +
                    "CLICK_PERFORMANCE_REPORT.  To set up a conversion tracker, run the " +
                    "AddConversionTrackers.cs example.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        /// <param name="conversionName">The name of the upload conversion to be
        /// created.</param>
        /// <param name="gClid">The Google Click ID of the click for which offline
        /// conversions are uploaded.</param>
        /// <param name="conversionValue">The conversion value to be uploaded.
        /// </param>
        /// <param name="conversionTime">The conversion time, in yyyymmdd hhmmss
        /// format.</param>
        public void Run(AdWordsUser user, string conversionName, string gClid,
            string conversionTime, double conversionValue)
        {
            using (OfflineConversionFeedService offlineConversionFeedService =
                (OfflineConversionFeedService) user.GetService(AdWordsService.v201809
                    .OfflineConversionFeedService))
            {
                try
                {
                    // Associate offline conversions with the existing named conversion tracker. If
                    // this tracker was newly created, it may be a few hours before it can accept
                    // conversions.
                    OfflineConversionFeed feed = new OfflineConversionFeed
                    {
                        conversionName = conversionName,
                        conversionTime = conversionTime,
                        conversionValue = conversionValue,
                        googleClickId = gClid
                    };

                    // Optional: To upload fractional conversion credits, set the external
                    // attribution model and credit. To use this feature, your conversion tracker
                    // should be marked as externally attributed. See
                    // https://developers.google.com/adwords/api/docs/guides/conversion-tracking#importing_externally_attributed_conversions
                    // to learn more about importing externally attributed conversions.

                    // feed.externalAttributionModel = "Linear";
                    // feed.externalAttributionCredit = 0.3;

                    OfflineConversionFeedOperation offlineConversionOperation =
                        new OfflineConversionFeedOperation
                        {
                            @operator = Operator.ADD,
                            operand = feed
                        };

                    OfflineConversionFeedReturnValue offlineConversionRetval =
                        offlineConversionFeedService.mutate(new OfflineConversionFeedOperation[]
                        {
                            offlineConversionOperation
                        });

                    OfflineConversionFeed newFeed = offlineConversionRetval.value[0];

                    Console.WriteLine(
                        "Uploaded offline conversion value of {0} for Google Click ID = " +
                        "'{1}' to '{2}'.", newFeed.conversionValue, newFeed.googleClickId,
                        newFeed.conversionName);
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException("Failed upload offline conversions.", e);
                }
            }
        }

    }
}

Upload offline data for store sales transactions

// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Ads.AdWords.Lib;
using Google.Api.Ads.AdWords.v201809;

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace Google.Api.Ads.AdWords.Examples.CSharp.v201809
{
    /// <summary>
    /// This code example shows how to upload offline data for store sales transactions.
    /// </summary>
    public class UploadOfflineData : ExampleBase
    {
        private SHA256 digest = SHA256.Create();

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            // The external upload ID can be any number that you use to keep track of your uploads.
            long externalUploadId = long.Parse("INSERT_EXTERNAL_UPLOAD_ID");

            // Insert the conversion type name that you'd like to attribute this upload to.
            string conversionName = "INSERT_CONVERSION_NAME";

            // Insert email addresses below for creating user identifiers.
            string[] emailAddresses =
            {
                "EMAIL_ADDRESS_1",
                "EMAIL_ADDRESS_2"
            };

            // Insert advertiser upload time. // For times, use the format yyyyMMdd HHmmss tz. For
            // more details on formats, see:
            // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
            // For time zones, see:
            // https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
            string advertiserUploadTime = "INSERT_ADVERTISER_UPLOAD_TIME";

            // Insert bridge map version ID.
            string bridgeMapVersionId = "INSERT_BRIDGEMAP_VERSION_ID";

            // Insert partner ID.
            int partnerId = int.Parse("INSERT_PARTNER_ID");

            // Specify the upload type (STORE_SALES_UPLOAD_FIRST_PARTY or
            // STORE_SALES_UPLOAD_THIRD_PARTY)
            OfflineDataUploadType uploadType =
                (OfflineDataUploadType) Enum.Parse(typeof(OfflineDataUploadType),
                    "INSERT_UPLOAD_TYPE");

            UploadOfflineData codeExample = new UploadOfflineData();
            Console.WriteLine(codeExample.Description);
            try
            {
                codeExample.Run(new AdWordsUser(), conversionName, externalUploadId, emailAddresses,
                    advertiserUploadTime, bridgeMapVersionId, uploadType, partnerId);
            }
            catch (Exception e)
            {
                Console.WriteLine("An exception occurred while running this code example. {0}",
                    ExampleUtilities.FormatException(e));
            }
        }

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description
        {
            get
            {
                return "This code example shows how to upload offline data for store sales " +
                    "transactions.";
            }
        }

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="user">The AdWords user.</param>
        /// <param name="conversionName">The conversion type name that you'd like to attribute this
        /// upload to.</param>
        /// <param name="externalUploadId">The external upload ID can be any number that you use to
        /// keep track of your uploads.</param>
        /// <param name="emailAddresses">The email addresses for creating user identifiers.</param>
        /// <param name="advertiserUploadTime">The advertiser upload time. For times, use the format
        /// yyyyMMdd HHmmss tz. For more details on formats, see:
        /// https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
        /// For time zones, see:
        /// https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
        /// </param>
        /// <param name="bridgeMapVersionId">The version ID of the bridge map.</param>
        /// <param name="uploadType">The type of data upload.</param>
        /// <param name="partnerId">The partner ID</param>
        public void Run(AdWordsUser user, string conversionName, long externalUploadId,
            string[] emailAddresses, string advertiserUploadTime, string bridgeMapVersionId,
            OfflineDataUploadType uploadType, int partnerId)
        {
            using (OfflineDataUploadService offlineDataUploadService =
                (OfflineDataUploadService) user.GetService(AdWordsService.v201809
                    .OfflineDataUploadService))
            {
                offlineDataUploadService.RequestHeader.partialFailure = true;

                // Create the first offline data row for upload.
                // This transaction occurred 7 days ago with amount of 200 USD.
                DateTime transactionTime1 = DateTime.Now;
                transactionTime1.AddDays(-7);
                long transactionAmount1 = 200000000;
                string transactionCurrencyCode1 = "USD";
                UserIdentifier[] userIdentifierList1 = new UserIdentifier[]
                {
                    CreateUserIdentifier(OfflineDataUploadUserIdentifierType.HASHED_EMAIL,
                        emailAddresses[0]),
                    CreateUserIdentifier(OfflineDataUploadUserIdentifierType.STATE, "New York")
                };
                OfflineData offlineData1 = CreateOfflineDataRow(transactionTime1,
                    transactionAmount1, transactionCurrencyCode1, conversionName,
                    userIdentifierList1);

                // Create the second offline data row for upload.
                // This transaction occurred 14 days ago with amount of 450 EUR.
                DateTime transactionTime2 = DateTime.Now;
                transactionTime2.AddDays(-14);
                long transactionAmount2 = 450000000;
                string transactionCurrencyCode2 = "EUR";
                UserIdentifier[] userIdentifierList2 = new UserIdentifier[]
                {
                    CreateUserIdentifier(OfflineDataUploadUserIdentifierType.HASHED_EMAIL,
                        emailAddresses[1]),
                    CreateUserIdentifier(OfflineDataUploadUserIdentifierType.STATE, "California")
                };
                OfflineData offlineData2 = CreateOfflineDataRow(transactionTime2,
                    transactionAmount2, transactionCurrencyCode2, conversionName,
                    userIdentifierList2);

                // Create offline data upload object.
                OfflineDataUpload offlineDataUpload = new OfflineDataUpload
                {
                    externalUploadId = externalUploadId,
                    offlineDataList = new OfflineData[]
                    {
                        offlineData1,
                        offlineData2
                    },

                    // Set the type and metadata of this upload.
                    uploadType = uploadType
                };
                StoreSalesUploadCommonMetadata storeSalesMetaData = null;

                switch (uploadType)
                {
                    case OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY:
                        storeSalesMetaData = new FirstPartyUploadMetadata()
                        {
                            loyaltyRate = 1,
                            transactionUploadRate = 1
                        };
                        break;

                    case OfflineDataUploadType.STORE_SALES_UPLOAD_THIRD_PARTY:
                        storeSalesMetaData = new ThirdPartyUploadMetadata()
                        {
                            loyaltyRate = 1.0,
                            transactionUploadRate = 1.0,
                            advertiserUploadTime = advertiserUploadTime,
                            validTransactionRate = 1.0,
                            partnerMatchRate = 1.0,
                            partnerUploadRate = 1.0,
                            bridgeMapVersionId = bridgeMapVersionId,
                            partnerId = partnerId
                        };
                        break;
                }

                UploadMetadata uploadMetadata = new UploadMetadata
                {
                    Item = storeSalesMetaData
                };
                offlineDataUpload.uploadMetadata = uploadMetadata;

                // Create an offline data upload operation.
                OfflineDataUploadOperation offlineDataUploadOperation =
                    new OfflineDataUploadOperation
                    {
                        @operator = Operator.ADD,
                        operand = offlineDataUpload
                    };

                // Keep the operations in an array, so it may be reused later for error processing.
                List<OfflineDataUploadOperation>
                    operations = new List<OfflineDataUploadOperation>();
                operations.Add(offlineDataUploadOperation);

                try
                {
                    // Upload offline data to the server.
                    OfflineDataUploadReturnValue result =
                        offlineDataUploadService.mutate(operations.ToArray());
                    offlineDataUpload = result.value[0];

                    // Print the upload ID and status.
                    Console.WriteLine(
                        "Uploaded offline data with external upload ID {0}, " +
                        "and upload status {1}.", offlineDataUpload.externalUploadId,
                        offlineDataUpload.uploadStatus);

                    // Print any partial failure errors from the response.
                    if (result.partialFailureErrors != null)
                    {
                        foreach (ApiError apiError in result.partialFailureErrors)
                        {
                            // Get the index of the failed operation from the error's field path
                            // elements.
                            int operationIndex = apiError.GetOperationIndex();
                            if (operationIndex != -1)
                            {
                                OfflineDataUpload failedOfflineDataUpload =
                                    operations[operationIndex].operand;
                                // Get the index of the entry in the offline data list from the
                                // error's field path elements.
                                int offlineDataListIndex =
                                    apiError.GetFieldPathIndex("offlineDataList");
                                Console.WriteLine(
                                    "Offline data list entry {0} in operation {1} with external " +
                                    "upload ID {2} and type '{3}' has triggered a failure for " +
                                    "the following reason: '{4}'.",
                                    offlineDataListIndex, operationIndex,
                                    failedOfflineDataUpload.externalUploadId,
                                    failedOfflineDataUpload.uploadType, apiError.errorString);
                            }
                            else
                            {
                                Console.WriteLine(
                                    "A failure has occurred for the following reason: {0}",
                                    apiError.errorString);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    throw new System.ApplicationException("Failed upload offline data conversions.",
                        e);
                }
            }
        }

        /// <summary>
        /// Creates the offline data row from the specified transaction time, transaction micro
        /// amount, transaction currency, conversion name and user identifier list.
        /// </summary>
        /// <param name="transactionTime">The transaction time.</param>
        /// <param name="transactionMicroAmount">The transaction micro amount.</param>
        /// <param name="transactionCurrency">The transaction currency.</param>
        /// <param name="conversionName">Name of the conversion.</param>
        /// <param name="userIdentifierList">The user identifier list.</param>
        /// <returns>The offline data row.</returns>
        private OfflineData CreateOfflineDataRow(DateTime transactionTime,
            long transactionMicroAmount, string transactionCurrency, string conversionName,
            UserIdentifier[] userIdentifierList)
        {
            StoreSalesTransaction storeSalesTransaction = new StoreSalesTransaction
            {
                // For times use the format yyyyMMdd HHmmss [tz].
                // For details, see
                // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
                transactionTime = transactionTime.ToString("yyyyMMdd HHmmss"),
                conversionName = conversionName,
                userIdentifiers = userIdentifierList
            };

            Money money = new Money
            {
                microAmount = transactionMicroAmount
            };
            RemarketingMoneyWithCurrency moneyWithCurrency = new RemarketingMoneyWithCurrency
            {
                money = money,
                currencyCode = transactionCurrency
            };
            storeSalesTransaction.transactionAmount = moneyWithCurrency;

            OfflineData offlineData = new OfflineData
            {
                Item = storeSalesTransaction
            };

            return offlineData;
        }

        /// <summary>
        /// Hash a string value using SHA-256 hashing algorithm.
        /// </summary>
        /// <param name="digest">Provides the algorithm for SHA-256.</param>
        /// <param name="value">The string value (e.g. an email address) to hash.</param>
        /// <returns>The hashed value.</returns>
        private static string ToSha256String(SHA256 digest, string value)
        {
            byte[] digestBytes = digest.ComputeHash(Encoding.UTF8.GetBytes(value));
            // Convert the byte array into an unhyphenated hexadecimal string.
            return BitConverter.ToString(digestBytes).Replace("-", string.Empty);
        }

        /// <summary>
        /// Creates the user identifier.
        /// </summary>
        /// <param name="type">The user identifier type.</param>
        /// <param name="value">The user identifier value.</param>
        /// <returns></returns>
        private UserIdentifier CreateUserIdentifier(OfflineDataUploadUserIdentifierType type,
            string value)
        {
            // If the user identifier type is a hashed type, also call hash function
            // on the value.
            if (type.ToString().StartsWith("HASHED_"))
            {
                value = ToSha256String(digest, ToNormalizedValue(value));
            }

            UserIdentifier userIdentifier = new UserIdentifier
            {
                userIdentifierType = type,
                value = value
            };

            return userIdentifier;
        }

        /// <summary>
        /// Removes leading and trailing whitespace and converts all characters to
        /// lower case.
        /// </summary>
        /// <param name="value">The value to normalize.</param>
        /// <returns>The normalized value.</returns>
        private static string ToNormalizedValue(string value)
        {
            return value.Trim().ToLower();
        }
    }
}