Integration Architecture and Data Flow

A robust integration between Google Shopping and Odoo 18 requires a clear, event-driven architecture. The system must handle bidirectional data flow, reacting to changes in both platforms. You will build this on top of Odoo’s built-in Connector framework, which provides the foundational queues and jobs for managing synchronous and asynchronous tasks. The core components include a custom Odoo module, the Google Shopping Content API, and a dedicated synchronization engine. Your design must account for real-time triggers for critical events like stock updates and batched processing for less time-sensitive product data feeds.

The primary data flow originates from Odoo to Google Merchant Center. When a product’s quantity or price changes in Odoo, an Odoo ORM trigger fires. This trigger creates a job in the Connector queue, which a dedicated job runner processes. The job runner fetches the product data, transforms it from the Odoo product model into the Google Shopping product schema, and pushes the update via the Content API. This ensures your shopping ads always reflect accurate, real-time information. For high-volume stores, you implement a delta-sync mechanism, transmitting only the fields that changed instead of the entire product record.

The reverse data flow handles order synchronization from Google Shopping to Odoo. A Google Pub/Sub topic, configured in your Google Cloud project, receives notifications for new orders. Your Odoo module exposes a webhook endpoint that receives these Pub/Sub messages. Upon receiving a notification, the endpoint authenticates the request, fetches the complete order details from the Content API, and creates a new sale order in Odoo. This process automatically creates the customer record if it does not exist and reserves inventory from your stock. The architecture treats Odoo as the system of record, ensuring all commerce data centralizes in your ERP.

You must design the data flow with idempotency and resilience. Every API call to Google must include idempotency keys to prevent duplicate product inserts or order creation from retry attempts. The Connector framework handles job retries for transient failures, but your code must manage hard failures, such as data validation errors from Google, by logging them for administrative review. This event-driven, queue-based pattern decouples the systems, preventing a slowdown in one platform from crippling the other.

Step-by-Step Configuration

Begin by creating a new Odoo module to host your integration logic. Define the module’s manifest file with dependencies on the connector, sale, and stock Odoo applications. Your module needs new models for storing Google Merchant account configurations and a model to extend the standard Odoo product template with Google-specific fields. Create a google_merchant_account model that stores credentials like merchant_id, auth_token, and the pubsub_topic name. This model serves as the central configuration point for connecting to each of your Merchant Center accounts.

Configure Google Cloud Project and API Access

Navigate to the Google Cloud Console and create a new project or select an existing one. Enable the "Shopping Content API" for this project. Under "APIs & Services > Credentials," create a new OAuth 2.0 Client ID. Configure the OAuth consent screen and set the application type to "Web application." You must add your Odoo server's URL to the authorized redirect URIs, typically `https://your-odoo-server.com/auth_oauth/signin`. Save the generated `client_id` and `client_secret`; you will need them for the Odoo configuration. Finally, create a Service Account and download its JSON key file for server-to-server authentication, which is more secure for background sync jobs.

Implement OAuth 2.0 Authentication in Odoo

Odoo’s auth_oauth module provides the foundation for OAuth flows. Extend it to support Google. Add a record to the ir.config_parameter model storing the client ID and secret. You must create a controller that handles the OAuth callback. This controller exchanges the authorization code for an access token and refresh token, which it then saves securely in the google_merchant_account record.

from odoo import http
from odoo.adds.auth_oauth.models.ir_http import OAuthLogin

class GoogleOAuthController(http.Controller):
    @http.route('/google_shopping/auth/callback', type='http', auth='public')
    def google_oauth_callback(self, **kw):
        code = kw.get('code')
        # Use the code to get tokens from Google
        access_token, refresh_token = self._get_google_tokens(code)
        # Store tokens in the configured account record
        account = http.request.env['google.merchant.account'].search([], limit=1)
        account.write({
            'access_token': access_token,
            'refresh_token': refresh_token,
        })
        return "Authentication Successful. You can close this window."

Set Up Product Sync Infrastructure

Define a "synchronizer" class using the Odoo Connector framework. This class encapsulates the logic for exporting a product to Google. Create a new backend model, `google.shopping.backend`, which links to your `google_merchant_account`. Then, create the synchronizer that inherits from `connector.backend`.
from odoo.adds.connector.components.mapper import mapping
from odoo.adds.connector.backend import backend

class GoogleProductSynchronizer(backend.Backend):
    _name = 'google.shopping.synchronizer'
    _backend_type = 'google_shopping'
    _model_name = 'product.product'

    @mapping
    def map_offer_id(self, record):
        # Map Odoo product ID to Google's offerId
        return {'offerId': record.default_code}

    @mapping
    def map_title_and_price(self, record):
        # Map name and price to Google's schema
        return {
            'title': record.name,
            'price': {
                'value': record.list_price,
                'currency': record.currency_id.name
            }
        }

Configure Order Import Webhook

In your Google Merchant Center, navigate to Settings > API Configuration. Provide your Odoo server’s webhook URL, which you will create in the next step. Odoo needs a controller to receive the Pub/Sub messages that Google sends when a new order arrives.

import json
from odoo import http

class GoogleShoppingOrderWebhook(http.Controller):
    @http.route('/google_shopping/order/notification', type='json', auth='public', methods=['POST'], csrf=False)
    def order_notification(self):
        request_data = json.loads(http.request.httprequest.data)
        # Verify the message authenticity (omitted for brevity)
        message_data = json.loads(base64.b64decode(request_data['message']['data']))
        order_id = message_data.get('orderId')
        # Create a connector job to import the order
        self._import_order_job.delay(order_id)
        return ''

Common Configuration Pitfalls

A frequent error involves incorrect timezone configuration. Google APIs require UTC timestamps, while Odoo uses the company’s timezone. Always convert datetime objects to UTC before sending them to Google. Another pitfall concerns the OAuth scope; you must request the full https://www.googleapis.com/auth/content scope for your integration to have the necessary permissions to manage products and orders. Failure to implement a robust token refresh mechanism will cause your syncs to fail silently once the initial access token expires. Your code must check for token expiration before each API call and automatically use the refresh token to obtain a new access token.

Data Mapping and Transformation

The core challenge of this integration lies in translating Odoo’s internal product data structure into the precise schema demanded by the Google Shopping Content API. Odoo’s product.product and product.template models contain your source data, but Google’s product resource has over fifty required and optional attributes. You must construct a meticulous mapping strategy that covers every field, from basic identifiers to complex tax and shipping information.

Core Identifier and Title Mapping

The offerId field in Google must map to a unique, stable identifier in Odoo. Never use the Odoo product id because it can change during database migrations. Use the product’s default_code (SKU) for the offerId. The product name in Odoo maps directly to Google’s title field. However, you must ensure the title meets Google’s character limits and does not contain promotional text like “free shipping.” The description field requires a similar transformation, often needing a sanitized version of the Odoo product description with HTML tags stripped out.

Price and Availability Mapping

Google's price structure is an object containing `value` and `currency`. Map the Odoo `list_price` to `price.value` and the company currency to `price.currency`. For availability, you must convert Odoo's stock state to Google's predefined values. Implement logic that checks the `qty_available` against the `sale_ok` and `available_threshold` fields.
@mapping
def map_availability(self, record):
    # Check if product is sellable and in stock
    if not record.sale_ok:
        return {'availability': 'out of stock'}
    qty_available = record.qty_available - record.outgoing_qty
    if qty_available > 0:
        return {'availability': 'in stock'}
    else:
        return {'availability': 'out of stock'}

Category and Attribute Handling

Google uses the Google Product Category (GPC) taxonomy, a numerical system completely different from Odoo’s product categories. You cannot map these automatically. You must extend your Odoo product category model to include a field for the corresponding GPC code. Your mapping function then reads this value. For product attributes like size or color, you map Odoo’s attribute values to Google’s size, color, and material fields. This requires a configuration table in Odoo that links Odoo attribute lines to the correct Google schema field.

The `image_link` field must point to a publicly accessible URL of the product's image. Odoo typically stores images as binary data. Your integration must generate a permanent, absolute URL for each product's primary image. You can achieve this by implementing a controller in Odoo that serves the images or, for better performance, by pushing the images to a CDN and storing the CDN URL in a dedicated field for the sync. Complex products require mapping for `shippingWeight` and `shippingLength` from Odoo's `product_dimension` fields, converting the units to the standard required by Google (e.g., weight in pounds).

Error Handling and Resilience

The integration will encounter errors. Your design must anticipate common failure modes and handle them gracefully without manual intervention. Categorize errors into transient network issues, data validation errors from Google, and authentication failures. The Odoo Connector framework provides a built-in retry mechanism for jobs, which you should configure for transient errors like 5xx HTTP status codes from Google or temporary network timeouts.

Common Google API Error Codes

The Google Content API returns specific error codes that dictate your response. A 401 Unauthorized error signals an expired or invalid access token. Your error handler must catch this, automatically use the refresh token to obtain new credentials, and retry the job. A 429 Too Many Requests error indicates rate limiting. Your code must catch this, parse the Retry-After header, and re-queue the job with the appropriate delay. For 400 Bad Request errors, the API response body contains a detailed reason, such as invalidValue for a field or missingAttribute.

Implementing a Dead Letter Queue

Not all errors can resolve with a retry. Data validation errors, like an invalid GPC code or a missing required field, will persist until you fix the underlying data in Odoo. For these, implement a dead letter queue. When a job fails permanently, log the error message, the product ID, and the failed operation to a dedicated google.sync.error model. This creates a clear dashboard for administrators to see and rectify data problems without sifting through server logs.

Idempotency and Duplicate Prevention

The Google APIs are idempotent when you provide a unique idempotencyKey with your requests. Generate this key for every insert or update operation. A good pattern combines the Odoo product ID, the timestamp of the last update, and the operation type. This ensures that if a network failure causes your code to retry a request, Google will recognize the duplicate and ignore it instead of creating two product entries.

def _generate_idempotency_key(self, record, operation):
    timestamp = record.write_date or record.create_date
    return f"{record.id}_{timestamp.timestamp()}_{operation}"

def _call_google_api(self, endpoint, data, operation):
    idempotency_key = self._generate_idempotency_key(data['record'], operation)
    headers = {
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotency_key
    }
    # ... make the API request ...

Order Import Error Scenarios

Order import failures can have immediate business impact. If the webhook fails to create an Odoo sale order, it must log a critical error and, if possible, send an alert to the sales team. Common issues include missing customer information in the Google order data or a product in the order that does not exist in Odoo. Your code must handle these partial failures, perhaps by creating a draft order that flags the missing information for manual completion.

Testing and Validation

Before deploying the integration to production, you must execute a rigorous testing protocol. Start with unit tests for your data mapping functions. Create test methods that feed known Odoo product records into your mappers and assert that the output matches the expected Google product JSON structure. Mock the Google API calls to test the synchronization logic without needing a live internet connection.

End-to-End Testing with a Sandbox

Google provides a Merchant Center sandbox environment for development and testing. Create a sandbox account and configure your Odoo test instance to connect to it. Execute a full product sync for a small subset of your catalog. Verify that the products appear in the sandbox Merchant Center dashboard with the correct titles, prices, and images. Then, simulate a test order through the Google Sandbox Orders API. Monitor your Odoo instance to confirm the sale order creates automatically with the correct line items and customer details.

Data Validation Checklist

Create a pre-flight checklist to run before the first full product sync. This checklist should verify that all required fields in Odoo have valid data for the Google mapping. Key validations include: every product has a `default_code` (SKU), every product category has a valid Google Product Category code, all product images are accessible via a public URL, and product weights and dimensions populate for items that require them. Automate these checks with a script that reports any products failing validation.

Performance and Load Testing

A integration that works for ten products might fail for ten thousand. Test the synchronization performance with a large volume of data. Use Odoo's built-in performance profiler to monitor the execution time of your connector jobs. Pay close attention to the number of SQL queries your mapping functions generate; an N+1 query problem can cripple performance. For high-volume stores, test the impact of simultaneous stock updates—if a flash sale updates the inventory of one hundred products at once, can your queue system handle the surge of one hundred sync jobs without slowing down the Odoo server?

Webhook and Order Flow Verification

Testing the order import flow presents a unique challenge because you cannot easily generate real Google Pub/Sub messages. Instead, create a utility function in Odoo that mimics the exact JSON payload structure that Google sends. Use this to trigger your webhook endpoint directly from the Odoo interface. Verify that the entire process—from receiving the notification to creating the confirmed sale order—completes without error. Test edge cases, such as an order for a product that has since been deactivated in Odoo.

Security Considerations

This integration handles sensitive business data, including product catalogs, pricing, and customer orders. You must secure every data transfer and storage point. All communication with Google APIs must use HTTPS with TLS 1.2 or higher. Never use HTTP for any endpoint, especially the webhook that receives order notifications.

OAuth 2.0 and Token Management

Store the OAuth `client_secret` and `refresh_token` in Odoo with the highest security. Use Odoo's `ir.config_parameter` model with its built-in encryption for sensitive fields. Never log these tokens to log files or display them in the user interface. Implement an automatic token refresh mechanism. Access tokens expire after a short period. Your code must detect a `401` error and use the stored refresh token to obtain a new access token without any administrator intervention.

Webhook Security and Validation

An open webhook endpoint presents a severe security risk. You must validate that every incoming webhook request originates from Google. Google signs the Pub/Sub messages it sends. Your webhook controller must verify this signature using the public keys provided by Google. Reject any request that fails signature verification. This prevents malicious actors from injecting fake orders into your system.

from google.auth import jwt

class GoogleShoppingOrderWebhook(http.Controller):
    def _verify_message(self, message):
        # Extract the JWT token from the 'Authorization' header
        token = http.request.httprequest.headers.get('Authorization').split(' ')[1]
        # Verify the token using Google's public certs
        decoded = jwt.decode(token, certs=GOOGLE_PUBLIC_CERTS)
        return decoded

Data Access and Permissions

Within Odoo, restrict access to the Google Merchant Account configuration models to only authorized users, such as system administrators. Apply Odoo’s record rules to ensure users can only see the sync logs and error reports relevant to their operational purview. The service account you use for server-to-server authentication should have the principle of least privilege, granted only the “Content Manager” role in Google Merchant Center, not owner-level permissions.

Performance Optimization

A poorly optimized integration can consume excessive server resources and slow down your entire Odoo instance. The primary bottleneck is usually the volume of API calls to Google, followed by database read/write operations in Odoo during the mapping process. Identify and mitigate these bottlenecks with a combination of batching, caching, and efficient querying.

API Call Batching and Quotas

The Google Shopping Content API supports batch operations. Instead of sending one HTTP request per product, you can bundle up to 1000 product operations in a single batch request. This reduces network overhead and speeds up the sync process dramatically. Implement a batch scheduler in your connector jobs that groups pending product updates into batches of the maximum allowed size. Monitor your API usage against Google’s quota limits to avoid being throttled. Spread large sync operations over time to stay within the per-minute quota.

Efficient Odoo ORM Querying

A common performance anti-pattern in Odoo is searching and reading records in a loop. When your mapper processes a batch of products, pre-fetch all the necessary related data in a single query. Use read_group for aggregate data and prefetch fields to minimize the number of database queries. For example, if you need stock quantities for a batch of products, query the stock quant model once for the entire batch instead of calling product.qty_available for each individual product.

Implementing a Caching Layer

Product data like category names or attribute values does not change often. Implement a simple caching layer for this semi-static data. Store the mapped values for Google Product Categories in a dictionary in memory, so you do not have to read the corresponding Odoo record for every single product. Use Odoo’s tools.cache decorator to cache the results of expensive functions that compute shipping dimensions or tax categories.

Queue Management and Job Chunking

The Odoo Connector queue can become overwhelmed if you create a separate job for every product update during a mass import. Instead, create chunked jobs. A single job should handle the synchronization of 100 or 500 products. This reduces the queue management overhead and allows for more efficient batch processing. Monitor your queue with the Odoo Job Queue manager and scale the number of job runners according to your sync load. For a high-traffic store, you might need to run multiple job runner processes to keep up with the update frequency.