API Docs

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

WooCommerce → 3PLGuys Flow
WooCommerce Order
Customer checkout
Webhook
order.created fires
3PLGuys PnP
Create & submit shipment
Completed
WooCommerce order marked complete

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 signature
const 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 fulfillment
if (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 draft
const 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 address
await 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 products
const 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 reference
await 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. Submit
const 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;
}
<?php
function 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 address
tplRequest('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. Submit
return 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 100
for (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 minutes
setInterval(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 status
add_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 cron
if (!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