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.
- 2026-09-26 — Provider companion standard applied; access, limits, lifecycle and evidence boundaries clarified.
A Google Hotels integration is not a single REST call. A production connection spans at least four contracts:
- Property identity — which hotels you submit and how they are matched,
- Pricing & availability — which delivery mode and XML contract moves rates,
- Landing-page handoff — whether Google clicks preserve the booking context,
- Monitoring & quality — mapping, freshness, price accuracy and downstream conversion.
The safest developer architecture keeps Google-specific XML/API models outside your internal hotel domain.
Internal Hotel Domain
|
+--> Google Hotel List Adapter
|
+--> Google Pricing Adapter
| +--> Pull
| +--> Changed Pricing
| +--> ARI
|
+--> Landing Page Adapter
|
+--> Travel Partner API / DiagnosticsThis 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:
https://www.googleapis.com/auth/travelpartnerPrice Feeds API scope:
https://www.googleapis.com/auth/travel-partner-price-uploadTravel Partner API endpoint pattern:
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:
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.
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:
interface GoogleHotelMapping {
hotelId: string;
googlePartnerHotelId: string;
matchStatus: "matched" | "unmatched" | "review";
matchConfidence?: number;
lastSubmittedAt?: string;
lastMatchedAt?: string;
}Invariant:
internal hotel id != Google partner hotel idA 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:
{
"id": "HTL-84721",
"name": "Example Bosphorus Hotel",
"countryCode": "TR",
"city": "Istanbul",
"latitude": 41.0423,
"longitude": 29.0082,
"active": true
}Simplified adapter output:
<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
Canonical Hotel DB
|
v
Eligibility validation
|
v
Google Hotel List serializer
|
v
Feed delivery
|
v
Match report / diagnostics
|
+--> matched
+--> unmatched
+--> manual reviewMonitor 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>.
Google HintRequest
|
Partner Hint
|
Google Query
|
Partner TransactionUse 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
| Situation | Likely starting point |
|---|---|
| Weak change detection, fast backend | Pull |
| Reliable change detection | Changed Pricing |
| Strong ARI source of truth | ARI |
| High supplier latency | More cache/Changed/ARI |
| Child occupancy critical | Evaluate context-aware live pricing carefully |
5. Pull / Changed Pricing request-response flow
Simplified Google query:
<Query>
<Checkin>2026-10-10</Checkin>
<Nights>3</Nights>
<PropertyList>
<Property>HTL-84721</Property>
</PropertyList>
</Query>Parse into an adapter DTO first:
interface GooglePriceQuery {
checkIn: string;
nights: number;
propertyIds: string[];
}Then map into the internal request model:
interface HotelPriceRequest {
hotelIds: string[];
checkIn: string;
checkOut: string;
rooms: Array<{
adults: number;
childAges: number[];
}>;
currency?: string;
market?: string;
}Flow:
Google Query
-> parse
-> validate
-> provider hotel id -> internal hotel id
-> internal price request
-> price engine
-> normalize
-> Google Transaction serializer6. Transaction XML response
Pricing responses are centered on <Transaction>.
Simplified example:
<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.
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:
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.
Transaction
├── PropertyDataSet
│ ├── Property
│ ├── RoomData
│ └── PackageData
│
└── Result
├── Rates
└── RoomBundleKeep physical room and commercial rate semantics separate internally:
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.
interface PriceBreakdown {
base: number;
taxes: number;
mandatoryFees: number;
payAtProperty?: number;
total: number;
currency: string;
}At the simplest level:
total = base + taxes + mandatory feesBut 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:
hotel
+ check-in
+ nights
+ adults
+ child ages
+ currency
+ marketGoogle'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.
interface Occupancy {
adults: number;
childAges: number[];
}2 adults != 2 adults + child age 711. Changed Pricing hint model
Represent change detection internally first:
interface PriceChangeEvent {
hotelId: string;
firstAffectedDate: string;
lastAffectedDate?: string;
nights?: number;
changedAt: string;
version: number;
}Then serialize a Google Hint:
<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.
https://booking.example.com/hotel/{hotelId}
?checkin={checkIn}
&checkout={checkOut}
&adults={adults}
¤cy={currency}
&click_id={trackingId}Internal model:
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:
Authorization: Bearer <oauth2-access-token>Conceptual call:
GET https://travelpartner.googleapis.com/v3/accounts/123456/priceAccuracyViews/latest
Authorization: Bearer <token>Client boundary:
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.
Price submitted
|
v
Google displays offer
|
v
Landing-page validation
|
+--> match
|
+--> mismatch
|
+--> classify reason
+--> provider/hotel alert
+--> freshness policy update
+--> mapping/rate fixUseful internal mismatch taxonomy:
TAX_MISMATCH
MANDATORY_FEE_MISMATCH
STALE_PRICE
WRONG_ROOM
WRONG_ITINERARY
CONDITIONAL_RATE
LANDING_CONTEXT_LOST
UNAVAILABLE
UNKNOWN15. 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.
interface RetryPolicy {
maxAttempts: number;
initialBackoffMs: number;
maxBackoffMs: number;
retryableStatusCodes: number[];
}Normalize Google failures:
GOOGLE_AUTH
GOOGLE_SCHEMA
GOOGLE_MAPPING
GOOGLE_RATE_LIMIT
GOOGLE_TIMEOUT
GOOGLE_UPSTREAM
GOOGLE_PRICE_REJECTED
GOOGLE_LANDING_ERROR16. Idempotency and ordering
Use unique transaction identities and preserve update ordering.
interface PricingUpdateEnvelope {
eventId: string;
hotelId: string;
version: number;
sourceUpdatedAt: string;
generatedAt: string;
}Rule:
older version -> ignore
same version -> idempotent no-op
new version -> publishGoogle'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.
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_hashRaw 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:
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
| Test | Expected behavior |
|---|---|
| 1 night / 2 adults | base happy path |
| 3 nights | correct stay total |
| child occupancy | child context preserved |
| refundable | correct policy |
| non-refundable | correct policy |
| tax-heavy market | correct total |
| mandatory property fee | correct disclosure |
| currency change | correct currency |
| sold out | offer removed |
| price changed | fresh response |
| hotel renamed | stable ID |
| hotel closed | listing state updated |
| mobile landing | context preserved |
| stale cache | correct revalidation/fallback |
| duplicate event | idempotent |
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:
canonical domain
-> Google adapter
-> Google XML/API contract
-> diagnostics
-> quality feedbackDeveloper 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.
Planning a Google Hotels integration?
We can review the feed, connectivity, attribution and production architecture with you.