Partners participating in the offers integration must complete the account setup for a Merchant or Entity (pilot) based integration step before they begin. The implementation, testing, and launch of the offers integration will be detailed in this guide. Read through this overview and offers policies before going through the integration steps.
Offers
The offers integration lets you relay structured information about merchant promotions and discounts applied to specific services at specific times. Offers are made up of the actual offer (percent-off, dollar-off ...), validity windows (specific times, days of the week ...), and applicable uses (the offer can only be used on certain services) as well of complex combinations of restrictions.
Examples of offers:
- Half-off appetizers on Wednesdays and Thursdays in December from 12pm to 5pm
- Buy one get one dessert free for Mother's Day dinner from 6pm to 10pm
- $5 off a brunch entree every Sunday from 10am to 2pm
- 10% off as walk-in offer combinable with 5% off for premium subscribers and 5% off if the user pays through your app.
In order for an offer to be included in the integration, it needs to fit within the technical data model as well as meet our eligibility requirements. Make sure to review our offers policies to ensure your integration is in compliance and for instructions on what to do with offers that don't fit the technical requirements.
Offers implementation
The offers integration consists of two feeds that will be uploaded daily or at frequency that ensures a high accuracy (meaning reduces staleness):
OfferFeed
Field Name | Type | Requirement | Description |
---|---|---|---|
data | array of object(Offer) |
Offer
Field Name | Type | Requirement | Description |
---|---|---|---|
offer_id | string | Required | Unique ID of the offer. Required. |
entity_ids | array of string | List of merchants who are participating in this offer. | |
add_on_offer_applicable_to_all_entities | boolean | If true, this offer is applicable to all entities under the aggregator. Only applicable for add on offers. | |
offer_source | enum(OfferSource) | Required | An offer can be provided by the aggregator, an individual merchant, or even a third party as an add on. Required. |
action_type | enum(ActionType) | Required | The service that is providing the offer. An offer_id can belong to only one action_type. If an offer can be shared across multiple service types then duplicate offers with unique Ids are expected to be created for each service type. Required. |
offer_modes | array of enum(OfferMode) | Required | The methods the offer can be availed - walk in, reservation, online, etc. Required. |
offer_category | enum(OfferCategory) | Required | The category of the offer. Required. |
source_assigned_priority | number | Non-negative integer ([1-100], where 1 represents the highest priority) indicating the priority level of the offer assigned by the source. When multiple offers are available for the same merchant, this will be a signal for ranking offers. 0 would represent that the priority is not set. | |
offer_details | object(OfferDetails) | Required | Details of the offer such as the discount, booking cost, etc. Required. |
offer_restrictions | object(OfferRestrictions) | Required | Describes how the offer is restricted i.e. whether a subscription/payment instrument is required, whether this offer can be combined with other offers (and what types), etc. Required. |
coupon | object(Coupon) | Details of a coupon. Required for offer_category: OFFER_CATEGORY_ADD_ON_COUPON_OFFER. | |
payment_instrument | object(PaymentInstrument) | Details of a payment instrument. Required for offer_category: OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER. | |
subscription | object(Subscription) | Details of a subscription. Required for offer_category: OFFER_CATEGORY_ADD_ON_SUBSCRIPTION_OFFER. | |
terms | object(Terms) | Required | Terms and conditions of the offer. Required. |
validity_periods | array of object(ValidityPeriod) | Required | The validity period of the offer. Describes what time period the offer is valid for including start and end times, days of the week, etc. Required. |
offer_url | string | URL to the merchant's offer page. Required for offer_category: OFFER_CATEGORY_BASE_OFFER. | |
image_url | string | URL to the merchant's offer image. |
OfferDetails
Field Name | Type | Requirement | Description |
---|---|---|---|
offer_display_text | string | Required | The offer text the offer provider wants to display to customers on the search results page. Required. |
| oneOf(offer_specification) | Required | Only one of the fields in this oneOf can be set. |
max_discount_value | object(Money) | The maximum discount that can be availed. For example, 10% off up to $100. | |
min_spend_value | object(Money) | The minimum spend value to avail the discount. For example, 10% off when the total price is $100 or more. | |
booking_cost | object(Money) | The cost to book this offer. For example, $100 off the final bill when a table is reserved at the cost of $15. | |
booking_cost_unit | enum(FeeUnit) | The unit of the booking cost. For example, per person, per transaction. | |
convenience_fee | object(Fee) | ||
booking_cost_adjustable | boolean | Whether the booking cost is adjustable i.e. the booking cost is subtracted from the final bill. For example: 30% off dinner with reservation. Cost to reserve $15 and it will be applied to the final bill. Hence final bill: Total Spent - 30% - $15 | |
additional_fees | array of object(AdditionalFee) | Additional fees that are charged to the user. Examples: convenience, handling etc. |
Money
Represents an amount of money with its currency type.
Field Name | Type | Requirement | Description |
---|---|---|---|
currency_code | string | The three-letter currency code defined in ISO 4217. | |
units | string | The whole units of the amount.
For example if currencyCode is "USD" , then 1 unit is one US dollar. | |
nanos | number | Number of nano (10^-9) units of the amount.
The value must be between -999,999,999 and +999,999,999 inclusive.
If units is positive, nanos must be positive or zero.
If units is zero, nanos can be positive, zero, or negative.
If units is negative, nanos must be negative or zero.
For example $-1.75 is represented as units =-1 and nanos =-750,000,000. |
Fee
Field Name | Type | Requirement | Description |
---|---|---|---|
unit | enum(FeeUnit) | ||
type | enum(FeeType) | ||
| oneOf(cost) | Only one of the fields in this oneOf can be set. |
MoneyRange
Field Name | Type | Requirement | Description |
---|---|---|---|
min_amount | object(Money) | ||
max_amount | object(Money) |
AdditionalFee
Field Name | Type | Requirement | Description |
---|---|---|---|
name | string | Required | The name of the additional fee. Examples: convenience fee, handling fee etc. Required. |
fee | object(Fee) |
OfferRestrictions
Field Name | Type | Requirement | Description |
---|---|---|---|
combinable_with_other_offers | boolean | Whether this offer can be combined with other offers. When true, partners can specify what offers this offer can be combined with. If both combinable_offer_categories & combinable_offer_ids are set then any offer matching one of the conditions above will be combinable. | |
combinable_offer_categories | array of enum(OfferCategory) | List of offer types that this offer can be combined with. For example, this offer may be combinable with other Coupons. If combinable_with_other_offers is true and this field is unset all types will be combinable. | |
combinable_offer_ids | array of string | List of offer_ids that this offer can be combined with. Some offers may only be combined with certain specific other offer_ids (can be considered parent offers). If combinable_with_other_offers is true and this field is unset all offer ids will be combinable. | |
inclusions | array of object(OfferCondition) | List of conditions that must be met for the offer to be valid (e.g., non-alcoholic drinks, food). | |
exclusions | array of object(OfferCondition) | List of conditions that would invalidate the offer (e.g., buffet, combo offers, and cocktails ). | |
min_guest | number | The minimum number of people required to avail the offer. | |
food_offer_restrictions | object(FoodOfferRestrictions) | Restrictions specific to food offers. |
OfferCondition
Field Name | Type | Requirement | Description |
---|---|---|---|
description | string |
FoodOfferRestrictions
Field Name | Type | Requirement | Description |
---|---|---|---|
meal_types | array of enum(MealType) | The meal types the offer can be applied to, such as lunch or dinner. If unset, the offer can be applied to all meal types. | |
restricted_to_certain_courses | boolean | Whether the offer can only be applied to certain courses. |
Coupon
Field Name | Type | Requirement | Description |
---|---|---|---|
text | string | The coupon text the offer provider wants to display to users. | |
code | string | Required | Coupon code required to redeem the offer. Required. |
PaymentInstrument
Field Name | Type | Requirement | Description |
---|---|---|---|
items | array of object(PaymentInstrumentItem) | Required | List of payment instruments that can be used to avail the offer. Required. |
provider_name | string | Required | Name of the payment instrument provider. Could be a banking partner, name of a bank, etc. For example: American Express, HDFC, ICICI. Required. |
PaymentInstrumentItem
Field Name | Type | Requirement | Description |
---|---|---|---|
type | enum(PaymentInstrumentType) | Required | Type of the payment instrument. Required. |
name | string | Required | Name of the payment instrument item like the name of the credit card. For example: HDFC Infinia, American Express Platinum. Required. |
Subscription
Field Name | Type | Requirement | Description |
---|---|---|---|
name | string | Required | The name of the subscription. Required. |
subscription_auto_added | boolean | Whether the subscription is auto added when a user avails this offer | |
cost | object(Money) | Required | The cost of the subscription. Required. |
subscription_duration | object(Duration) | Required | How long the subscription is valid for at the subscription_cost. Required. |
terms_and_conditions_url | string | URL to the partner's terms and conditions relevant to this subscription. |
Duration
Field Name | Type | Requirement | Description |
---|---|---|---|
seconds | string | Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive. Note: these bounds are computed from: 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years | |
nanos | number | Signed fractions of a second at nanosecond resolution of the span
of time. Durations less than one second are represented with a 0
seconds field and a positive or negative nanos field. For durations
of one second or more, a non-zero value for the nanos field must be
of the same sign as the seconds field. Must be from -999,999,999
to +999,999,999 inclusive. |
Terms
Field Name | Type | Requirement | Description |
---|---|---|---|
url | string | URL to the partner's terms and conditions. | |
restricted_to_certain_users | boolean | Whether the offer is restricted to certain users. | |
terms_and_conditions | string | Primary T&C text provided by the partner. | |
additional_terms_and_conditions | array of string | Terms and conditions in addition to the primary T&C from the partner. |
ValidityPeriod
Field Name | Type | Requirement | Description |
---|---|---|---|
valid_period | object(ValidityRange) | The start and end timestamp that the offer is valid for. These times must represent distinct days i.e. the start time must be 00:00 (beginning of the day) and the end time must be 00:00 (exclusive) on the day the validity period ends. | |
time_of_day | array of object(TimeOfDayWindow) | Specifies the valid time interval on a given day and which days are
available for the offer.
For example:
Monday: 10AM to 5PM
Tuesday: 10AM to 2PM
Tuesday: 5PM to 7PM
Wed, Thur, Fri, Sat, Sun: 3PM to 7PM
If none set, it means the offer is available for all time within
valid_period . | |
time_exceptions | array of object(ValidTimeException) | Specifies exceptions to the above valid_period and valid_time_of_week |
ValidityRange
A closed-open timestamp range.
Field Name | Type | Requirement | Description |
---|---|---|---|
valid_from_time | object(Timestamp) | Required | The beginning time of the range (inclusive). Required. |
valid_through_time | object(Timestamp) | The ending time of the range (exclusive). If not set, it means that this period is never ending. Optional. |
Timestamp
Field Name | Type | Requirement | Description |
---|---|---|---|
seconds | string | Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. | |
nanos | number | Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. |
TimeOfDayWindow
The TimeWindow object is a composite entity that describes a list of windows the user's order can be either placed or fulfilled.
Field Name | Type | Requirement | Description |
---|---|---|---|
time_windows | object(TimeOfDayRange) | Required | The time window the order can be placed/fulfilled. Required. |
day_of_week | array of enum(DayOfWeek) | The list of days in a week the windows are applied. If none set, it means that it applies for all days of the week. Optional. |
TimeOfDayRange
A closed-open time range.
Field Name | Type | Requirement | Description |
---|---|---|---|
open_time | object(TimeOfDay) | A Time indicating the beginning time of the day of the range (inclusive). If not set, it means 00:00:00. Optional. | |
close_time | object(TimeOfDay) | A Time indicating the ending time of the day of the range (exclusive). If not set, it means 23:59:59. Optional. |
TimeOfDay
Field Name | Type | Requirement | Description |
---|---|---|---|
hours | number | Hours of a day in 24 hour format. Must be greater than or equal to 0 and typically must be less than or equal to 23. An API may choose to allow the value "24:00:00" for scenarios like business closing time. | |
minutes | number | Minutes of an hour. Must be greater than or equal to 0 and less than or equal to 59. | |
seconds | number | Seconds of a minute. Must be greater than or equal to 0 and typically must be less than or equal to 59. An API may allow the value 60 if it allows leap-seconds. | |
nanos | number | Fractions of seconds, in nanoseconds. Must be greater than or equal to 0 and less than or equal to 999,999,999. |
ValidTimeException
Field Name | Type | Requirement | Description |
---|---|---|---|
exceptional_period | object(ValidityRange) | The start and end timestamp that the offer is not valid for. These times must represent distinct days i.e. the start time must be 00:00 (beginning of the day) and the end time must be 00:00 (exclusive) on the day the exception period ends. |
OfferSource
Name | Description |
---|---|
OFFER_SOURCE_UNSPECIFIED | |
OFFER_SOURCE_AGGREGATOR |
ActionType
Name | Description |
---|---|
ACTION_TYPE_UNSPECIFIED | |
ACTION_TYPE_DINING |
OfferMode
Name | Description |
---|---|
OFFER_MODE_OTHER | |
OFFER_MODE_WALK_IN | |
OFFER_MODE_FREE_RESERVATION | |
OFFER_MODE_PAID_RESERVATION | |
OFFER_MODE_ONLINE_ORDER |
OfferCategory
Category of the offer. A base offer is a standard offer available to all customers such as 10% off spending over $100. A base offer restricted by a coupon or payment instrument will have the respective fields set. We also have add on x offers such as ADD_ON_PAYMENT_OFFER. Such offers can be added to other offers to gain additional discounts.
Name | Description |
---|---|
OFFER_CATEGORY_UNSPECIFIED | |
OFFER_CATEGORY_BASE_OFFER | |
OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER | |
OFFER_CATEGORY_ADD_ON_COUPON_OFFER | |
OFFER_CATEGORY_ADD_ON_SUBSCRIPTION_OFFER |
FeeUnit
Name | Description |
---|---|
FEE_UNIT_UNSPECIFIED | |
FEE_UNIT_PER_GUEST | |
FEE_UNIT_PER_TRANSACTION |
FeeType
Name | Description |
---|---|
FEE_TYPE_UNSPECIFIED | |
FEE_TYPE_FIXED | |
FEE_TYPE_VARIABLE |
MealType
Name | Description |
---|---|
MEAL_TYPE_UNSPECIFIED | |
MEAL_TYPE_BREAKFAST | |
MEAL_TYPE_LUNCH | |
MEAL_TYPE_DINNER |
PaymentInstrumentType
Name | Description |
---|---|
PAYMENT_INSTRUMENT_TYPE_UNSPECIFIED | |
PAYMENT_INSTRUMENT_CREDIT_CARD | |
PAYMENT_INSTRUMENT_DEBIT_CARD | |
PAYMENT_INSTRUMENT_BANK_ACCOUNT | |
PAYMENT_INSTRUMENT_UPI | |
PAYMENT_INSTRUMENT_ONLINE_WALLET |
DayOfWeek
Represents a day of the week.
Name | Description |
---|---|
DAY_OF_WEEK_UNSPECIFIED | The day of the week is unspecified. |
MONDAY | Monday |
TUESDAY | Tuesday |
WEDNESDAY | Wednesday |
THURSDAY | Thursday |
FRIDAY | Friday |
SATURDAY | Saturday |
SUNDAY | Sunday |
offer_specification
The discount can be a percentage or a fixed value subtracted from the total value. For example: 1. 10% off the final bill. 2. $15 off an order. Merchants can also offer custom discounts such as 'buy one get one free' through the relevant specification fields. Required.
Field Name | Type | Requirement | Description |
---|---|---|---|
discount_percent | number | Mutally exclusive with | Percentage of the bill that is discounted. [0, 100] For 1+1 or 50% off offers that are applicable to the whole meal (e.g. 1+1 buffet, 1+1 on entire bill, 1+1 on set menu), this value can be set to 50. |
discount_value | object(Money) | Mutally exclusive with | Fixed value of the discount. |
other_offer_detail_text | string | Mutally exclusive with | Free-form text to describe the discount. For specific 1+1 offers (e.g. 1+1 drinks, +1 main course, 1+1 selected menu items), these details should be described here. |
cost
Field Name | Type | Requirement | Description |
---|---|---|---|
amount | object(Money) | Mutally exclusive with | |
amount_range | object(MoneyRange) | Mutally exclusive with |
Feed upload
The
Offers feed
must be uploaded to the Generic
feed SFTP server. Follow the
How to use the Generic feed SFTP server tutorial
for instructions and use the name
set to google.offer
in your descriptor
file.
Upload frequency
In general, Google expect 1 feed upload per day. frequency may be increased or decreased depending on the frequency of offer updates on your side to ensure a consistently high precision. Consult with your Google POC.
The data will take a few hours before appearing on Google.
Offers categorization
OFFER_CATEGORY_BASE_OFFER
: Offers that can be claimed independently without being combined with any other offer. This includes:- Flat discounts on the entire bill (e.g., 20% off)
- Subscription offers (e.g., Free dessert with membership)
- Payment offers in cases where there are no other base offers for the restaurant
- Add-On Offers: Offers that require a base offer to be claimed. These
include:
OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER
(e.g., Additional 10% off with specific credit card)OFFER_CATEGORY_ADD_ON_COUPON_OFFER
(e.g., Free drink with a specific coupon code)OFFER_CATEGORY_ADD_ON_SUBSCRIPTION_OFFER
(e.g., Additional 10% off for subscribers)
Other considerations:
- When a restaurant has no Base Offer set, Add-On offers won't be displayed.
If there's no Base Offer, any Payment, Subscription or Coupon Offer that can
be claimed without needing to be added on to another offer must be tagged as
OFFER_CATEGORY_BASE_OFFER
.- Depending on the type, the relevant data for
PaymentInstrument
,Subscription
, orCoupon
must be set. - Partners must provide 2 copies of each of these offers to cover
scenarios where they function as both Base Offers and Add-On Offers. The
Add-On Offer copy can then be set for multiple restaurants using either
entity_ids
oradd_on_offer_applicable_to_all_entities
.
- Depending on the type, the relevant data for
- When a restaurant has multiple Base Offers that can be stackable, all the
Base Offers should be tagged as
OFFER_CATEGORY_BASE_OFFER
, and Base Offers which are Payment, Subscription or Coupon offers should be sent additionally as the relevant Add-On Offer type. ValidityPeriod
should be used to activate Add-On offers as Base Offers only when there is no active Base Offer.
Example scenarios:
A restaurant offers 5% off when paying with a specific credit card and a free drink with a specific coupon code
- 5% off credit card offer should be sent as 2 copies, one tagged as
OFFER_CATEGORY_BASE_OFFER
and one tagged asOFFER_CATEGORY_ADD_ON_PAYMENT_OFFER
with thePaymentInstrument
details included. - Free drink with a coupon code offer should be sent as
OFFER_CATEGORY_ADD_ON_COUPON_OFFER
with theCoupon
details included.
- 5% off credit card offer should be sent as 2 copies, one tagged as
A restaurant offers 10% off for walk-ins and 5% off when paying with a specific credit card, both of which can be combined.
- 10% walk-in offer should be tagged as
OFFER_CATEGORY_BASE_OFFER
. - 5% off credit card offer should have 2 copies, with one tagged as
OFFER_CATEGORY_BASE_OFFER
, and one tagged asOFFER_CATEGORY_ADD_ON_PAYMENT_OFFER
.
- 10% walk-in offer should be tagged as
A restaurant offers 10% off only for lunch on weekdays, and 5% off anytime when paying with a specific credit card.
- 10% off offer should have
ValidityPeriod
set to indicate only during the restaurant's lunch hours on weekdays. - 5% off credit card offer should be sent as 2 copies.
- One copy should be tagged as
OFFER_CATEGORY_BASE_OFFER
with thePaymentInstrument
details included.ValidityPeriod
of should be set to exclude lunch hours on weekdays when the 10% off lunch offer is active - One copy should be tagged as
OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER
with thePaymentInstrument
details included.
- One copy should be tagged as
- All other Payment Offers for this restaurant should be tagged as
OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER
.
- 10% off offer should have
Development & launch process
Throughout your integration, the Partner Portal will be able to assist you with information and feedback based on your development. The development process will follow this flow:
- The integration will be first developed in the Sandbox environment. You should be using an export of production (or even production data directly) in the Google Sandbox environment. This helps ensure that your development catches all edge cases and allows Google to evaluate data quality and better assist you based on your data model.
- Once you are uploading complete and daily Merchant, Services, and Deals feed consistently in the Google Sandbox environment the Google team will evaluate your feeds. Once the Google team provides approval, you can push your code to production and begin sending production data to the Google Production environment.
- After you have fully tested the Production integration the Google team will test as well. Once all testing is complete, then your integration will launch.
Monitoring
To ensure a good user experience, Google will check that the offers provided are valid, correct and meet our policy criteria pre and post launch. To do so, Google will use a combination of human and automated review. The result of these reviews will be accessible in the Offer Dashboard of the Action Center (production only). The outcome of this monitoring might be used to affect the ranking of the offers.
Automated checks (Crawlers)
Google quality team implements crawlers. Crawlers are scripts that automate a web browser to perform some clicks and extract offer information, for quality testing purposes only.
Number of queries
For example, if we decided to send 5000 checks per day, it means that 5000 times per day (evenly distributed across the day, that is approximately one every 17 seconds), our crawler performs all of the following actions a regular user would perform:
- Start from Google Search, and click the partner link.
- Look for the offer information.
- If the offer requires booking, it will continue toward the booking flow to confirm the offer is available at the specified time (no booking will be placed).
Web scraper detection
To ensure that the web scraper does not get banned (which may lead it to conclude the offers are not available) make sure your system allows our web scraper to query your page at all times. To identify our web scraper:
- The web scraper User-Agent will contain the string "Google-Offers":
- Example: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko; Google-Offers) Chrome/104.0.5112.101 Safari/537.36
- You may also check if the calls come from google using reverse DNS as
recommended in
"Verifying Googlebot and other Google crawlers".
In our specific case, the reverse DNS resolution follows this pattern:
google-proxy-***-***-***-***.google.com
.
Technical behavior
Caching
For purposes of reducing load on the partner website, our crawlers are generally configured to respect all standard HTTP caching headers present in the response. That means that for correctly configured websites we avoid repeatedly fetching content that changes rarely (e.g. JavaScript libraries). For more details on how to implement caching, read this HTTP caching documentation.