This guide describes how to use the Loyalty Customer Match Service in the Merchant API. This service enables merchants to manage customer loyalty data, such as user identifiers and tier information, for organic personalization on Google Search, without requiring an active Google Ads account.
Overview
Use the Loyalty Customer Match Service to upload loyalty data, which is then
used to provide organic loyalty personalization features on Google Search, such
as displaying member-specific pricing. You use the ManageLoyaltyCustomerMatch
custom method to associate your customers with loyalty program tiers, enabling
you to insert, update, or remove their loyalty status based on user
identifiers.
Key concepts
- Unified interface: A unique endpoint to add, update, or remove customer loyalty tier details.
- Privacy-first design: To protect user privacy and prevent unauthorized account probing, the API does not support GET or LIST operations, ensuring that data is managed without retrieval or auditing.
- Flexible identification: Match users using at least one valid identifier, such as an email address, physical address, or phone number.
- Consent-based processing: The service stores and uses customer data only when the end-user has granted the necessary consent to Google. To protect and prevent account existence probing or consent status, the service returns a silent success if a match is not made or consent is not granted.
Prerequisites
Follow these requirements to use the Loyalty Customer Match Service:
- Account setup: Ensure you have an active Merchant Center account. You don't need to create a Google Ads account to use the Loyalty Customer Match Service.
- Loyalty program configuration: Enable the loyalty program in your Merchant Center account, and make sure you have defined loyalty tiers.
- Tier order awareness: Be aware of the order in which your loyalty tiers are defined in the Merchant Center UI. The API uses this exact sequence for its enum mapping.
Method: ManageLoyaltyCustomerMatch
The ManageLoyaltyCustomerMatch method serves as the central interface for
managing customer loyalty associations. Based on the input provided, the service
automatically determines whether to insert, update, or remove the loyalty tier
status of a customer. The operation is
idempotent: repeated identical
requests have the same effect as a single request.
The following request demonstrates how to manage customer loyalty associations through the API:
POST https://merchantapi.googleapis.com/{api_version}/accounts/{account_id}/loyaltyCustomers:manage
This request defines the following required path parameters:
api_version: The API version such as v1.account_id: The Merchant Center account ID.
Include a loyaltyCustomer object in the request body.
{
"userIdentifier": {
"emailAddress": "string",
"address": {
"addressLines": ["string"],
"locality": "string",
"administrativeArea": "string",
"postalCode": "string",
"regionCode": "string"
},
"phoneNumber": "string"
},
"loyaltyTier": "LoyaltyTier",
"pointBalance": "integer"
}
loyaltyCustomer fields
- userIdentifier: The set of identifiers used to match the customer. At least one field within userIdentifier must be provided and valid.
- loyaltyTier: The loyalty tier to associate with the
customer. Maps to the order of the tier in the Merchant Center setup.
For details, see
Understanding
loyaltyTiermapping. Use NON_MEMBER to remove an existing association. - pointBalance: The customer's current points balance.
userIdentifier fields
At least one of the following fields must be provided:
- emailAddress: The customer's email address.
- address: The customer's physical address. PostalCode is required.
- phoneNumber: The customer's phone number. E.164 format is recommended.
Understand loyaltyTier mapping
The API does not use the custom names. The loyaltyTier enum values (TIER1
to TIER7) are semantic labels. They don't use the custom names (for example,
"Gold Rewards") or custom labels (for example, "gold_tier") you assigned in your
Merchant Center UI. Instead, they map strictly to the order in which
you defined your tiers in the loyalty program settings in the
Merchant Center:
TIER1: Corresponds to the first tier listed in your Merchant Center loyalty program configuration.TIER2: Corresponds to the second tier listed in your Merchant Center loyalty program configuration.TIER3throughTIER7: Correspond to the third through seventh tiers listed in your Merchant Center loyalty program configuration.
Example:
If your Merchant Center loyalty program has tiers defined in this order:
- Tier Name: "Silver Status", Tier Label: "silver"
- Tier Name: "Gold Member", Tier Label: "gold"
- Tier Name: "Platinum Elite", Tier Label: "platinum"
Then, in accounts.loyaltyCustomers.manage
API calls:
- To assign a customer to "Silver Status", you must use
loyaltyTier: TIER1. - To assign a customer to "Gold Member", you must use
loyaltyTier: TIER2. - To assign a customer to "Platinum Elite", you must use
loyaltyTier: TIER3.
LoyaltyTier enum values
TIER1TIER2TIER3TIER4TIER5TIER6TIER7NON_MEMBER(Used to signal removal of the customer's loyalty association)
Understand the ManageLoyaltyCustomerMatch response body
The ManageLoyaltyCustomerMatch method returns a
ManageLoyaltyCustomerMatchResponse object:
{
"loyaltyCustomer": {
// loyaltyCustomer object from the request
}
}
Important considerations on possible responses:
Successful upsert (data stored): To successfully store or update a customer's loyalty tier association, meet the following conditions:
- you match a Google user with the provided
userIdentifier - you set the
loyaltyTierin the request to a valid value other thanNON_MEMBER - the matched user has consented to loyalty data usage
- you match a Google user with the provided
The response contains the loyaltyCustomer object from your request,
indicating the data is successfully processed and stored:
{
"loyaltyCustomer": {
"userIdentifier": {
"emailAddress": "customer@example.com"
},
"loyaltyTier": "TIER2",
"pointBalance": 1500
}
}
- Successful deletion: To successfully remove any existing loyalty
association for the customer with this merchant, the following conditions
must be fulfilled:
- you match a Google user with the provided
userIdentifier - you set the
loyaltyTierin the request toNON_MEMBER
- you match a Google user with the provided
The response is an empty JSON object:
{}
- No match / no consent (silent success): If the provided
userIdentifierdoes not match a Google Account, or if the matched user has not consented to loyalty data usage, the API returns an HTTP 200 OK status with an empty JSON object:{}. This happens for both upsert and removal attempts.
Examples
TIER1 corresponds to the first defined tier of the merchant, the one called "Basic", and TIER2 to their second - "Premium"
To add a customer to TIER2 or update their status using an email address, send the following request:
POST
"https://merchantapi.googleapis.com/v1/accounts/{account_id}/loyaltyCustomers:manage"
-d '{
"userIdentifier": {
"emailAddress": "customer@example.com"
},
"loyaltyTier": "TIER2",
"pointBalance": 1500
}'
When a user is successfully matched and has consented, the API returns the following response:
{
"loyaltyCustomer": {
"userIdentifier": {
"emailAddress": "customer@example.com"
},
"loyaltyTier": "TIER2",
"pointBalance": 1500
}
}
When there is no match or the user hasn't consented, the API returns the following response:
{}
To remove a customer's loyalty association using a phone number, send the following request:
POST
"https://merchantapi.googleapis.com/v1/accounts/{account_id}/loyaltyCustomers:manage" \
-d '{
"userIdentifier": {
"phoneNumber": "+18005550132"
},
"loyaltyTier": "NON_MEMBER"
}'
Regardless of whether a record existed, the API returns the following success response:
{}
To add or update a customer using multiple identifiers, send the following request:
POST
"https://merchantapi.googleapis.com/v1/accounts/{account_id}/loyaltyCustomers:manage" \
-d '{
"userIdentifier": {
"emailAddress": "user@example.com",
"address": {
"postalCode": "94043",
"regionCode": "US"
}
},
"loyaltyTier": "TIER1"
}'
Response is similar to the first example, depending on match and consent.
Error handling
The API uses standard HTTP codes. Common error strings include:
| HTTP code | Error String | Description |
| 400 | INVALID_ARGUMENT | Missing user_identifier or loyalty_tier, or identifier is empty. |
| 401 | UNAUTHENTICATED | Invalid or missing credentials. |
| 403 | PERMISSION_DENIED | The authenticated user does not have access to the specified Merchant Center account. |
| 404 | NOT_FOUND | The specified loyalty tier label does not exist in your configuration. |
| 412 | FAILED_PRECONDITION | You have not configured a loyalty program in your account. |
| 429 | RESOURCE_EXHAUSTED | Quota limit reached. |
Error examples
Example for 404 NOT_FOUND:
Any valid request to an account ID that does not have a loyalty program configured.
The API returns the following error response:
{
"error": {
"code": 404,
"message": "The loyalty program is not found for account: {account_id}.",
"status": "NOT_FOUND",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "notFound",
"domain": "merchantapi.googleapis.com",
"metadata": {
"ACCOUNT_ID": "{account_id}",
"REASON": "NOT_FOUND_LOYALTY_PROGRAM"
}
}
]
}
}
Reason: The merchant account in the path does not have an active loyalty program.
Examples of 400 INVALID_ARGUMENT:
An error occurs if the request contains an invalid value for the loyaltyTier
field:
{
"loyaltyCustomer": {
"userIdentifier": {
"emailAddress": "customer@example.com"
},
"loyaltyTier": "TIER11",
"pointBalance": 100
}
}
The API returns the following error response:
{
"error": {
"code": 400,
"message": "Invalid value at 'loyalty_customer.loyalty_tier' (type.googleapis.com/google.shopping.merchant.loyaltycustomers.v1.LoyaltyCustomer.LoyaltyTier), \"TIER11\"",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "loyalty_customer.loyalty_tier",
"description": "Invalid value at 'loyalty_customer.loyalty_tier' (type.googleapis.com/google.shopping.merchant.loyaltycustomers.v1.LoyaltyCustomer.LoyaltyTier), \"TIER11\""
}
]
}
]
}
}
Reason: TIER11 is not a valid enum value for loyaltyTier. The same
error can happen when you try to specify TIER2 when there's only one tier
available.
An error occurs if the required loyaltyTier field is missing from the request
body:
{
"loyaltyCustomer": {
"userIdentifier": {
"emailAddress": "customer@example.com"
},
"pointBalance": 100
}
}
The API returns the following error response:
{
"error": {
"code": 400,
"message": "[loyalty_customer.loyalty_tier] Required field not provided: loyalty_customer.loyalty_tier",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "required",
"domain": "merchantapi.googleapis.com",
"metadata": {
"FIELD_LOCATION": "loyalty_customer.loyalty_tier",
"REASON": "MISSING_REQUIRED_FIELD"
}
}
]
}
}
Reason: The loyaltyTier field is required.
An error occurs if an address identifier is incomplete, such as when the postalCode field is missing:
{
"loyaltyCustomer": {
"userIdentifier": {
"address": {
"locality": "Sunnyvale",
"administrativeArea": "CA",
"regionCode": "US"
}
},
"loyaltyTier": "TIER1",
"pointBalance": 100
}
}
The API returns the following error response:
{
"error": {
"code": 400,
"message": "[loyalty_customer.user_identifier] The format of loyalty_customer.user_identifier does not match the expected format ... Value: at least one valid user identifier should be provided.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "invalid",
"domain": "merchantapi.googleapis.com",
"metadata": {
"FIELD_NAME": "loyalty_customer.user_identifier",
"REASON": "INVALID_VALUE"
}
}
]
}
}
Reason: An address is provided, but it's missing the required postalCode
field, so it's not considered a valid identifier.
An error occurs if you request a tier index that is out of bounds for the configured program:
Scenario: The merchant has only one tier configured in the Merchant Center.
{
"loyaltyCustomer": {
"userIdentifier": {
"emailAddress": "customer@example.com"
},
"loyaltyTier": "TIER2",
"pointBalance": 100
}
}
The API returns the following error response:
{
"error": {
"code": 400,
"message": "[loyalty_customer.loyalty_tier] The format of loyalty_customer.loyalty_tier does not match the expected format `valid LoyaltyTier`. Value: TIER2.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "invalid",
"domain": "merchantapi.googleapis.com",
"metadata": {
"FIELD_NAME": "loyalty_customer.loyalty_tier",
"PATTERN": "valid LoyaltyTier",
"FIELD_VALUE": "TIER2",
"REASON": "INVALID_VALUE"
}
}
]
}
}
Reason: TIER2 is requested, but the loyalty program linked to the account doesn't have a second tier defined.
An error occurs if the request contains a malformed emailAddress:
{
"loyaltyCustomer": {
"userIdentifier": {
"emailAddress": "customer@google"
},
"loyaltyTier": "TIER1",
"pointBalance": 100
}
}
The API returns the following error response:
{
"error": {
"code": 400,
"message": "[loyalty_customer.user_identifier] The format of loyalty_customer.user_identifier does not match the expected format `email_address: \t \"customer@google\"\n`. Value: at least one valid user identifier should be provided.",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "invalid",
"domain": "merchantapi.googleapis.com",
"metadata": {
"FIELD_NAME": "loyalty_customer.user_identifier",
"REASON": "INVALID_VALUE"
}
}
]
}
}
Reason: The email address format is invalid.
An error occurs if the userIdentifier object is empty:
{
"loyaltyCustomer": {
"userIdentifier": {},
"loyaltyTier": "TIER1",
"pointBalance": 100
}
}
The API returns the following error response:
{
"error": {
"code": 400,
"message": "[loyalty_customer.user_identifier] Required field not provided: loyalty_customer.user_identifier",
"status": "INVALID_ARGUMENT",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "required",
"domain": "merchantapi.googleapis.com",
"metadata": {
"FIELD_LOCATION": "loyalty_customer.user_identifier",
"REASON": "MISSING_REQUIRED_FIELD"
}
}
]
}
}
Reason: The userIdentifier object is present but contains no actual
identifier fields.
Note on identifier validation:
- The API performs basic format checks on identifiers (for example,
email structure, presence of
postalCodein addresses). - However, some identifiers that pass initial checks might not match any
Google user account or may not be in a format recognized by the backend
matching system. In such cases, you will receive the silent success empty
response
{}with HTTP status200 OK.
Best practices
Follow these best practices to optimize your integration.
For large-scale integration: Because the API operates on a per-request basis, client-side parallelism is required to achieve the necessary throughput for large datasets. You should design your integration to manage multiple concurrent requests. For guidance on how to structure your implementation to handle higher volumes through parallelization, refer to our guide on how to send multiple requests.
Quota management: The default quota is 1,000,000 requests/day and 10,000 requests/minute. To see how you can monitor and check your quotas, see Quotas and limits.
Prioritize email address: Whenever possible, include the customer's
emailAddressin theuserIdentifier. Email addresses are generally the most accurate and reliable identifier for matching users to their Google Accounts.Handle empty responses: Design your application to correctly interpret empty
{}responses as a success, understanding that it means the data was not stored due to privacy reasons (no match or no consent). Don't retry the request.Verify tier order: Always confirm the order of your loyalty tiers in the Merchant Center UI to ensure you are using the correct
TIER1throughTIER7enum values in your API calls. This mapping is based on the defined order in the UI, not their names.Monitor errors: Log and monitor API responses, paying attention to any
4xxerrors to catch integration issues, especially404errors which might indicate a mismatch in tier understanding.