Google Hotels Integration: Developer Guide

Implement Google Hotels from a developer perspective with Hotel Lists, property mapping, Pull/Changed Pricing/ARI, Transaction XML, landing pages, OAuth2, request/response models, error handling and monitoring.

Editorial information
Changelog
  • 2026-09-26 — Provider companion standard applied; access, limits, lifecycle and evidence boundaries clarified.
Advertisement

A Google Hotels integration is not a single REST call. A production connection spans at least four contracts:

  1. Property identity — which hotels you submit and how they are matched,
  2. Pricing & availability — which delivery mode and XML contract moves rates,
  3. Landing-page handoff — whether Google clicks preserve the booking context,
  4. Monitoring & quality — mapping, freshness, price accuracy and downstream conversion.

The safest developer architecture keeps Google-specific XML/API models outside your internal hotel domain.

text
Internal Hotel Domain
      |
      +--> Google Hotel List Adapter
      |
      +--> Google Pricing Adapter
      |       +--> Pull
      |       +--> Changed Pricing
      |       +--> ARI
      |
      +--> Landing Page Adapter
      |
      +--> Travel Partner API / Diagnostics

This guide focuses on the implementation boundary rather than repeating Google's documentation.

For platform ownership and readiness decisions, see the Google Hotels profile. For contract boundaries, publication recovery and the Hotel List/POI distinction, see Google Hotel Feeds.

1. Access and prerequisites

Google Hotels is not a generic self-service endpoint opened with one public API key. You need the appropriate Hotel Center partner setup, pricing/feed configuration and, for some APIs, trusted-partner access.

Google currently documents OAuth 2.0 with service accounts for hotel APIs.

Travel Partner API scope:

text
https://www.googleapis.com/auth/travelpartner

Price Feeds API scope:

text
https://www.googleapis.com/auth/travel-partner-price-upload

Travel Partner API endpoint pattern:

text
https://travelpartner.googleapis.com/v3/accounts/{account_id}/{path}

The service account also needs access to the relevant Hotel Center account.

Internal credential boundary

Credentials belong on the server:

ts
interface GoogleHotelsCredentials {
  serviceAccountEmail: string;
  privateKeyRef: string;
  hotelCenterAccountId: string;
  partnerKey?: string;
}

Store a secret-manager reference rather than embedding the private key in application configuration.

2. Start with a canonical hotel model

Before submitting properties to Google, your own identity model should be stable.

ts
interface Hotel {
  id: string;
  name: string;
  countryCode: string;
  city?: string;
  addressLine?: string;
  latitude?: number;
  longitude?: number;
  phone?: string;
  website?: string;
  active: boolean;
}

Keep Google mapping separate:

ts
interface GoogleHotelMapping {
  hotelId: string;
  googlePartnerHotelId: string;
  matchStatus: "matched" | "unmatched" | "review";
  matchConfidence?: number;
  lastSubmittedAt?: string;
  lastMatchedAt?: string;
}

Invariant:

text
internal hotel id != Google partner hotel id

A rename or rebrand should not create a new internal identity.

3. Hotel List integration

The Hotel List is the property-identity layer used by Google. The <Property> value in pricing messages must match the listing ID defined in your Hotel List.

Example internal record:

json
{
  "id": "HTL-84721",
  "name": "Example Bosphorus Hotel",
  "countryCode": "TR",
  "city": "Istanbul",
  "latitude": 41.0423,
  "longitude": 29.0082,
  "active": true
}

Simplified adapter output:

xml
<listing>
  <id>HTL-84721</id>
  <name>Example Bosphorus Hotel</name>
  <address>
    <component name="country">TR</component>
    <component name="locality">Istanbul</component>
  </address>
  <latitude>41.0423</latitude>
  <longitude>29.0082</longitude>
</listing>

Use Google's current Hotel List XML reference for the production schema.

Pipeline

text
Canonical Hotel DB
      |
      v
Eligibility validation
      |
      v
Google Hotel List serializer
      |
      v
Feed delivery
      |
      v
Match report / diagnostics
      |
      +--> matched
      +--> unmatched
      +--> manual review

Monitor stable IDs, geocoding, duplicates, rebrands, closures and unsupported entities.

4. Choosing a pricing delivery mode

Google documents three primary models.

Pull

Google sends a <Query>; your system responds with current pricing in a <Transaction>.

Useful when:

  • you cannot reliably detect every price change,
  • your query-time backend is fast,
  • upstream latency is controlled.

Main risks are supplier latency, query bursts and high look-to-book load.

Changed Pricing

Google sends <HintRequest>. Your system identifies changed hotels or itineraries with <Hint>. Google then requests only those contexts with <Query>.

text
Google HintRequest
      |
Partner Hint
      |
Google Query
      |
Partner Transaction

Use it when change detection is reliable.

ARI

Your system pushes Availability, Rate and Inventory changes.

Use it when local ARI state is authoritative and you can generate reliable deltas/events.

Decision table

SituationLikely starting point
Weak change detection, fast backendPull
Reliable change detectionChanged Pricing
Strong ARI source of truthARI
High supplier latencyMore cache/Changed/ARI
Child occupancy criticalEvaluate context-aware live pricing carefully

5. Pull / Changed Pricing request-response flow

Simplified Google query:

xml
<Query>
  <Checkin>2026-10-10</Checkin>
  <Nights>3</Nights>
  <PropertyList>
    <Property>HTL-84721</Property>
  </PropertyList>
</Query>

Parse into an adapter DTO first:

ts
interface GooglePriceQuery {
  checkIn: string;
  nights: number;
  propertyIds: string[];
}

Then map into the internal request model:

ts
interface HotelPriceRequest {
  hotelIds: string[];
  checkIn: string;
  checkOut: string;
  rooms: Array<{
    adults: number;
    childAges: number[];
  }>;
  currency?: string;
  market?: string;
}

Flow:

text
Google Query
   -> parse
   -> validate
   -> provider hotel id -> internal hotel id
   -> internal price request
   -> price engine
   -> normalize
   -> Google Transaction serializer

6. Transaction XML response

Pricing responses are centered on <Transaction>.

Simplified example:

xml
<Transaction timestamp="2026-09-20T12:00:00Z" id="txn-84721-1010">
  <Result>
    <Property>HTL-84721</Property>
    <Checkin>2026-10-10</Checkin>
    <Nights>3</Nights>

    <Baserate currency="TRY">12000.00</Baserate>
    <Tax currency="TRY">2400.00</Tax>
    <OtherFees currency="TRY">300.00</OtherFees>

    <Refundable available="true" refundable_until_days="2" />
    <Occupancy>2</Occupancy>
  </Result>
</Transaction>

Google's current Transaction reference requires a timestamp and transaction ID. The <Property> must match the Hotel List listing ID. <Rates> and <RoomBundle> support richer room/rate scenarios.

The documented upper message-size limit is 100 MB; treat that as a protocol ceiling, not an operational batching target.

7. Normalize before serializing

Do not let Google XML become the domain model.

ts
interface HotelOffer {
  hotelId: string;
  provider: string;

  checkIn: string;
  nights: number;
  occupancy: {
    adults: number;
    childAges: number[];
  };

  roomId?: string;
  ratePlanId?: string;

  baseAmount: number;
  taxAmount: number;
  mandatoryFeeAmount: number;
  totalAmount: number;
  currency: string;

  refundable?: boolean;
  refundableUntil?: string;

  observedAt: string;
  source: "live" | "cache";
}

Serializer boundary:

ts
function toGoogleTransaction(offer: HotelOffer): GoogleTransaction {
  return {
    property: offer.hotelId,
    checkIn: offer.checkIn,
    nights: offer.nights,
    baseRate: offer.baseAmount,
    tax: offer.taxAmount,
    otherFees: offer.mandatoryFeeAmount,
    currency: offer.currency,
    occupancy: offer.occupancy.adults
  };
}

The production serializer must implement Google's current XML schema.

8. RoomData, PackageData and RoomBundle

For richer room-level offers, Google separates room/package metadata from itinerary-level pricing.

text
Transaction
  ├── PropertyDataSet
  │     ├── Property
  │     ├── RoomData
  │     └── PackageData
  │
  └── Result
        ├── Rates
        └── RoomBundle

Keep physical room and commercial rate semantics separate internally:

ts
interface Room {
  id: string;
  hotelId: string;
  name: string;
  maxOccupancy?: number;
}

interface RatePlan {
  id: string;
  meal?: string;
  refundable?: boolean;
  paymentType?: string;
  conditionalRule?: string;
}

Google RoomID and PackageID belong in the adapter boundary and do not need to equal your canonical IDs.

9. Taxes, fees and total price

Do not flatten components too early.

ts
interface PriceBreakdown {
  base: number;
  taxes: number;
  mandatoryFees: number;
  payAtProperty?: number;
  total: number;
  currency: string;
}

At the simplest level:

text
total = base + taxes + mandatory fees

But pay-at-property and market-specific display rules still need explicit semantics.

A classic failure is submitting a low base rate while the landing page displays a materially higher mandatory total.

10. Occupancy and child pricing

Occupancy belongs in the cache key:

text
hotel
+ check-in
+ nights
+ adults
+ child ages
+ currency
+ market

Google's current Transaction documentation specifically calls out child occupancy pricing in the context of live/context-aware pricing. Do not collapse child cases into generic double-occupancy cache entries.

ts
interface Occupancy {
  adults: number;
  childAges: number[];
}
text
2 adults != 2 adults + child age 7

11. Changed Pricing hint model

Represent change detection internally first:

ts
interface PriceChangeEvent {
  hotelId: string;
  firstAffectedDate: string;
  lastAffectedDate?: string;
  nights?: number;
  changedAt: string;
  version: number;
}

Then serialize a Google Hint:

xml
<Hint>
  <Item>
    <Property>HTL-84721</Property>
    <Stay>
      <CheckInDate>2026-10-10</CheckInDate>
      <LengthOfStay>3</LengthOfStay>
    </Stay>
  </Item>
</Hint>

Change events should be idempotent.

12. Landing-page integration

Google landing-page files use <PointsOfSale> with one or more <PointOfSale> definitions.

The real implementation concern is context preservation.

text
https://booking.example.com/hotel/{hotelId}
  ?checkin={checkIn}
  &checkout={checkOut}
  &adults={adults}
  &currency={currency}
  &click_id={trackingId}

Internal model:

ts
interface BookingHandoff {
  hotelId: string;
  checkIn: string;
  checkOut: string;
  occupancy: Occupancy;
  currency: string;
  roomId?: string;
  ratePlanId?: string;
  clickId: string;
}

Validate the correct property, dates, occupancy, currency, selected product, total price, mobile redirects and attribution IDs.

13. Where the Travel Partner API fits

Travel Partner API is not a generic booking API replacing the pricing XML flow. It is a separate Hotel Center management/diagnostic API surface.

Authorization:

http
Authorization: Bearer <oauth2-access-token>

Conceptual call:

http
GET https://travelpartner.googleapis.com/v3/accounts/123456/priceAccuracyViews/latest
Authorization: Bearer <token>

Client boundary:

ts
interface GoogleTravelPartnerClient {
  getPriceAccuracyView(accountId: string, viewId: string): Promise<PriceAccuracyView>;
  getAccountDiagnostics(accountId: string): Promise<unknown>;
}

Keep Google API DTOs out of your dashboard/domain model.

14. Price-accuracy feedback loop

Google's structured-data and price-accuracy tooling can help validate submitted pricing against the visible landing experience.

text
Price submitted
     |
     v
Google displays offer
     |
     v
Landing-page validation
     |
     +--> match
     |
     +--> mismatch
             |
             +--> classify reason
             +--> provider/hotel alert
             +--> freshness policy update
             +--> mapping/rate fix

Useful internal mismatch taxonomy:

text
TAX_MISMATCH
MANDATORY_FEE_MISMATCH
STALE_PRICE
WRONG_ROOM
WRONG_ITINERARY
CONDITIONAL_RATE
LANDING_CONTEXT_LOST
UNAVAILABLE
UNKNOWN

15. Timeouts, retries and errors

Do not apply one retry policy to every failure.

Usually retryable

  • transient network failure,
  • selected 5xx,
  • temporary timeout,
  • quota/rate-limit conditions with appropriate backoff.

Usually not blindly retryable

  • invalid XML,
  • Hotel List ID mismatch,
  • schema validation error,
  • unsupported currency,
  • malformed occupancy,
  • invalid landing configuration.
ts
interface RetryPolicy {
  maxAttempts: number;
  initialBackoffMs: number;
  maxBackoffMs: number;
  retryableStatusCodes: number[];
}

Normalize Google failures:

text
GOOGLE_AUTH
GOOGLE_SCHEMA
GOOGLE_MAPPING
GOOGLE_RATE_LIMIT
GOOGLE_TIMEOUT
GOOGLE_UPSTREAM
GOOGLE_PRICE_REJECTED
GOOGLE_LANDING_ERROR

16. Idempotency and ordering

Use unique transaction identities and preserve update ordering.

ts
interface PricingUpdateEnvelope {
  eventId: string;
  hotelId: string;
  version: number;
  sourceUpdatedAt: string;
  generatedAt: string;
}

Rule:

text
older version -> ignore
same version  -> idempotent no-op
new version   -> publish

Google's Transaction documentation also gives timestamp ordering semantics, so stale updates should not be allowed to overwrite newer source state in your own pipeline.

17. Persistence and audit trail

You do not need to keep every raw XML payload forever, but retain enough lineage for replay and debugging.

text
google_price_updates
- event_id
- hotel_id
- check_in
- nights
- occupancy_key
- source_version
- base_amount
- tax_amount
- fee_amount
- currency
- generated_at
- sent_at
- result_status
- retry_count
- payload_hash

Raw payloads can live behind object-storage references.

18. Monitoring and SLOs

Property identity

  • Hotel List accepted count,
  • mapping coverage,
  • unmatched hotels,
  • duplicate/review queue.

Pricing

  • query count,
  • transaction success,
  • p95 response latency,
  • stale age,
  • changed-pricing event lag,
  • rejected messages.

Quality

  • price accuracy,
  • tax/fee mismatch,
  • unavailable-after-click,
  • landing-context success.

Commercial

  • Google click-to-booking conversion,
  • cancellation-adjusted conversion,
  • unattributed bookings,
  • revenue by freshness bucket.

Example internal SLOs:

text
mapping coverage                    >= 99%
p95 pricing response               <= internal budget
offers under freshness threshold   >= 97%
severe price mismatch              < 1%
landing context success            >= 99%

Calibrate targets to your own supply/account profile.

19. Integration test matrix

TestExpected behavior
1 night / 2 adultsbase happy path
3 nightscorrect stay total
child occupancychild context preserved
refundablecorrect policy
non-refundablecorrect policy
tax-heavy marketcorrect total
mandatory property feecorrect disclosure
currency changecorrect currency
sold outoffer removed
price changedfresh response
hotel renamedstable ID
hotel closedlisting state updated
mobile landingcontext preserved
stale cachecorrect revalidation/fallback
duplicate eventidempotent

20. Go-live checklist

  • Hotel Center / partner access ready
  • Service account authorized
  • Canonical hotel IDs stable
  • Hotel List validation complete
  • Mapping coverage dashboard available
  • Pricing delivery mode selected
  • Google Query parser tested
  • Transaction serializer schema-valid
  • Room/rate semantics normalized
  • Tax/fee breakdown correct
  • Occupancy/child cases tested
  • Changed Pricing/ARI ordering safe
  • Landing pages preserve context
  • OAuth token refresh works
  • Retry taxonomy implemented
  • Idempotency implemented
  • Price-accuracy dashboard exists
  • Alerts available at provider/property level
  • Conversion reconciliation ownership clear

Summary

If you model Google Hotels as “an XML export,” provider-specific behavior quickly leaks across the system.

A stronger design is:

text
canonical domain
   -> Google adapter
   -> Google XML/API contract
   -> diagnostics
   -> quality feedback

Developer success is not just a feed being accepted. Property identity, occupancy, rate semantics, total price and booking context should remain consistent from Google results through the booking engine.

Companion contract checklist

Capability boundary

Google Hotels spans distinct property, pricing, landing-page and diagnostic surfaces. Treat each surface as a separate contract boundary rather than assuming one API owns the entire integration.

Polling and pagination

Pricing XML flows are message-driven rather than generic paginated REST collections. Travel Partner API pagination is endpoint-specific. Do not invent a universal polling interval or generic page loop.

Evidence boundary

The implementation examples in this guide are illustrative. They are based on official public documentation reviewed on 2026-09-26 and are not claimed to have been tested against a live Hotel Center account.

Technical advisory

Planning a Google Hotels integration?

We can review the feed, connectivity, attribution and production architecture with you.

Discuss your project →

Sources

Related content