StackMap.aiIntegration Hub
Back to integrations
E-Commerce & Accounting#Magento#Xero#Accounting#Automated Sync

How to Connect Magento to Xero Setup Blueprint

Verified Blueprint

This blueprint outlines a real-time, automated synchronization pipeline for pushing new Magento orders and customer data to Xero as invoices and contacts, ensuring financial records are always up-to-date.

Magento
Xero
Alternative Flow

Need an alternative to manual coding? Connect these apps in minutes via Make.com.

Try Make.com

How to Connect Magento to Xero (Automated Data Sync)

📊 Integration Overview This integration blueprint details a robust, event-driven pipeline for synchronizing new order data from Magento, an e-commerce platform, to Xero, an accounting software. The primary goal is to automate the creation of invoices and associated customer contacts in Xero whenever a new order is placed in Magento. The pipeline ensures data consistency, reduces manual data entry, and provides real-time visibility into sales for financial reporting. Data flows unidirectionally from Magento to Xero, triggered by specific e-commerce events.

Available integrations in directory: How to Connect Magento to QuickBooks, How to Connect Shopify to Xero, How to Connect WooCommerce to Xero, How to Connect Shopify to QuickBooks, How to Connect WooCommerce to QuickBooks, How to Connect Shopify to Wave Accounting, How to Connect WooCommerce to Wave Accounting.

🛠️ Core Connection Requirements Primary Key: order_id (Magento) mapped to Reference (Xero Invoice) Trigger Event: New Order Placed in Magento Action Event: Create/Update Invoice and Create/Update Contact in Xero

📋 The 5-Step Execution Blueprint

Step 1: Authentication & Scope Configuration Secure authentication to both Magento and Xero APIs is paramount. For Magento, an OAuth 1.0a or API Key (Token) setup with appropriate resource access is required. For Xero, OAuth 2.0 with specific scopes is necessary.

Magento Authentication: Requires generating a new integration within the Magento Admin panel (System > Integrations). This provides Consumer Key, Consumer Secret, Access Token, and Access Token Secret for OAuth 1.0a, or simply an API key/token for basic authentication depending on the setup. The integration must be granted Sales resource access.

Xero Authentication: Requires setting up a new Xero application in the Xero Developer portal. The application type should be "Web app" for standard OAuth 2.0 flow. Required scopes for this integration typically include:

  • accounting.contacts (for creating/updating customer records)
  • accounting.transactions (for creating invoices)
  • offline_access (for refreshing access tokens without user re-authentication)

Secure .env Setup:

# .env file for secure environment variables
MAGENTO_API_BASE_URL="https://your-magento-store.com/rest/V1"
MAGENTO_ACCESS_TOKEN="your_magento_admin_access_token" # Or OAuth 1.0a keys

XERO_CLIENT_ID="your_xero_client_id"
XERO_CLIENT_SECRET="your_xero_client_secret"
XERO_REDIRECT_URI="https://your-integration-service.com/xero/callback"
XERO_TENANT_ID="your_xero_organisation_id" # Obtained after OAuth authorization
XERO_REFRESH_TOKEN="your_xero_refresh_token" # Stored securely after initial authorization

Step 2: Webhook Trigger Setup To capture new orders in real-time, a webhook should be configured in Magento. While Magento's native webhook capabilities might require extensions or custom development, the core principle involves registering a callback URL that Magento will POST data to upon a sales_order_save_after or similar event.

Webhook Registration (Conceptual via Magento Extension/Custom Code): Register a webhook endpoint in Magento to listen for new order creation events. The endpoint URL would be https://your-integration-service.com/webhook/magento.

Webhook Receiver Implementation (TypeScript/Node.js): A robust webhook receiver must validate the incoming request to ensure its authenticity. While Magento's native webhooks might not always include cryptographic signatures, implementing signature validation is a best practice for security. This example assumes a shared secret and an X-Webhook-Signature header, which would need to be implemented on the Magento side (e.g., via a custom module) or an intermediary security layer.

import express from 'express';
import bodyParser from 'body-parser';
import crypto from 'crypto';

const app = express();
const WEBHOOK_SECRET = process.env.MAGENTO_WEBHOOK_SECRET || 'your_super_secret_key'; // This secret must be shared securely with Magento

// Raw body parser for signature validation
app.use(bodyParser.json({
  verify: (req: any, res, buf) => {
    req.rawBody = buf;
  }
}));

app.post('/webhook/magento', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const payload = req.rawBody.toString();

  if (!signature) {
    console.error('No signature found in webhook request');
    return res.status(401).send('Unauthorized: No signature');
  }

  // Example HMAC-SHA256 signature validation
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  hmac.update(payload);
  const digest = hmac.digest('hex');

  if (digest !== signature) {
    console.error('Webhook signature mismatch. Expected:', digest, 'Received:', signature);
    return res.status(401).send('Unauthorized: Invalid signature');
  }

  console.log('Webhook signature validated successfully!');
  const orderData = req.body; // Magento order object
  console.log('Received new Magento order:', orderData.order_id);

  // Process the order data (e.g., add to a queue for further processing)
  // enqueueOrderForXero(orderData);

  res.status(200).send('Webhook received and processed.');
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook listener running on port ${PORT}`);
});

Step 3: Payload Transformation & Mapping Upon receiving a new order webhook, the Magento order payload must be transformed and mapped to the Xero API's Contact and Invoice schemas. This involves extracting relevant customer and order line item details.

Input (Simplified Magento Order Payload):

{
  "event_type": "sales_order_save_after",
  "order": {
    "entity_id": 12345,
    "increment_id": "0000012345",
    "customer_id": 6789,
    "customer_email": "john.doe@example.com",
    "customer_firstname": "John",
    "customer_lastname": "Doe",
    "billing_address": {
      "firstname": "John",
      "lastname": "Doe",
      "street": ["123 Main St"],
      "city": "Anytown",
      "region": "CA",
      "postcode": "90210",
      "country_id": "US",
      "telephone": "555-123-4567"
    },
    "shipping_address": {
      "firstname": "John",
      "lastname": "Doe",
      "street": ["123 Main St"],
      "city": "Anytown",
      "region": "CA",
      "postcode": "90210",
      "country_id": "US",
      "telephone": "555-123-4567"
    },
    "items": [
      {
        "item_id": 101,
        "sku": "PROD-A",
        "name": "Widget Pro",
        "qty_ordered": 2,
        "price": 50.00,
        "base_row_total": 100.00,
        "base_tax_amount": 8.00,
        "row_total_incl_tax": 108.00
      },
      {
        "item_id": 102,
        "sku": "SERV-B",
        "name": "Installation Service",
        "qty_ordered": 1,
        "price": 25.00,
        "base_row_total": 25.00,
        "base_tax_amount": 2.00,
        "row_total_incl_tax": 27.00
      }
    ],
    "base_grand_total": 135.00,
    "base_tax_amount": 10.00,
    "base_shipping_amount": 5.00,
    "base_shipping_tax_amount": 0.50,
    "base_total_due": 135.00,
    "currency_id": "USD",
    "created_at": "2023-10-27 14:30:00"
  }
}

Output (Xero Contact and Invoice Payloads): The transformation logic would first attempt to find an existing contact in Xero by customer_email or Name. If not found, a new Contact is created. Then, an Invoice is created, linked to this Contact.

{
  "Contact": {
    "Name": "John Doe",
    "EmailAddress": "john.doe@example.com",
    "FirstName": "John",
    "LastName": "Doe",
    "Addresses": [
      {
        "AddressType": "STREET",
        "AddressLine1": "123 Main St",
        "City": "Anytown",
        "Region": "CA",
        "PostalCode": "90210",
        "Country": "US"
      }
    ],
    "Phones": [
      {
        "PhoneType": "DEFAULT",
        "PhoneNumber": "555-123-4567"
      }
    ]
  },
  "Invoice": {
    "Type": "ACCREC",
    "Contact": {
      "ContactID": "existing_or_new_xero_contact_id"
    },
    "Date": "2023-10-27",
    "DueDate": "2023-11-26",
    "Reference": "MAG-0000012345",
    "LineItems": [
      {
        "Description": "Widget Pro (PROD-A)",
        "Quantity": 2,
        "UnitAmount": 50.00,
        "AccountCode": "200", // Example Sales Account Code in Xero
        "TaxType": "OUTPUT",
        "LineAmount": 100.00
      },
      {
        "Description": "Installation Service (SERV-B)",
        "Quantity": 1,
        "UnitAmount": 25.00,
        "AccountCode": "200", // Example Sales Account Code
        "TaxType": "OUTPUT",
        "LineAmount": 25.00
      },
      {
        "Description": "Shipping",
        "Quantity": 1,
        "UnitAmount": 5.00,
        "AccountCode": "200", // Example Sales Account Code for Shipping
        "TaxType": "OUTPUT",
        "LineAmount": 5.00
      }
    ],
    "TotalTax": 10.50, // Sum of base_tax_amount and base_shipping_tax_amount
    "Total": 135.50, // base_grand_total + base_shipping_amount (if not included) - this might need adjustment based on Magento's grand total calculation
    "CurrencyCode": "USD",
    "Status": "AUTHORISED" // Or "DRAFT" if manual review is needed
  }
}

Step 4: Endpoint Despatch & Error Guarding API requests to Xero for creating contacts and invoices must handle various success and error states.

Sequence of Operations:

  1. Search for Contact: GET /Contacts?where=EmailAddress=="john.doe@example.com"
  2. Create/Update Contact: If contact not found, POST /Contacts. If found, retrieve ContactID.
  3. Create Invoice: POST /Invoices with the retrieved/created ContactID and mapped line items.

Error Handling Strategy:

  • HTTP 401 Unauthorized (Token Expiry):

    • Instruction: Xero OAuth 2.0 access tokens expire after 30 minutes. The offline_access scope allows for token refresh. Implement logic to catch 401 errors, use the stored XERO_REFRESH_TOKEN to request a new access token, update the stored token, and then retry the original API request.
    • Example (Conceptual):
      async function makeXeroApiRequest(url: string, method: string, data?: any): Promise<any> {
        let accessToken = await getXeroAccessToken(); // Retrieve current token, refresh if needed
        try {
          const response = await fetch(url, {
            method,
            headers: {
              'Authorization': `Bearer ${accessToken}`,
              'Xero-Tenant-Id': process.env.XERO_TENANT_ID!,
              'Content-Type': 'application/json'
            },
            body: data ? JSON.stringify(data) : undefined
          });
      
          if (response.status === 401) {
            console.warn('Xero access token expired. Refreshing token...');
            accessToken = await refreshXeroAccessToken(); // Function to refresh token
            // Retry with new token
            return makeXeroApiRequest(url, method, data);
          }
          if (!response.ok) {
            const errorBody = await response.json();
            throw new Error(`Xero API Error ${response.status}: ${JSON.stringify(errorBody)}`);
          }
          return response.json();
        } catch (error) {
          console.error('Failed to make Xero API request:', error);
          throw error;
        }
      }
      
  • HTTP 400 Bad Request (Validation Errors):

    • Instruction: Xero's API returns 400 for validation failures (e.g., missing required fields, invalid account codes). Log the detailed error message from the API response body, which often includes specific validation failures. For recoverable errors (e.g., unknown account code), map to a default or flag for manual review. For structural issues, alert developers.
    • Example: const errorDetails = await response.json(); console.error('Xero validation error:', errorDetails.Elements[0].ValidationErrors);
  • HTTP 429 Too Many Requests (Rate Limiting):

    • Instruction: Xero imposes rate limits. For high-volume scenarios, implement an asynchronous queueing system (e.g., using Redis with BullMQ/Kafka) to buffer requests. When a 429 is encountered, pause processing for a duration specified by the Retry-After header (if present) or apply an exponential backoff strategy before retrying the failed request.
    • Queueing Example (Conceptual with BullMQ):
      // worker.ts
      import { Queue, Worker } from 'bullmq';
      // ... (connection to Redis)
      const xeroInvoiceQueue = new Queue('xero-invoices', { connection: redisConnection });
      new Worker('xero-invoices', async (job) => {
        try {
          await makeXeroApiRequest('/api.xro/2.0/Invoices', 'POST', job.data.invoicePayload);
          console.log(`Invoice for order ${job.data.orderId} created in Xero.`);
        } catch (error: any) {
          if (error.message.includes('429')) {
            console.warn(`Rate limit hit for job ${job.id}. Retrying...`);
            throw new Error('RateLimitExceeded'); // BullMQ will handle retry logic
          }
          throw error; // Other errors will fail the job
        }
      }, { connection: redisConnection,
           // Optional: advanced retry strategies
           defaultJobOptions: {
             attempts: 5,
             backoff: {
               type: 'exponential',
               delay: 1000 // 1s, 2s, 4s, 8s, 16s
             }
           }
         });
      
      // producer.ts (from webhook handler)
      await xeroInvoiceQueue.add('createInvoice', { orderId: orderData.entity_id, invoicePayload: transformedInvoice });
      
  • HTTP 5xx Server Errors:

    • Instruction: These indicate transient issues on Xero's side. Implement retry logic with exponential backoff for 5xx errors. Alert monitoring systems if consecutive 5xx errors persist.
    • Example: Retry the request after a short delay, increasing delay on subsequent retries.

Step 5: Live Loop Validation Thorough testing in sandbox environments is critical before deploying to production.

  1. Magento Sandbox Setup: Configure a Magento development or staging environment with the webhook pointing to the integration service.
  2. Xero Demo Company: Utilize a Xero Demo Company (available in the Xero Developer portal) for all API interactions during development and testing. This ensures no real financial data is affected.
  3. End-to-End Test Workflow:
    • Place a new test order in Magento's sandbox environment.
    • Verify that the Magento webhook successfully triggers the integration service.
    • Monitor integration service logs for successful payload transformation and API dispatch to Xero.
    • Log in to the Xero Demo Company.
    • Navigate to Contacts and verify that a new contact for the test customer has been created or updated, matching the Magento customer details.
    • Navigate to Accounts > Sales > Invoices and verify that a new invoice corresponding to the Magento order (Reference field matching MAG-order_id) has been created.
    • Crucially, check the Line Items, Total, Tax, and Currency on the Xero invoice to ensure they precisely match the Magento order details without any truncation or calculation discrepancies.
  4. Edge Case Testing:
    • Test orders with various product types (simple, configurable).
    • Test orders with different tax rates and shipping costs.
    • Test with existing customers versus new customers.
    • Simulate high volume to test rate limit handling.
  5. Validation Queries: After a successful sync, perform a GET /Invoices?where=Reference=="MAG-0000012345" query on the Xero API to programmatically verify the invoice existence and its key details.

❓ Integration Frequently Asked Questions

Q: How does this pipeline handle duplicate data entries? A: To prevent duplicate contacts and invoices in Xero, the pipeline implements an idempotency strategy. Before creating a new Contact in Xero, the integration first queries Xero using GET /Contacts?where=EmailAddress=="customer@example.com". If a contact with the same email exists, its ContactID is retrieved and used for the invoice, preventing duplicate contact creation. Similarly, before creating an Invoice, the pipeline can query GET /Invoices?where=Reference=="MAG-order_id" to check if an invoice with the corresponding Magento order_id (mapped to Xero Reference) already exists. If found, the new invoice creation is skipped, or an update operation might be considered depending on business rules (e.g., for order status changes, though this blueprint focuses on initial creation). This ensures each Magento order maps to a unique invoice in Xero.

Q: What happens if the API rate limit is exceeded during high volume? A: In scenarios of high order volume that could lead to exceeding Xero's API rate limits, the integration employs a robust asynchronous queuing mechanism, typically using a message broker like Redis with BullMQ. When a new order webhook is received from Magento, instead of directly calling the Xero API, the order payload is pushed onto a queue. A separate set of worker processes then asynchronously picks up jobs from this queue. If a worker encounters a HTTP 429 Too Many Requests error from Xero, it won't block the entire system. Instead, the job is marked for retry with an exponential backoff strategy (e.g., retrying after 1s, then 2s, 4s, etc.). This isolates rate limit issues to individual jobs, allowing the system to naturally throttle itself and ensure eventual processing of all orders without data loss. Monitoring and alerting are also in place to notify operations teams if queues back up significantly.

Developer Infrastructure

Deploy custom integration scripts safely.

Get $200 free server credits on DigitalOcean to host webhook brokers, queues, and database engines.

Get $200 Free Credits

Integration Core Specs

Source Platform

Magento

Destination Platform

Xero

Primary Key Identifieremail
Pipeline SpeedSub-second Realtime

Production Guardrails

  • Automatic signature checks for HMAC SHA256 payloads.
  • Redis queue throttle buffers to prevent Intuit/HubSpot API caps.
  • Fallbacks for missing SKU or contact mappings.
  • Idempotent validation gates before REST ledger entry creation.