Shopify + Picqer: warehouse koppeling zonder middleware
Terug naar blog

Shopify + Picqer: warehouse koppeling zonder middleware

AuthorRuthger Idema
27 april 202610 min leestijd

Picqer en Shopify praten rechtstreeks met elkaar via REST APIs. Geen iPaaS, geen middleware, geen maandelijkse SaaS-kosten voor een tussenlaag. Wat je bouwt, hoe je het test en wat de valkuilen zijn.

Shopify + Picqer: warehouse koppeling zonder middleware

Picqer en Shopify hebben allebei een goed gedocumenteerde REST API. Ze praten rechtstreeks met elkaar. Geen iPaaS, geen extra SaaS-laag die €500 per maand kost, geen visuele configuratietool die de complexiteit verbergt maar nooit wegneemt.

Dit artikel legt uit hoe je een directe koppeling bouwt tussen Shopify en Picqer: order sync, voorraadterugkoppeling, en de pick & pack flow. Met de concrete API-calls, de aandachtspunten bij schaal en de valkuilen die wij tegenkomen in de praktijk.

Wat je leert in dit artikel

  • Hoe de Shopify-Picqer architectuur eruitziet
  • Order sync van Shopify naar Picqer: API-flow en datamodel
  • Voorraad terugkoppelen van Picqer naar Shopify
  • Trackingnummers automatisch doorzetten naar Shopify
  • Valkuilen bij schaal en hoe je ze voorkomt

De architectuur: twee APIs, één datastroom

Een directe koppeling tussen Shopify en Picqer bestaat uit twee delen:

Shopify → Picqer: nieuwe orders worden via webhooks doorgezet naar Picqer. Picqer neemt de order over voor picking, packing en verzending. Picqer → Shopify: zodra een order verstuurd is, stuurt Picqer het trackingnummer terug naar Shopify. Voorraadmutaties in Picqer worden teruggestuurd naar Shopify om de webshopvoorraad actueel te houden.

De middleware die deze datastroom beheert, schrijven wij doorgaans in Laravel. Een kleine applicatie met twee verantwoordelijkheden: webhooks ontvangen en verwerken, en periodieke sync-taken uitvoeren voor voorraad.

Shopify Webhook          Laravel Middleware          Picqer API
     │                         │                         │
     │── orders/create ────────►│                         │
     │                         │── POST /orders ─────────►│
     │                         │◄── order_id ─────────────│
     │                         │                         │
     │                         │◄── shipment created ─────│
     │◄── fulfillment update ──│                         │
     │                         │                         │
     │                         │◄── stock_changed ────────│
     │◄── inventory update ────│                         │

Order sync van Shopify naar Picqer

Een order aanmaken in Picqer via de API gaat via een POST-request naar /api/v1/orders.

php
class ShopifyOrderToPicqer
{
    public function handle(array $shopifyOrder): string
    {
        // Shopify-order omzetten naar Picqer-formaat
        $picqerOrder = [
            'reference'      => (string) $shopifyOrder['id'],
            'firstname'      => $shopifyOrder['shipping_address']['first_name'],
            'lastname'       => $shopifyOrder['shipping_address']['last_name'],
            'emailaddress'   => $shopifyOrder['email'],
            'telephone'      => $shopifyOrder['shipping_address']['phone'] ?? '',
            'deliveryname'   => $shopifyOrder['shipping_address']['name'],
            'deliveryaddress' => $shopifyOrder['shipping_address']['address1'],
            'deliveryzipcode' => $shopifyOrder['shipping_address']['zip'],
            'deliverycity'   => $shopifyOrder['shipping_address']['city'],
            'deliverycountry' => $shopifyOrder['shipping_address']['country_code'],
            'products'       => $this->mapLineItems($shopifyOrder['line_items']),
        ];

        $response = $this->picqerClient->post('/api/v1/orders', $picqerOrder);

        return $response['idorder'];
    }

    private function mapLineItems(array $lineItems): array
    {
        return array_map(fn($item) => [
            'idproduct'  => $this->lookupPicqerProductId($item['sku']),
            'amount'     => $item['quantity'],
            'price'      => (float) $item['price'],
        ], $lineItems);
    }
}

De lookupPicqerProductId functie is het kritieke punt. Picqer werkt met interne product-IDs. Je moet een mapping bijhouden tussen Shopify-SKUs en Picqer-product-IDs. Die mapping bouw je op door bij de eerste sync alle Picqer-producten op te halen en te indexeren op SKU.

Wat je nooit moet doen: voor elke order een Picqer-product opzoeken via de API. Dat kost 1-2 extra API-calls per orderregel. Bij 50 orders met elk 5 regels zijn dat 250 extra calls. Cache de SKU-naar-product-ID mapping in Redis of de database.

Shopify-webhook ontvangen en valideren

Shopify ondertekent webhooks met een HMAC-hash. Valideer altijd de handtekening voordat je de payload verwerkt.

php
class ShopifyWebhookController extends Controller
{
    public function handleOrderCreate(Request $request): JsonResponse
    {
        // Handtekening valideren
        $hmac = $request->header('X-Shopify-Hmac-Sha256');
        $data = $request->getContent();
        $secret = config('services.shopify.webhook_secret');

        $calculatedHmac = base64_encode(
            hash_hmac('sha256', $data, $secret, true)
        );

        if (! hash_equals($calculatedHmac, $hmac)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        // Order asynchroon verwerken via queue
        ProcessShopifyOrder::dispatch($request->json()->all());

        // Shopify verwacht snel een 200-respons
        return response()->json(['status' => 'queued'], 200);
    }
}

Twee aandachtspunten: geef altijd snel een 200-respons terug. Shopify markeert een webhook als mislukt als de respons langer dan 5 seconden duurt. Verwerk de order asynchroon in een queue job. En valideer altijd de HMAC — anders verwerk je potentieel nep-orders.

Voorraad terugkoppelen naar Shopify

Picqer heeft webhooks die je kunt registreren voor voorraadmutaties. Zodra voorraad verandert — door het inboeken van een inkooporder, het verwerken van een retour of een handmatige correctie — stuurt Picqer een webhook naar jouw middleware.

php
class PicqerStockWebhookController extends Controller
{
    public function handleStockChanged(Request $request): JsonResponse
    {
        $payload = $request->json()->all();

        // Picqer product-ID omzetten naar Shopify inventory item
        $shopifyInventoryItemId = $this->lookupShopifyInventoryItem(
            $payload['idproduct']
        );

        if (! $shopifyInventoryItemId) {
            Log::warning('Geen Shopify inventory item gevonden', [
                'picqer_product_id' => $payload['idproduct'],
            ]);
            return response()->json(['status' => 'skipped'], 200);
        }

        // Voorraad bijwerken in Shopify
        UpdateShopifyInventory::dispatch(
            $shopifyInventoryItemId,
            $payload['stock'],
            $payload['idwarehouse']
        );

        return response()->json(['status' => 'queued'], 200);
    }
}

De Shopify inventory API heeft twee relevante endpoints:

  • POST /admin/api/2024-01/inventory_levels/set.json — zet de voorraad op een exact aantal
  • POST /admin/api/2024-01/inventory_levels/adjust.json — pas de voorraad aan met een delta

Gebruik set als Picqer de absolute voorraad teruggeeft. Gebruik adjust als je een delta wilt doorgeven. Bij twijfel: gebruik altijd set. Een delta die twee keer verwerkt wordt, geeft een fout getal. Een absolute waarde die twee keer verwerkt wordt, geeft hetzelfde correcte getal.

Trackingnummers doorzetten

Picqer stuurt een webhook zodra een order verstuurd is. Die webhook bevat het trackingnummer en de vervoerder. Je zet die informatie door naar Shopify als een fulfillment.

php
class ProcessPicqerShipment implements ShouldQueue
{
    public function handle(): void
    {
        $shopifyOrderId = $this->lookupShopifyOrderId(
            $this->picqerOrderReference
        );

        // Fulfillment aanmaken in Shopify
        $fulfillmentData = [
            'fulfillment' => [
                'location_id'        => config('services.shopify.location_id'),
                'tracking_number'    => $this->trackingNumber,
                'tracking_company'   => $this->carrier,
                'tracking_url'       => $this->trackingUrl,
                'notify_customer'    => true,
                'line_items_by_fulfillment_order' => [
                    [
                        'fulfillment_order_id' => $this->getFulfillmentOrderId(
                            $shopifyOrderId
                        ),
                    ],
                ],
            ],
        ];

        $this->shopifyClient->post(
            "/admin/api/2024-01/fulfillments.json",
            $fulfillmentData
        );
    }
}

Let op: Shopify gebruikt een nieuw fulfillment API-model waarbij je werkt met fulfillment_orders in plaats van directe fulfillments. Het oudere model werkt nog maar is deprecated. Bouw nieuwe koppelingen altijd op het nieuwe model.

De pick & pack flow in Picqer

Picqer organiseert het magazijnproces in drie stappen: picking, packing en verzending. De koppeling met Shopify is primair voor het invoer van orders en het terugkoppelen van resultaten. De interne Picqer-workflow — welke picker welk product pakt, in welke volgorde, via welk lopende band — beheer je in Picqer zelf.

Wat je wel kunt doen via de API: prioriteiten instellen op orders. Een order van een premium klant of een spoedorder kan een hogere pickprioriteit krijgen.

php
$this->picqerClient->put("/api/v1/orders/{$picqerOrderId}", [
    'priority' => 10, // Hogere waarde = hogere prioriteit
]);

Valkuilen bij schaal

Dubbele orderverwerking. Shopify-webhooks worden soms meerdere keren verstuurd. Gebruik idempotency: sla per Shopify order-ID op of de order al verwerkt is, en skip duplicaten.
php
if (PicqerOrder::where('shopify_order_id', $orderId)->exists()) {
    Log::info("Order {$orderId} al verwerkt, skipping");
    return;
}
Rate limits. Picqer heeft een rate limit van 60 requests per minuut. Shopify heeft 2 requests per seconde op standaard, 4 op Plus. Bij drukke periodes — Black Friday, flash sales — loop je hier tegenaan. Bouw rate limit handling in met exponential backoff. Webhook timeouts. Shopify markeert webhooks als mislukt na 5 seconden. Als je middleware traag is, mist je orders. Monitor de responstijd van je webhook-endpoints en zorg dat de verwerking altijd asynchroon gaat. SKU-mismatch. Picqer en Shopify moeten dezelfde SKUs gebruiken. Klinkt logisch, maar in de praktijk zien wij regelmatig shops waar de SKU in Shopify afwijkt van de SKU in Picqer door handmatige invoer of een import die fout ging. Bouw een reconciliatie-script dat dagelijks controleert of alle Shopify-SKUs in Picqer bestaan.

Conclusie

Een directe Shopify-Picqer koppeling is technisch niet complex. De API's zijn goed gedocumenteerd, het datamodel is overzichtelijk en de webhooks werken betrouwbaar.

De uitdaging zit in de details: idempotency, rate limit handling, SKU-consistentie en de overgang naar het nieuwe Shopify fulfillment API-model. Bouw die foutafhandeling van het begin af aan — niet als naschrift.


De middleware die deze koppelingen aandrijft, bouwen wij in Laravel — lees meer over Laravel als e-commerce middleware en hoe het Adapter Pattern je integraties systeemonafhankelijk maakt. Zie ook de Picqer API-documentatie en de Shopify Fulfillment API.

Bouw je een Shopify-Picqer koppeling of wil je je bestaande integratie verbeteren? Bekijk onze Shopify-diensten of neem contact op. Gerelateerde artikelen:
Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check