Connecting WooCommerce to 3PLGuys
Automate your WooCommerce store's fulfillment by routing orders to your 3PLGuys warehouse. This guide covers receiving WooCommerce order webhooks, creating Pick & Pack shipments, syncing inventory, and updating order status.
Prerequisites
You need a 3PLGuys OAuth application with shipments and inventory scopes, plus WooCommerce REST API keys (Consumer Key and Consumer Secret) from WooCommerce → Settings → Advanced → REST API.
Architecture Overview
Step 1: Set Up WooCommerce Webhooks
In WooCommerce admin, go to Settings → Advanced → Webhooks and create a webhook:
- Topic: Order created
- Delivery URL:
https://your-server.com/webhooks/woocommerce/orders - Secret: A random string for signature verification
Step 2: Handle Order Webhooks
import express from "express";import crypto from "crypto";const app = express();app.use(express.raw({ type: "application/json" }));const WC_SECRET = process.env.WC_WEBHOOK_SECRET;app.post("/webhooks/woocommerce/orders", async (req, res) => {// Verify WooCommerce signatureconst signature = crypto.createHmac("sha256", WC_SECRET).update(req.body).digest("base64");if (signature !== req.headers["x-wc-webhook-signature"]) {return res.status(401).send("Invalid signature");}const order = JSON.parse(req.body);res.status(200).send("OK");// Only process orders that need fulfillmentif (order.status === "processing") {await fulfillWooOrder(order);}});
<?php$payload = file_get_contents('php://input');$signature = hash_hmac('sha256', $payload, WC_WEBHOOK_SECRET, true);$expected = base64_encode($signature);if ($expected !== $_SERVER['HTTP_X_WC_WEBHOOK_SIGNATURE']) {http_response_code(401);exit('Invalid signature');}$order = json_decode($payload, true);http_response_code(200);if ($order['status'] === 'processing') {fulfillWooOrder($order);}
Step 3: Create Pick & Pack Shipment
Map WooCommerce order data to a 3PLGuys shipment:
const TPL_BASE = "https://api.3plguys.com/v0";const tplAuth = { Authorization: `Bearer ${tplToken}` };async function fulfillWooOrder(wcOrder) {const shipping = wcOrder.shipping;// 1. Create draftconst draft = await fetch(`${TPL_BASE}/shipments/pnp`, {method: "POST",headers: { ...tplAuth, "Content-Type": "application/json" },body: JSON.stringify({ warehouseId: "1" }),}).then(r => r.json());// 2. Set shipping addressawait fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/shipping-address`, {method: "PUT",headers: { ...tplAuth, "Content-Type": "application/json" },body: JSON.stringify({name: `${shipping.first_name} ${shipping.last_name}`,company: shipping.company || undefined,address1: shipping.address_1,address2: shipping.address_2 || undefined,city: shipping.city,state: shipping.state,zip: shipping.postcode,phone: wcOrder.billing.phone || undefined,}),});// 3. Map WooCommerce line items to 3PLGuys productsconst items = wcOrder.line_items.map(item => ({productId: skuMap[item.sku],quantity: item.quantity,}));await fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/items`, {method: "PUT",headers: { ...tplAuth, "Content-Type": "application/json" },body: JSON.stringify({ items }),});// 4. Add WooCommerce order referenceawait fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/notes`, {method: "PUT",headers: { ...tplAuth, "Content-Type": "application/json" },body: JSON.stringify({notes: `WooCommerce Order #${wcOrder.number}`,}),});// 5. Submitconst submitted = await fetch(`${TPL_BASE}/shipments/pnp/${draft.id}/submit`,{ method: "POST", headers: tplAuth }).then(r => r.json());await saveOrderMapping(wcOrder.id, submitted.id);return submitted;}
<?phpfunction fulfillWooOrder($wcOrder) {$shipping = $wcOrder['shipping'];$headers = ['Authorization: Bearer ' . TPL_TOKEN,'Content-Type: application/json',];// 1. Create draft$draft = tplRequest('POST', '/shipments/pnp', ['warehouseId' => '1']);// 2. Set shipping addresstplRequest('PUT', "/shipments/pnp/{$draft['id']}/shipping-address", ['name' => "{$shipping['first_name']} {$shipping['last_name']}",'address1' => $shipping['address_1'],'city' => $shipping['city'],'state' => $shipping['state'],'zip' => $shipping['postcode'],]);// 3. Map line items$items = array_map(fn($item) => ['productId' => $skuMap[$item['sku']],'quantity' => $item['quantity'],], $wcOrder['line_items']);tplRequest('PUT', "/shipments/pnp/{$draft['id']}/items", ['items' => $items]);// 4. Submitreturn tplRequest('POST', "/shipments/pnp/{$draft['id']}/submit");}function tplRequest($method, $path, $body = null) {$ch = curl_init('https://api.3plguys.com/v0' . $path);curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . TPL_TOKEN,'Content-Type: application/json',]);if ($body) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));$response = curl_exec($ch);curl_close($ch);return json_decode($response, true);}
Step 4: Sync Inventory
Push 3PLGuys stock levels to WooCommerce to keep product availability accurate:
async function syncInventoryToWoo() {const levels = await fetch(`${TPL_BASE}/inventory/products/breakdown?take=50`,{ headers: tplAuth }).then(r => r.json());// WooCommerce batch update (up to 100 per request)const updates = levels.filter(p => productToWooMap[p.id]).map(p => ({id: productToWooMap[p.id],stock_quantity: p.quantity,manage_stock: true,}));// Batch update in chunks of 100for (let i = 0; i < updates.length; i += 100) {const batch = updates.slice(i, i + 100);await fetch(`https://your-store.com/wp-json/wc/v3/products/batch`,{method: "POST",headers: {Authorization: `Basic ${btoa(`${WC_KEY}:${WC_SECRET}`)}`,"Content-Type": "application/json",},body: JSON.stringify({ update: batch }),});}}// Run every 15 minutessetInterval(syncInventoryToWoo, 15 * 60 * 1000);
Step 5: Update Order Status
Poll for fulfilled shipments and mark WooCommerce orders as complete:
async function checkFulfillment() {const pending = await getPendingOrderMappings();for (const { wooOrderId, tplShipmentId } of pending) {const shipment = await fetch(`${TPL_BASE}/shipments/${tplShipmentId}`,{ headers: tplAuth }).then(r => r.json());if (shipment.status === "forwarded") {await fetch(`https://your-store.com/wp-json/wc/v3/orders/${wooOrderId}`,{method: "PUT",headers: {Authorization: `Basic ${btoa(`${WC_KEY}:${WC_SECRET}`)}`,"Content-Type": "application/json",},body: JSON.stringify({ status: "completed" }),});await markOrderComplete(wooOrderId);}}}setInterval(checkFulfillment, 5 * 60 * 1000);
WooCommerce Plugin Approach
For WordPress-native solutions, you can build a custom plugin instead of a standalone server:
<?php/*** Plugin Name: 3PLGuys Fulfillment* Description: Automatic order fulfillment via 3PLGuys API*/add_action('woocommerce_order_status_processing', 'tplguys_fulfill_order');function tplguys_fulfill_order($order_id) {$order = wc_get_order($order_id);$shipping = $order->get_address('shipping');// Create and submit 3PLGuys Pick & Pack shipment// ... (same API calls as above)$order->add_order_note('3PLGuys shipment created: #' . $submitted['id']);update_post_meta($order_id, '_tplguys_shipment_id', $submitted['id']);}// Cron job for checking fulfillment statusadd_action('tplguys_check_fulfillment', 'tplguys_poll_shipments');function tplguys_poll_shipments() {// Query orders with _tplguys_shipment_id meta// Check status via API, mark as completed when forwarded}// Schedule cronif (!wp_next_scheduled('tplguys_check_fulfillment')) {wp_schedule_event(time(), 'every_five_minutes', 'tplguys_check_fulfillment');}
Testing
Always test the full flow in the 3PLGuys sandbox environment (https://sandbox.3plguys.com) before going live. Use WooCommerce test orders to verify the entire webhook → fulfillment → status update cycle.
Next Steps
- Connecting Shopify to 3PLGuys — Same pattern for Shopify stores
- Multi-Channel Fulfillment — Manage WooCommerce alongside other channels
- Automating Pick & Pack — Deep dive into the Pick & Pack API
- Syncing Inventory — Advanced inventory sync strategies