🚀 Executive Summary

TL;DR: A third-party plugin for syncing WooCommerce products to a Facebook Catalog failed after a WooCommerce update, leading to manual reconciliation. The solution involved building a direct API-to-API Python script to fetch, transform, and upload product data, providing full control and robust logging.

🎯 Key Takeaways

  • Utilize a Facebook System User Access Token with `catalog_management` permission for long-lived, server-to-server API interactions, ensuring stability for automated syncs.
  • Implement pagination when fetching WooCommerce products (e.g., in batches of 100) to prevent API timeouts or rate limiting, especially for large inventories.
  • Prioritize using the WooCommerce SKU as the primary `id` for Facebook Catalog items; if unavailable, fall back to the product ID to prevent duplicate entries and ensure consistent product identification.
  • Leverage Facebook’s `/batch` endpoint for efficient bulk product uploads, allowing up to 5,000 operations (create, update, delete) in a single API call, using the ‘UPDATE’ method for idempotency.
  • Securely manage API credentials and sensitive information using environment variables (e.g., `python-dotenv` with a `config.env` file) to keep them out of version control and enhance security.

Syncing WooCommerce Products to Facebook Catalog via API

Syncing WooCommerce Products to Facebook Catalog via API

Hey there, Darian Vance here. As the Senior DevOps Engineer at TechResolve, I’ve seen my fair share of “duct tape” solutions for e-commerce integrations. For a long time, syncing our WooCommerce store to our Facebook Catalog was managed by a third-party plugin. It worked, until it didn’t. After a WooCommerce update, the sync started failing silently. I used to spend a couple of hours every week manually cross-referencing product lists and checking error logs. It was a time sink. That’s when I decided to cut out the middleman and build a direct API-to-API sync script. It gave us full control, robust logging, and saved me a ton of headaches. Today, I’m going to walk you through how I did it, so you can reclaim that time, too.

Prerequisites

Before we dive in, make sure you have the following ready to go. This isn’t a beginner’s guide, so I’ll assume you know how to get these.

  • A live WooCommerce store with REST API access enabled (you’ll need a Consumer Key and Consumer Secret).
  • A Facebook Business Manager account with an existing, empty Catalog.
  • Your Facebook Catalog ID.
  • A Facebook System User Access Token. It’s crucial this token has the catalog_management permission. In my production setups, I always use a System User token as they are long-lived and designed for this kind of server-to-server work.
  • Python 3 installed on your machine or server.

The Guide: Building the Sync Script

Alright, let’s get to the code. I’ll skip the standard project setup steps like creating a directory and a virtual environment; you likely have your own workflow for that. The key is to have a clean space to work in. You’ll need to install two main Python libraries for this: requests for making HTTP calls and python-dotenv for managing our secrets. You can do that with your preferred package manager.

Step 1: Securely Store Your Credentials

First rule of DevOps: never hardcode secrets. We’ll create a file named config.env in our project directory to hold all our sensitive information. This keeps them out of version control and makes them easy to manage.

Your config.env file should look something like this:

WC_STORE_URL="https://your-store.com"
WC_CONSUMER_KEY="ck_yourkey"
WC_CONSUMER_SECRET="cs_yoursecret"
FB_ACCESS_TOKEN="your_long_lived_access_token"
FB_CATALOG_ID="your_catalog_id"

Step 2: Fetching Products from WooCommerce

Our first task is to pull the product data from WooCommerce. The API is pretty solid for this. We’ll make a GET request to the `/wp-json/wc/v3/products` endpoint. A key thing to remember is pagination. You can’t just ask for all products at once if you have a large inventory; the API will time out or rate-limit you. I always fetch them in batches of 50 or 100.

Here’s the Python logic for that:

import os
import requests
from dotenv import load_dotenv

load_dotenv('config.env')

WC_STORE_URL = os.getenv('WC_STORE_URL')
WC_CONSUMER_KEY = os.getenv('WC_CONSUMER_KEY')
WC_CONSUMER_SECRET = os.getenv('WC_CONSUMER_SECRET')

def fetch_woocommerce_products():
    print("Fetching products from WooCommerce...")
    products = []
    page = 1
    per_page = 100

    while True:
        api_url = f"{WC_STORE_URL}/wp-json/wc/v3/products"
        params = {
            "per_page": per_page,
            "page": page,
            "status": "publish" # We only want active products
        }
        
        response = requests.get(
            api_url,
            auth=(WC_CONSUMER_KEY, WC_CONSUMER_SECRET),
            params=params
        )
        
        if response.status_code != 200:
            print(f"Error fetching products: {response.text}")
            break

        batch = response.json()
        if not batch:
            break # No more products to fetch

        products.extend(batch)
        print(f"Fetched page {page} with {len(batch)} products.")
        page += 1

    print(f"Total products fetched: {len(products)}")
    return products

Step 3: Transforming Data for the Facebook Catalog

WooCommerce and Facebook have different ideas about how product data should be structured. Our job is to be the translator. The Facebook Graph API’s batch endpoint expects a list of objects, where each object represents a product and contains specific fields. We’ll map the WooCommerce data to the required Facebook fields like id, title, price, and so on.

Pro Tip: This transformation step is where you can add a lot of value. For instance, my production script checks if a product is a variable product. If it is, it creates separate catalog entries for each variation. It also includes logic to strip HTML from descriptions and ensure the `availability` is correctly set to ‘in stock’ or ‘out of stock’ based on WooCommerce’s `stock_status`.

Here’s a simple transformation function:

def transform_for_facebook(products):
    print("Transforming products for Facebook format...")
    fb_products = []
    for product in products:
        # Skip products without a price or image, as Facebook will reject them
        if not product.get('price') or not product.get('images'):
            continue
            
        fb_product = {
            "method": "UPDATE",
            "data": {
                "id": str(product['sku']) if product['sku'] else str(product['id']), # Use SKU if available, otherwise ID
                "title": product['name'],
                "description": product['short_description'] if product['short_description'] else product['description'],
                "availability": "in stock" if product['in_stock'] else "out of stock",
                "condition": "new",
                "price": f"{product['price']} USD", # Important: Add currency code
                "link": product['permalink'],
                "image_link": product['images'][0]['src'],
                "brand": "Your Brand Name" # You can hardcode this or pull from a custom field
            }
        }
        fb_products.append(fb_product)
        
    print(f"Transformed {len(fb_products)} products.")
    return fb_products

Step 4: Uploading to the Facebook Catalog

With our data correctly formatted, we can now send it to Facebook. We’ll use the `/batch` endpoint, which is incredibly efficient. It allows us to send up to 5,000 product operations (create, update, delete) in a single API call. We’re using the “UPDATE” method here, which is idempotent: it will create the product if it doesn’t exist (based on the `id` field) or update it if it does.

FB_ACCESS_TOKEN = os.getenv('FB_ACCESS_TOKEN')
FB_CATALOG_ID = os.getenv('FB_CATALOG_ID')

def upload_to_facebook(fb_products):
    if not fb_products:
        print("No products to upload.")
        return

    print("Uploading products to Facebook Catalog...")
    api_url = f"https://graph.facebook.com/v18.0/{FB_CATALOG_ID}/batch"
    
    # Facebook API has a limit of 5000 items per batch request
    # You might need to chunk `fb_products` if you have more than that.
    payload = {
        'access_token': FB_ACCESS_TOKEN,
        'requests': fb_products
    }

    response = requests.post(api_url, json=payload)

    if response.status_code == 200:
        print("Successfully sent batch request to Facebook.")
        print(f"Response: {response.json()}")
    else:
        print("Failed to upload products to Facebook.")
        print(f"Status Code: {response.status_code}")
        print(f"Error: {response.text}")

def main():
    wc_products = fetch_woocommerce_products()
    if wc_products:
        fb_formatted_products = transform_for_facebook(wc_products)
        upload_to_facebook(fb_formatted_products)

if __name__ == "__main__":
    main()

Step 5: Automating the Sync

A script is only useful if it runs automatically. I use a simple cron job for this on our Linux servers. You can set it to run as often as you need—daily is usually a good starting point.

Here’s a sample cron entry to run the script every day at 2 AM:

0 2 * * * python3 script.py

Just make sure the command is executed from the correct directory so it can find the `config.env` file and the script itself. No need for `crontab -e` or other shell commands here, you know how to set up your own schedulers.

Common Pitfalls (Where I Usually Mess Up)

  • SKUs are King: I learned this the hard way. Always use the SKU as the primary `id` for Facebook. If you use the WooCommerce product ID and it ever changes (e.g., during a site migration), you’ll end up with duplicate products in your catalog. If a product has no SKU, fall back to the product ID, but make it a consistent rule.
  • Rate Limiting: On very large catalogs, you can hit API rate limits on either WooCommerce or Facebook. The pagination helps with WooCommerce, but for Facebook, you might need to break your product list into smaller chunks and send them in separate batch requests with a small delay in between.
  • Invalid Data Rejections: A single product with a malformed price (e.g., missing currency) or a broken `image_link` can be rejected. The batch request itself might succeed, but you’ll need to check the Facebook Commerce Manager for individual item errors. Good logging in your script is essential to track down which product caused the issue.

Conclusion

And that’s the core of it. By connecting the APIs directly, you’ve built a reliable, controllable, and transparent sync process that isn’t dependent on a third-party plugin’s update cycle. This script is a solid foundation. From here, you can expand it with more robust error handling, notifications for failed runs, and support for more complex product types. You’re no longer in the dark—you own the entire workflow. Happy automating!

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ What are the essential prerequisites for setting up a direct WooCommerce to Facebook Catalog API sync?

You need a live WooCommerce store with REST API access (Consumer Key/Secret), a Facebook Business Manager account with an empty Catalog, your Facebook Catalog ID, a Facebook System User Access Token with `catalog_management` permission, and Python 3.

âť“ How does a direct API sync compare to third-party WooCommerce plugins for Facebook Catalog integration?

A direct API sync offers full control, robust logging, and independence from plugin update cycles, unlike third-party plugins which can fail silently after WooCommerce updates and necessitate manual reconciliation.

âť“ What are common pitfalls when implementing a direct WooCommerce to Facebook Catalog API sync?

Common pitfalls include not using SKUs consistently as primary identifiers, hitting API rate limits on large catalogs, and invalid data rejections (e.g., malformed prices or broken image links) which require careful logging and error checking.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading