Development

WordPress REST API w praktyce — budujemy integrację ze sklepu z systemem ERP

Krok po kroku: synchronizacja sklepu WooCommerce z zewnętrznym ERP przez REST API. Custom endpointy, autoryzacja JWT, webhooks, obsługa błędów, retry logic.

DDawid Penkala
14 min czytania
Kod REST API na ekranie laptopa

Większość agencji WordPress zatrzymuje się na „zainstalujmy WooCommerce i gotowe”. W praktyce każdy sklep powyżej pewnej skali potrzebuje integracji z zewnętrznymi systemami — ERP do zarządzania produktami, CRM do obsługi klientów, system kurierski dla etykiet przewozowych. Tu wchodzi REST API.

Poniżej opisujemy realną implementację z projektu klienta (anonimizowana nazwa): synchronizacja sklepu WooCommerce (3500 produktów, 400 zamówień/dzień) z systemem ERP Comarch Optima przez custom REST endpoints. Co robiliśmy, czego uniknęliśmy, gdzie się potknęliśmy.

Co klient potrzebował

  • Produkty: jedno źródło prawdy = ERP. Dodanie / edycja / dezaktywacja w ERP → automatycznie na sklepie w ciągu 5 minut.
  • Stany magazynowe: rezerwacja przy zamówieniu (ERP), zwolnienie przy rezygnacji.
  • Ceny: różne per grupa klientów (B2B / B2C). ERP trzymał pełną politykę cenową, sklep tylko renderował odpowiednie.
  • Zamówienia: PO utworzeniu w WooCommerce → transfer do ERP (z rezerwacją stanu, auto-fakturowanie).
  • Klienci: synchronizacja w obie strony (nowy klient B2C na sklepie → konto w ERP; zmiana danych w ERP → aktualizacja konta sklepu).

Wszystko musiało działać niezawodnie — jeden błąd = zdublowana faktura, błędny stan magazynowy, frustrowani klienci.

Architektura — nie „jeden cron co minutę”

Klasyczny błąd: zrobić WP-Cron co 60 sekund, który wyszukuje zmiany w ERP i synchronizuje. Dla 3500 produktów + 100 zmian dziennie = 1440 crona × fetch wszystkiego = zabity ERP i sklep.

Nasza architektura: event-driven + webhooks + idempotency.

ERP Optima
   │ webhook po zmianie

Cloudflare Worker (message queue + limit liczby żądań)


Custom REST endpoint w WP (/wp-json/devance/v1/sync)
   │ JWT auth
   │ process event

WooCommerce update + log w tabeli wp_devance_sync_log

Trzy poziomy: webhook z ERP → Worker jako buffer → WP endpoint robi pracę. Worker dodaje limit liczby żądań i retry. WP endpoint jest idempotentny (ten sam event = ten sam efekt, więc duplicate webhook OK).

Custom REST endpoint — jak się robi porządnie

Bez wtyczek „WP REST API Helper” — czysty kod w mu-plugin:

<?php
// mu-plugins/erp-sync.php

add_action('rest_api_init', function () {
    register_rest_route('devance/v1', '/sync', [
        'methods'             => 'POST',
        'callback'            => 'devance_handle_sync',
        'permission_callback' => 'devance_verify_jwt',
        'args'                => [
            'event_id' => [
                'required'          => true,
                'validate_callback' => fn($v) => preg_match('/^[a-f0-9-]{36}$/', $v),
            ],
            'type' => [
                'required'          => true,
                'enum'              => ['product.updated', 'stock.changed', 'customer.updated'],
            ],
            'payload' => [
                'required' => true,
                'type'     => 'object',
            ],
        ],
    ]);
});

function devance_verify_jwt(WP_REST_Request $request) {
    $auth_header = $request->get_header('Authorization');
    if (!$auth_header || !preg_match('/Bearer (.+)/', $auth_header, $matches)) {
        return new WP_Error('no_token', 'Brak tokenu autoryzacji', ['status' => 401]);
    }

    try {
        $payload = \Firebase\JWT\JWT::decode(
            $matches[1],
            new \Firebase\JWT\Key(DEVANCE_JWT_SECRET, 'HS256')
        );
        if ($payload->iss !== 'erp-optima') {
            return new WP_Error('invalid_issuer', 'Nieznany nadawca', ['status' => 401]);
        }
        return true;
    } catch (\Throwable $e) {
        error_log('[DEVANCE SYNC] JWT decode failed: ' . $e->getMessage());
        return new WP_Error('invalid_token', 'Token nieważny', ['status' => 401]);
    }
}

Kluczowe:

  • permission_callback, nie __return_true. Każdy endpoint musi autoryzować.
  • JWT zamiast WordPress cookies / basic auth. ERP tworzy podpisany token, WP weryfikuje.
  • Walidacja argumentówvalidate_callback + enum. Nieprawidłowy format = 400 od razu, zanim dojdzie do logiki.
  • Secret w wp-config.php, nie w kodzie. define('DEVANCE_JWT_SECRET', '...');

Idempotency — najtrudniejszy problem

Webhook z ERP może być dostarczony 2 razy (network retry, queue worker crashed mid-send). Bez idempotency = dwie identyczne faktury, dwa razy odjęty stan magazynowy.

Rozwiązanie: każdy event ma event_id (UUID). WP trzyma tabelę wp_devance_sync_processed:

CREATE TABLE wp_devance_sync_processed (
    event_id CHAR(36) PRIMARY KEY,
    processed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    result ENUM('success', 'failed', 'skipped') NOT NULL,
    retry_count INT UNSIGNED NOT NULL DEFAULT 0,
    error_message TEXT
) ENGINE=InnoDB;

Handler na początku sprawdza, czy event już był:

function devance_handle_sync(WP_REST_Request $request) {
    global $wpdb;
    $event_id = $request->get_param('event_id');

    $existing = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT result FROM wp_devance_sync_processed WHERE event_id = %s",
            $event_id
        )
    );

    if ($existing) {
        return rest_ensure_response([
            'status' => 'duplicate',
            'original_result' => $existing->result,
        ]);
    }

    // ... actual processing
}

Obsługa błędów — retry z exponential backoff

ERP jest czasem niedostępny (maintenance, timeout). Zamiast ślepego retry, exponential backoff:

function devance_fetch_from_erp($endpoint, $params = [], $attempt = 0) {
    $max_attempts = 5;

    try {
        $response = wp_remote_get(DEVANCE_ERP_URL . $endpoint, [
            'headers' => ['Authorization' => 'Bearer ' . devance_get_erp_token()],
            'timeout' => 10,
            'body'    => $params,
        ]);

        if (is_wp_error($response)) {
            throw new \Exception($response->get_error_message());
        }

        $code = wp_remote_retrieve_response_code($response);
        if ($code >= 500) {
            throw new \Exception("ERP 5xx: $code");
        }

        return json_decode(wp_remote_retrieve_body($response), true);

    } catch (\Throwable $e) {
        if ($attempt < $max_attempts) {
            $wait = pow(2, $attempt); // 1, 2, 4, 8, 16 s
            sleep($wait);
            return devance_fetch_from_erp($endpoint, $params, $attempt + 1);
        }
        error_log("[DEVANCE SYNC] Exhausted retries for $endpoint: " . $e->getMessage());
        throw $e;
    }
}

Uwaga: sleep() w handlerze REST blokuje worker PHP-FPM. Dla production — lepiej dispatch’nąć do background queue (Action Scheduler albo własna queue), handler zwraca 202 Accepted, a proces w tle robi retry.

Webhooks w drugą stronę — WooCommerce → ERP

WooCommerce ma własne webhooks (WooCommerce → Settings → Advanced → Webhooks). Można rejestrować na zdarzenia order.created, order.updated, customer.created.

Format — JSON POST na podany URL, podpisany HMAC SHA256.

U nas każdy webhook idzie do Cloudflare Worker, który:

  1. Weryfikuje HMAC signature
  2. Parsuje payload
  3. Transformuje do formatu ERP
  4. POST do ERP API
  5. Log do Cloudflare KV na potrzeby debugowania

Worker pozwala oddzielić logikę integracji od WP — jeśli transformacja się zepsuje, nie psuje WP; WP po prostu zobaczy 200 OK z Worker, Worker asynchronicznie spróbuje kilka razy.

Monitoring — bo bez niego zepsuje się cicho

Każda integracja ma tabelę logów. Dashboard w admin panelu pokazuje:

  • Liczba synced produktów / zamówień dziennie
  • Liczba failed + jakie błędy
  • Opóźnienie mediana / p95 między event w ERP a effect w WP
  • Alert email jeśli failure rate > 5%

Bez tego — po 3 miesiącach klient krzyczy „produktów nie synchronizuje od tygodnia”, ale Ty się dowiesz o tym dopiero gdy klient zadzwoni. Lepiej mieć alert po 10 failed events w godzinie.

Częste błędy (które widzimy u klientów)

1. Brak ograniczanie liczby żądańu

Klient ma 3500 produktów, ERP wysyła webhook per update. Masz aktualizację cenników „promocja” = 3500 webhooks w minutę. WP REST endpoint przyjmuje wszystkie → PHP-FPM zablokowany → cała strona zdycha.

Fix: limit liczby żądań w Cloudflare Worker. Max 50 requests/min, reszta queueing.

„Zalogujmy się jako admin w ERP i klikajmy, ERP robi requesty z naszym cookie.” Kruche, niebezpieczne, niemożliwe do zautomatyzowania.

Fix: Application Passwords (od WP 5.6) albo JWT. Nigdy cookie.

3. Synchronous wszystko

Klient robi order → WP czeka na odpowiedź ERP (2 sekundy) → pokazuje „Dziękujemy” → user czeka na page load. Dla paid traffic każda sekunda opóźnienia = mniejsza konwersja.

Fix: async. Po create order, dispatch do queue (Action Scheduler), response od razu. ERP synchronizowany w tle.

4. Brak versionowania API

Endpoint /wp-json/devance/v1/sync. Po roku chcesz zmienić format payload. Stary klient ERP jeszcze używa starego formatu. Łamiesz.

Fix: wersjonuj URL od początku. v1, v2. Utrzymuj starą wersję 6 miesięcy przed wygaszeniem.

Kiedy nie warto pisać własnej integracji

Gotowe wtyczki (WP All Import, Zapier, Make / Integromat) są OK dla small scale (do 500 produktów, do 10 zamówień dziennie) i prostych transformacji. Jeśli klient pasuje, zaczynajcie od nich.

Custom REST integration ma sens gdy:

  • Volume > 100 events dziennie
  • Custom business logic (rabaty, loyalty points, dynamic pricing)
  • Integracja z niestandardowym ERP bez gotowego connectora

W Devance większość integracji robimy custom, bo klienci mają niestandardowe systemy (polski Comarch Optima, Subiekt, enova365 — dla każdego inna komunikacja).


Jeśli planujesz integrację WooCommerce ← → ERP/CRM i nie wiesz od czego zacząć — możemy podzielić się konkretnym planem architektonicznym w ramach konsultacji. Dla stałych klientów pakietu opieki custom integracje robimy w ramach godzin pakietu.

Tagi:WordPressREST APIWooCommerceERPintegracjeJWTcustom endpoints
Dawid Penkala
Dawid Penkala

Doświadczony WordPress Developer z ponad 14-letnim stażem w tworzeniu zaawansowanych stron i sklepów internetowych. Specjalizuje się w WordPressie, dedykowanych wtyczkach i motywach.

Więcej o autorze