StackMap.aiIntegration Hub
Back to integrations
E-Commerce & Accounting#Shopify#WaveAccounting#Accounting#Automated Sync#E-commerce#Finance

How to Connect Shopify to WaveAccounting Setup Blueprint

Verified Blueprint

Establishes a real-time, automated data synchronization pipeline to transfer Shopify order and customer data to WaveAccounting for streamlined financial management and invoicing.

Shopify
WaveAccounting
Alternative Flow

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

Try Make.com

How to Connect Shopify to WaveAccounting (Automated Data Sync)

📊 Integration Overview This integration blueprint details a robust, real-time data synchronization pipeline connecting Shopify, a leading e-commerce platform, with WaveAccounting, a popular online accounting solution. The primary objective is to automate the creation of financial records (e.g., invoices, sales receipts, or customer profiles) in WaveAccounting whenever a new order is placed in Shopify. This eliminates manual data entry, reduces human error, and ensures that financial records are consistently up-to-date, providing a real-time view of business revenue and customer data.

The data flow commences with a webhook triggered by Shopify upon a new order event. This event payload is then received by a secure endpoint, transformed to match WaveAccounting's API schema, and subsequently dispatched to create the corresponding financial records. Error handling, idempotency, and rate limit management are critical components to ensure data integrity and system reliability, even under high transaction volumes.

For businesses utilizing similar platforms, consider exploring related integrations such as Shopify to FreshBooks or Shopify to QuickBooks for alternative accounting solutions. If your CRM is HubSpot, a Shopify to HubSpot sync can streamline customer management. Similarly, if Salesforce is your CRM, the Shopify to Salesforce integration provides a powerful sync. For other e-commerce platforms, a WooCommerce to Xero integration offers a comparable accounting sync. This integration also shares common categories with Shopify to Xero.

🛠️ Core Connection Requirements Primary Key: order_id (from Shopify, used for idempotency and reference in WaveAccounting) Trigger Event: orders/create (Shopify webhook) Action Event: Create Invoice and Create Customer (WaveAccounting API)

📋 The 5-Step Execution Blueprint

Step 1: Authentication & Scope Configuration Securely configure API credentials for both Shopify and WaveAccounting. This typically involves obtaining API keys/tokens and setting appropriate OAuth scopes to grant necessary permissions.

Shopify:

  • Admin API Access Token: Required for programmatic access to Shopify resources. Generated when installing a custom app or during OAuth for a public app.
  • Webhook Shared Secret: Used to cryptographically sign webhook payloads, enabling verification of their authenticity.

WaveAccounting:

  • OAuth 2.0 Credentials: Client ID, Client Secret, and the process to obtain an Access Token and Refresh Token. Wave uses OAuth 2.0 for API authentication.
  • Business ID: The unique identifier for the Wave business where data will be written.

Required Scopes:

  • Shopify: read_orders, read_customers (for richer data if needed beyond webhook payload)
  • WaveAccounting: invoice_read, invoice_write, customer_read, customer_write, business_read

Sample .env setup:

SHOPIFY_API_SECRET_KEY="shpss_********************************"
SHOPIFY_WEBHOOK_SECRET="whsec_********************************"

WAVE_CLIENT_ID="****************************************"
WAVE_CLIENT_SECRET="****************************************"
WAVE_REFRESH_TOKEN="****************************************"
WAVE_BUSINESS_ID="********************************" # Your specific Wave Business ID

Step 2: Webhook Trigger Setup Register a webhook in Shopify to listen for orders/create events. Your integration service will expose a publicly accessible HTTPS endpoint to receive these payloads. Crucially, this endpoint must validate the incoming webhook's authenticity using the shared secret.

Registering the Webhook (via Shopify Admin or API):

  • Event: Order creation
  • URL: https://your-integration-service.com/api/webhooks/shopify/order-created
  • API version: Latest stable version (e.g., 2023-10)

Webhook Endpoint Verification (TypeScript/JavaScript example):

import { createHmac } from 'crypto';
import express from 'express';
import bodyParser from 'body-parser';

const app = express();
const SHOPIFY_WEBHOOK_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET!;

app.post('/api/webhooks/shopify/order-created', bodyParser.raw({ type: 'application/json' }), (req, res) => {
    const hmacHeader = req.get('X-Shopify-Hmac-Sha256');
    const body = req.body.toString('utf8'); // raw body as string for HMAC

    const generatedHmac = createHmac('sha256', SHOPIFY_WEBHOOK_SECRET)
        .update(body, 'utf8')
        .digest('base64');

    if (generatedHmac !== hmacHeader) {
        console.error('Webhook signature validation failed!');
        return res.status(401).send('Unauthorized: Invalid HMAC signature.');
    }

    const orderData = JSON.parse(body);
    console.log('Shopify Webhook received:', orderData.id);

    // Process orderData asynchronously (e.g., push to a queue)
    processOrderInWave(orderData)
        .then(() => res.status(200).send('Webhook received and processing started.'))
        .catch(error => {
            console.error('Error processing order:', error);
            res.status(500).send('Internal Server Error.');
        });
});

async function processOrderInWave(order: any) {
    // This function will contain logic for Step 3 and 4
    console.log(`Processing Shopify Order ${order.id} for WaveAccounting...`);
    // ...
}

app.listen(3000, () => console.log('Webhook receiver listening on port 3000.'));

Step 3: Payload Transformation & Mapping The received Shopify order object must be transformed into the appropriate JSON structure required by WaveAccounting's API for creating customers and invoices. This involves mapping Shopify fields to their Wave equivalents.

Sample Shopify order input (simplified):

{
  "id": 1234567890,
  "email": "customer@example.com",
  "name": "#1001",
  "customer": {
    "id": 987654321,
    "first_name": "John",
    "last_name": "Doe",
    "email": "customer@example.com",
    "default_address": {
      "address1": "123 Commerce St",
      "city": "Shopville",
      "province": "CA",
      "zip": "90210",
      "country": "United States"
    }
  },
  "line_items": [
    {
      "id": 11111,
      "name": "Product A",
      "quantity": 2,
      "price": "10.00",
      "sku": "PA-001"
    },
    {
      "id": 22222,
      "name": "Product B",
      "quantity": 1,
      "price": "25.00",
      "sku": "PB-002"
    }
  ],
  "current_total_price": "45.00",
  "currency": "USD",
  "processed_at": "2023-10-26T10:00:00-04:00"
}

Transformed WaveAccounting invoiceCreate payload (simplified, assuming customer is pre-existing or created): First, check for or create customer. WaveAccounting customerCreate payload:

{
  "input": {
    "businessId": "WAVE_BUSINESS_ID",
    "firstName": "John",
    "lastName": "Doe",
    "email": "customer@example.com",
    "address": {
      "addressLine1": "123 Commerce St",
      "city": "Shopville",
      "provinceCode": "CA",
      "postalCode": "90210",
      "countryCode": "US"
    },
    "externalId": "SHOPIFY_CUSTOMER_ID_987654321" // Store Shopify Customer ID for idempotency
  }
}

Then, create the invoice. WaveAccounting invoiceCreate payload (using GraphQL mutation, common for Wave):

{
  "query": "mutation InvoiceCreate($input: InvoiceCreateInput!) { invoiceCreate(input: $input) { didSucceed invoice { id } errors { message code } } }",
  "variables": {
    "input": {
      "businessId": "WAVE_BUSINESS_ID",
      "customerId": "WAVE_CUSTOMER_ID_GENERATED_OR_FOUND",
      "title": "Invoice for Shopify Order #1001",
      "invoiceNumber": "SHOPIFY-1234567890", # Use Shopify order ID as invoice reference
      "invoiceDate": "2023-10-26",
      "dueDate": "2023-11-26", # Example: 30 days out
      "currency": {
        "code": "USD"
      },
      "items": [
        {
          "productId": null, # Can link to existing products in Wave if desired
          "description": "Product A",
          "quantity": 2,
          "unitPrice": 10.00,
          "taxPercent": 0.0, # Adjust for actual tax calculation
          "subtotal": 20.00
        },
        {
          "productId": null,
          "description": "Product B",
          "quantity": 1,
          "unitPrice": 25.00,
          "taxPercent": 0.0,
          "subtotal": 25.00
        }
      ],
      "memo": "Shopify Order ID: 1234567890",
      "externalId": "SHOPIFY_ORDER_ID_1234567890" # Crucial for idempotency
    }
  }
}

Step 4: Endpoint Despatch & Error Guarding Once the payload is transformed, despatch API requests to WaveAccounting. Implement robust error handling strategies to ensure resilience.

API Request Despatch (Conceptual):

import axios from 'axios';
import { getWaveAccessToken, refreshWaveAccessToken } from './waveAuthService'; // Custom service for token management

const WAVE_API_BASE_URL = 'https://api.waveapps.com/graphql/v1'; // Wave GraphQL endpoint
const WAVE_BUSINESS_ID = process.env.WAVE_BUSINESS_ID!;

async function createWaveCustomerAndInvoice(shopifyOrder: any) {
    let accessToken = await getWaveAccessToken();

    try {
        // --- 1. Check for existing customer or create new ---
        let customerId = await findOrCreateWaveCustomer(accessToken, shopifyOrder.customer, shopifyOrder.email);

        // --- 2. Check for existing invoice by externalId (Shopify Order ID) ---
        const existingInvoice = await findWaveInvoiceByExternalId(accessToken, `SHOPIFY_ORDER_ID_${shopifyOrder.id}`);
        if (existingInvoice) {
            console.log(`Invoice for Shopify Order ${shopifyOrder.id} already exists (ID: ${existingInvoice.id}). Skipping creation.`);
            return existingInvoice.id;
        }

        // --- 3. Construct Invoice Payload ---
        const invoicePayload = {
            query: `mutation InvoiceCreate($input: InvoiceCreateInput!) { invoiceCreate(input: $input) { didSucceed invoice { id } errors { message code } } }`,
            variables: {
                input: {
                    businessId: WAVE_BUSINESS_ID,
                    customerId: customerId,
                    title: `Invoice for Shopify Order #${shopifyOrder.name}`,
                    invoiceNumber: `SHOPIFY-${shopifyOrder.id}`,
                    invoiceDate: shopifyOrder.processed_at.substring(0, 10),
                    dueDate: new Date(new Date(shopifyOrder.processed_at).setDate(new Date(shopifyOrder.processed_at).getDate() + 30)).toISOString().substring(0, 10),
                    currency: { code: shopifyOrder.currency },
                    items: shopifyOrder.line_items.map((item: any) => ({
                        description: item.name,
                        quantity: item.quantity,
                        unitPrice: parseFloat(item.price),
                        taxPercent: 0.0 // Ensure accurate tax calculation based on your business logic
                    })),
                    memo: `Shopify Order ID: ${shopifyOrder.id}`,
                    externalId: `SHOPIFY_ORDER_ID_${shopifyOrder.id}` // Use this for idempotency
                }
            }
        };

        // --- 4. Despatch Invoice Request ---
        const response = await axios.post(WAVE_API_BASE_URL, invoicePayload, {
            headers: {
                'Authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            }
        });

        if (response.data.errors || !response.data.data.invoiceCreate.didSucceed) {
            throw new Error(`Wave API Error: ${JSON.stringify(response.data.errors || response.data.data.invoiceCreate.errors)}`);
        }
        console.log(`Successfully created Wave Invoice ID: ${response.data.data.invoiceCreate.invoice.id} for Shopify Order ${shopifyOrder.id}`);
        return response.data.data.invoiceCreate.invoice.id;

    } catch (error: any) {
        // Error Guarding
        if (error.response) {
            switch (error.response.status) {
                case 401: // Unauthorized - Access token expired or invalid
                    console.warn('Wave API 401 Unauthorized. Attempting to refresh token...');
                    accessToken = await refreshWaveAccessToken(); // Implement token refresh logic
                    // Retry the request with the new token (consider a single retry for 401)
                    return createWaveCustomerAndInvoice(shopifyOrder); // Recursive retry
                case 400: // Bad Request - Invalid payload
                    console.error('Wave API 400 Bad Request:', error.response.data);
                    // Log detailed error, notify ops, potentially halt processing for this specific order
                    throw new Error(`Bad Request to Wave API for Shopify Order ${shopifyOrder.id}: ${JSON.stringify(error.response.data)}`);
                case 429: // Rate Limiting - Too Many Requests
                    console.warn('Wave API 429 Rate Limited. Pushing to queue and applying backoff.');
                    // Push event back to an asynchronous queue (e.g., Redis/BullMQ)
                    // Implement exponential backoff for retries to avoid further rate limits
                    await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryAttempt) * 1000 + Math.random() * 1000)); // Exponential backoff
                    throw new Error(`Rate limited by Wave API for Shopify Order ${shopifyOrder.id}. Will retry.`); // Re-throw to be caught by queue handler
                case 500: // Internal Server Error
                case 502:
                case 503:
                case 504:
                    console.error('Wave API 5xx Server Error:', error.response.status, error.response.data);
                    // Implement retry with exponential backoff. If persistent, log and alert.
                    await new Promise(resolve => setTimeout(resolve, Math.pow(2, retryAttempt) * 1000 + Math.random() * 1000));
                    throw new Error(`Wave API Server Error for Shopify Order ${shopifyOrder.id}. Will retry.`);
                default:
                    console.error('Unhandled Wave API Error:', error.response.status, error.response.data);
                    throw error;
            }
        } else if (error.request) {
            console.error('No response received from Wave API:', error.request);
            // Network error, no response from server. Retry logic could apply.
            throw new Error(`Network error contacting Wave API for Shopify Order ${shopifyOrder.id}.`);
        } else {
            console.error('Error in setting up request to Wave API:', error.message);
            throw error;
        }
    }
}

Step 5: Live Loop Validation Thoroughly test the integration in sandbox or test environments to confirm successful data flow and integrity.

  1. Sandbox Setup: Ensure both Shopify and WaveAccounting have dedicated sandbox or test accounts. WaveAccounting allows creating multiple businesses, one of which can be designated for testing.
  2. Trigger Test Orders: In your Shopify test store, create several test orders with varying line items, customer details, and total amounts. Include edge cases like zero-value orders or orders with multiple items.
  3. Monitor Webhook: Observe your integration service's logs to confirm the Shopify webhook is correctly received, validated, and processed.
  4. Verify WaveAccounting Records:
    • Log into your WaveAccounting test business.
    • Navigate to the "Sales" -> "Customers" section to verify that new customer profiles have been created or existing ones updated with details from Shopify orders. Pay attention to externalId for mapping.
    • Go to "Sales" -> "Invoices" and check for the newly created invoices.
    • Validation Queries:
      • Confirm the invoiceNumber (which contains SHOPIFY-ORDER-ID) matches the original Shopify order ID.
      • Verify the customerId on the invoice correctly links to the associated Wave customer.
      • Check that totalAmount, currency, invoiceDate, and lineItems details (description, quantity, unit price) accurately reflect the Shopify order data without truncation or rounding errors.
      • Ensure the memo field contains the Shopify Order ID.
      • Perform a quick search for externalId if Wave allows it, to confirm it's unique and present.
  5. Error Log Review: Scrutinize all integration service logs for any errors (e.g., 400s, 429s, 5xxs). Verify that retry mechanisms for transient errors (429, 5xx) are functioning as expected and that critical errors (400) are properly logged and escalated.
  6. Idempotency Check: Trigger the same Shopify order multiple times (if possible, by re-enacting or simulating) and verify that it does not create duplicate invoices or customers in WaveAccounting. Only the first creation should succeed, or subsequent attempts should update or be skipped due to the externalId check.

❓ Integration Frequently Asked Questions

Q: How does this pipeline handle duplicate data entries? A: Duplicate data entries are prevented through an idempotency mechanism. Before creating a new customer or invoice in WaveAccounting, the integration first performs a GET request or a search query to check for the existence of a corresponding record. For customers, this is typically done by searching for the customer's email address. For invoices, a unique identifier from Shopify (e.g., Shopify Order ID) is stored in a custom field on the Wave invoice, such as the externalId field. When a new Shopify order event arrives, the system first queries WaveAccounting to see if an invoice with that specific externalId already exists. If found, the event is considered a duplicate or already processed, and the creation of a new invoice is skipped. This ensures that each unique Shopify order results in only one corresponding invoice in WaveAccounting, preventing data pollution and maintaining data integrity.

Q: What happens if the API rate limit is exceeded during high volume? A: To manage high transaction volumes and mitigate the impact of API rate limits, this integration implements an asynchronous processing architecture with a robust queuing system, typically built on Redis (e.g., using a library like BullMQ). When a Shopify webhook event is received, instead of directly calling the WaveAccounting API, the event payload is immediately pushed onto a message queue. A pool of worker processes then consumes these events from the queue at a controlled, throttled rate that respects WaveAccounting's API limits. If a worker receives a 429 Too Many Requests error from the Wave API, it will place the failed job back into the queue with an exponential backoff strategy. This means the job will be retried after an increasing delay (e.g., 1 second, then 2, then 4, etc.) to give the API time to recover. Additionally, a circuit breaker pattern can be implemented to temporarily stop sending requests if the API consistently returns errors, preventing further resource waste and allowing the upstream system to stabilize before retries resume. This design ensures that all events are eventually processed without loss, even during peak loads or temporary API outages, by cushioning bursts and gracefully handling transient failures.

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

Shopify

Destination Platform

WaveAccounting

Primary Key Identifiershopify_order_id
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.